Theming
Stylesheets
Load two entry points once, in your app layout:
@import "@zuilib/tokens/styles.css"; /* or tokens.css without Tailwind */
@source "../node_modules/@zuilib/text-editor/dist"; /* Tailwind v4 hosts */
@import "@zuilib/text-editor/styles.css";
| Entry | Role |
|---|---|
@zuilib/tokens/styles.css (or tokens.css) | The design tokens the editor's chrome reads: --primary, --popover, --border, --muted, --radius, --shadow-medium, … Optional: without them the editor falls back to its own defaults |
@zuilib/text-editor/styles.css | Editor chrome, tables, rails, canvas, code colours, layout. Required |
Document content (headings, lists, inline code, quotes) is styled with
Tailwind utility classes such as text-4xl, font-bold, ml-8,
bg-muted. A Tailwind v4 host must @source the package so those classes
are generated; a host without Tailwind can style the block classes below
directly.
Dark mode
The editor follows the ZUI convention: a dark class on <html> (or a
common ancestor). The chrome reads the tokens, which switch under
.dark; the parts the editor owns itself (code colours, the drawing
surface) have their own .dark rules.
document.documentElement.classList.toggle('dark', isDark)
The drawing area inverts Excalidraw-style under .dark so stored shape
colours stay theme-agnostic; --zui-drawing-dark-filter: none opts out.
Block classes
Every top-level block carries a stable class next to its utility classes, for styling and for the layout bleed variable:
| Class | Block |
|---|---|
.zui-paragraph | Paragraph |
.zui-heading, .zui-heading-1 … .zui-heading-6 | Headings |
.zui-list, .zui-list-ordered, .zui-list-unordered, .zui-checklist | Lists |
.zui-quote | Blockquote |
.zui-code, .zui-token-<type> | Code block and its tokens |
.zui-frontmatter | Frontmatter block |
.zui-table, .zui-table-cell, .zui-table-cell-header, .zui-table-rail | Table, cells, rails |
.zui-drawing, .zui-drawing-canvas (+ .is-ink, .is-text-width) | Drawing block and canvas |
Structural wrappers: .zui-text-editor (root, height: 100%, scrolls),
.zui-text-editor-main (the content pane; a CSS container),
.zui-text-editor-content (the editable surface),
.zui-text-editor-textarea (the edit-raw textarea).
Custom properties
Layout
| Property | Set on | Meaning |
|---|---|---|
--zui-text-editor-measure | .zui-text-editor or an ancestor | Max width of the text column (the measure prop writes this) |
--zui-text-editor-gutter | derived on .zui-text-editor-main | Pane padding: 2rem without a measure |
--zui-text-editor-bleed | derived on .zui-text-editor-main | How far a full-width block extends past the column |
--zui-text-editor-block-bleed | any block class | Per-block override of the bleed |
Code colours
--zui-code-<type> on .zui-code, one per token type, with light and
.dark defaults; see Code blocks.
Drawing canvas
The canvas chrome (header, property row, popover, selection handles) is
styled entirely through --zui-drawing-* custom properties. They default
to the ZUI tokens, so the canvas follows the host theme in both modes.
Override any of them on .zui-drawing-canvas or an ancestor:
.my-app .zui-drawing-canvas {
--zui-drawing-accent: #0f766e; /* selection, active tool, handles */
--zui-drawing-surface: #fbfbf7; /* drawing area (pre dark-mode filter) */
--zui-drawing-grid: rgba(0, 0, 0, 0.12);
--zui-drawing-grid-size: 16px;
--zui-drawing-font: 'Inter', sans-serif;
}
| Variable | Default | Used for |
|---|---|---|
--zui-drawing-accent | var(--primary) | Selection frame and handles, active tool, focus ring, bind highlight |
--zui-drawing-foreground | var(--foreground) | Header text and icons |
--zui-drawing-muted-foreground | var(--muted-foreground) | Swatch labels, empty-state hint |
--zui-drawing-border | var(--border) | Block border, header divider, popover border |
--zui-drawing-chrome | var(--background) | Header and property row background |
--zui-drawing-popover / -popover-foreground | var(--popover) / var(--popover-foreground) | "More shapes" menu |
--zui-drawing-danger / --zui-drawing-success | var(--destructive) / var(--success) | Delete button, "copied" state |
--zui-drawing-radius / --zui-drawing-control-radius | var(--radius-lg) / var(--radius-sm) | Block corners / buttons and menus |
--zui-drawing-shadow | var(--shadow-medium) | Popover |
--zui-drawing-font | system sans | Shape text and the inline editor (Recursive under .is-ink) |
--zui-drawing-surface / --zui-drawing-grid / --zui-drawing-grid-size | white / 9% black / 20px | Drawing area and its dot grid, before the dark filter |
--zui-drawing-dark-filter | invert(93%) hue-rotate(180deg) | Applied to the drawing area under .dark; none opts out |
--zui-drawing-hover / --zui-drawing-active | currentColor at 8% / 12% | Button hover and pressed backgrounds |
The table rails and the floating table toolbar use the same tokens
(--primary, --popover, --border, --shadow-medium, --destructive
for the delete tint); their motion respects prefers-reduced-motion.
Example: a subtree retheme
The wrapper around this editor sets --primary: #0f766e (teal), the
drawing accent and grid, and a squarer --zui-drawing-radius. Only the
chrome that reads the custom properties directly changes; Tailwind
utilities such as text-primary resolve at :root, so a subtree override
does not reach them.
.example-teal {
--primary: #0f766e;
--zui-drawing-accent: #0f766e;
--zui-drawing-surface: #fbfbf7;
--zui-drawing-grid: rgba(15, 118, 110, 0.18);
--zui-drawing-grid-size: 16px;
--zui-drawing-radius: 0.25rem;
}
.dark .example-teal {
--primary: #2dd4bf;
--zui-drawing-accent: #2dd4bf;
}