Skip to Content

Theming

The editor is themed with CSS custom properties. Set them in your own stylesheet on .stesura (the class on the editor root) or on any ancestor of the editor.

/* your-theme.css, loaded after @stesura/core/styles */ .stesura { --stesura-canvas-solid: #fffef5; --stesura-page-border-color: rgba(0, 0, 0, 0.2); --editor-accent-color: oklch(0.65 0.19 55); }
import "./your-theme.css";

Every variable is optional. Without them the editor uses its built-in light and dark defaults.

The UI tokens (--primary, --border, --radius, …) and the icon variables are declared on .stesura itself, so set those on .stesura: a value on an ancestor or on :root doesn’t get past it. To theme one editor, scope the selector (.my-wrapper .stesura). Popups (menus, dialogs, tooltips) render outside the editor but carry .stesura too, so they follow the same overrides. To restyle a component, use .stesura .your-selector or something more specific.

Scoping

@stesura/ui/styles.css only styles elements inside a .stesura element. It follows that:

  • Our components outside the editor need .stesura. If you use @stesura/ui/components/* in your own UI, wrap them in an element with the stesura class, or they render unstyled. Their popups carry the class themselves.
  • Anything you render outside the editor root needs it too. A node view, toolbar extension or menu that portals into <body> (createPortal, a Base UI Portal) must put stesura on the portaled root.
  • Your rules still win from two classes up. Inside the editor, the editor’s rules beat your element and single-class rules. They lose to rules with two or more classes (.prose .lead p) and to !important, including Tailwind’s important: true.
  • Your content inside the editor gets our utilities. In a toolbar slot or a custom node view, a Tailwind class you share with us (p-2, rounded-md) resolves to the editor’s value. Pin your own with .stesura .your-selector.
  • Popups take dark mode from <html> or <body>. .dark there darkens the editor and its popups. .dark on the editor’s wrapper alone darkens the editor but not its popups, which render outside it.

Canvas background

The canvas is the surface the pages sit on. It spans the toolbar’s tab strip and the margins around each page. It is either a solid color or a two-stop linear gradient. Light and dark mode are configured independently: every variable has a -dark counterpart, so theming one mode never changes the other.

Canvas colors must be opaque. The gaps between pages must be opaque (see What the gradient covers), so the editor rejects translucent values such as rgba(…, 0.5) or #fff8: it falls back to the next entry in the resolution order and warns in development.

Solid

.stesura { --stesura-canvas-solid: #fffef5; --stesura-canvas-solid-dark: #0b1220; }

Gradient

Opt in by setting both stops for that mode. direction is optional and defaults to to bottom right.

.stesura { --stesura-canvas-gradient-from: #ffffff; --stesura-canvas-gradient-to: #ffd6d6; --stesura-canvas-gradient-direction: to bottom right; --stesura-canvas-gradient-from-dark: #1a1416; --stesura-canvas-gradient-to-dark: #4a1f24; --stesura-canvas-gradient-direction-dark: to bottom right; }

direction must be one of the CSS to … keywords: to top, to bottom, to left, to right, to top left, to top right, to bottom left, to bottom right. Angles are not supported. Any other value disables the gradient for that mode.

Colors can use any CSS color syntax (hex, rgb(), named colors, oklch(), color-mix()). The editor normalizes them to sRGB and interpolates in sRGB.

Resolution order

For each mode, the editor uses the first of these that applies:

  1. the gradient, if both stops are valid opaque colors and direction is valid or unset
  2. that mode’s solid color
  3. the built-in default

So a gradient in light mode with a solid color in dark mode (or the reverse) works.

What the gradient covers

The gradient is screen-space: it is anchored to the editor’s box, not to the document, so it does not scroll with the pages. It is painted behind the toolbar’s tab strip and the margins around the pages. The raised toolbar ribbon keeps its own opaque background.

The gaps between pages look like windows onto the gradient, but they are opaque: content flows behind mid-table page breaks and would otherwise show through. Each gap paints the slice of the gradient at its on-screen position. That is why the gradient is declared as from/to/direction rather than a free CSS background-image: the slice has to be computable.

The note and header/footer sub-editors paint the gradient across their own panel instead of continuing the canvas behind them.

Changing the theme at runtime

The editor reads the canvas variables on mount and again whenever class changes on <html> or on the editor root, or the OS color scheme changes. To apply other runtime changes (a swapped stylesheet), toggle one of those classes or remount the editor.

Pages

.stesura { --stesura-page-border-color: rgba(0, 0, 0, 0.2); --stesura-page-shadow-color: rgba(0, 0, 0, 0.08); }

Both default to transparent: pages have no border or shadow until a theme sets them. The border is a 1px solid line; the shadow is a 6px blur.

The page background is not themeable. It comes from the document’s docBgColor attribute (white by default), so it round-trips through DOCX and is edited in the UI.

Side panels and toolbar

By default the side panels float as rounded cards with a 4px gap, matching the toolbar ribbon. Set the margin and radius to 0 for panels that fill their column edge to edge:

.stesura { --stesura-panel-margin: 0; --stesura-panel-radius: 0; --stesura-panel-shadow: none; --stesura-panel-divider-color: var(--border); }
VariableDefault
--stesura-panel-margin4pxGap above, below and on the outer side. The side facing the editor never gets one.
--stesura-panel-radiusvar(--radius-md)Corner radius.
--stesura-panel-bordernoneA border shorthand.
--stesura-panel-shadowthe toolbar’s shadowA box-shadow.
--stesura-panel-backgroundvar(--card)The panel’s fill, painted while its content loads.
--stesura-panel-divider-colortransparentThe resize handle’s line.
--stesura-panel-transition-duration200msOpen/close slide. 0s turns it off, as does prefers-reduced-motion.

The toolbar ribbon has its own set, defaulting to its current look:

VariableDefault
--stesura-toolbar-margin0px (0.25rem sideways on wide screens)Gap above and on both sides.
--stesura-toolbar-radiusvar(--radius-md)Corner radius.
--stesura-toolbar-bordernoneA border shorthand.
--stesura-toolbar-shadowa small shadow in var(--secondary)A box-shadow.

On compact viewports panels open as bottom drawers and ignore the panel variables.

The header/footer and footnote editing panels slide over the canvas with their own fill, --stesura-hf-panel-surface (#f3f4f6, #1f2937 in dark mode):

.stesura { --stesura-hf-panel-surface: #eef2f7; }

Accent and icons

.stesura { --editor-accent-color: oklch(0.65 0.19 55); }

Set the accent on .stesura, in a stylesheet loaded after @stesura/core/styles. The accent-derived icon variables (--editor-icon-outline-secondary, --editor-icon-accent-*) are re-declared on .stesura, so override those there too.

Each path in an icon names a semantic layer. --editor-icon-outline-* layers paint strokes, the rest paint fills:

VariableDefaultPurpose
--editor-icon-outline-primarycurrentColorGlyph strokes. Icons follow button text color, including hover and disabled states.
--editor-icon-outline-secondaryaccentStroked accents: indent arrows, line-height marks.
--editor-icon-accent-primary/-secondary/-tertiaryaccentFilled emphasis marks such as list numerals and bullets.
--editor-icon-fill-primarycurrentColorSolid glyph surfaces.
--editor-icon-fill-secondary/-tertiarytransparentOptional interior surfaces.
--editor-icon-constructive / --editor-icon-destructivegreen / redTable actions: insert vs delete.

The PDF and Word brand marks use --editor-icon-brand-pdf and --editor-icon-brand-word. Leave them alone: they are logo colors, and the accent deliberately doesn’t touch them.

Icon set

The iconSet prop picks the icon geometry. Microsoft Fluent is the default; the classic set (the original Stesura icons, completed with lucide) is its own subpath, so only apps that import it ship it. Both use the layer variables above.

import { classicIcons } from "@stesura/icons/classic"; <StesuraEditor iconSet={classicIcons} /* … */ />

Comments

.stesura { --stesura-comment-bg: oklch(71.4% 0.203 305.504 / 20%); --stesura-comment-boundary-color: oklch(71.4% 0.203 305.504 / 40%); --stesura-comment-pending-comment-bg: oklch(71.4% 0.203 305.504 / 80%); --stesura-comment-selected-comment-bg: oklch(71.4% 0.203 305.504); }

The values shown are the defaults: commented text, the range boundary markers, a comment being drafted, and the selected thread.

Callouts

Each built-in callout kind (note, tip, warning, danger, quote) reads a fill, a left-rule and a title colour:

.stesura { --stesura-callout-note-bg: #eff6ff; --stesura-callout-note-border: #2563eb; --stesura-callout-note-title: #1d4ed8; }

The values shown are the note defaults; the others follow the same pattern (--stesura-callout-tip-bg, …). They print as set, and have no dark variant: the page stays a sheet of paper. Custom kinds are styled by data-kind, see Callouts → Custom kinds.

Dark mode

The editor is in dark mode when:

  • an ancestor of the editor, or the editor root, has the .dark class (the next-themes convention with attribute="class"), or
  • no .dark class is present, <html> doesn’t have the .light class, and the OS prefers dark.

data-theme is not read. With a class-based theme switcher, use attribute="class".

In dark mode the editor reads the -dark canvas variables itself, so you don’t need a .dark { … } block for them. Other variables (pages, icons, comments) have a single value: override them inside .dark if dark mode needs different ones.

PDF export

@stesura/pdf-export-client forces a flat white canvas and hides the page border and shadow during export, whatever the theme. Browser printing (Ctrl+P) is not affected.

Variables the editor writes

The editor writes these on the editor root. Read them in your own UI; don’t set them in a theme, they are overwritten.

VariableValue
--stesura-canvas-surfaceThe canvas’s flat color: the solid color, or the gradient’s from stop. Use it where a plain background should match the canvas.
--stesura-canvas-gradientThe active gradient as a CSS linear-gradient(…), or none for a solid canvas.
Last updated on