Skip to main content

Theme & UI styling

@ydesign/react-editor UI is driven by built-in shadcn components + semantic CSS variables. Import one stylesheet, then use the built-in theme API for light/dark and accent presets—or override CSS variables for brand styling.

Styling stack

LayerStackRole
ComponentsBuilt-in shadcn (Button, Dialog, Select, Tooltip…)All editor form / feedback UI
TokensSemantic CSS variables (--primary, --background…)Color, radius, borders; light/dark and presets write here
UtilitiesTailwind CSS v4 (compiled into style.css)Layout, spacing, dark: variants
Layout shellsstyled-componentsDesignEditorContainer / SidePanelWrap / WorkspaceWrap
Theme API@ydesign/react-editor/themeuseTheme / setTheme / ThemeSwitcher
Canvas engine@ydesign/coreNo UI stylesheet

You do not need to install Tailwind, shadcn, or a separate token package:

import '@ydesign/react-editor/style.css';

1. Quick start: built-in theme switcher

import '@ydesign/react-editor/style.css';
import { ThemeSwitcher } from '@ydesign/react-editor/theme';

export function Topbar() {
return (
<header>
<ThemeSwitcher />
</header>
);
}

Or split the controls:

import { ModeToggle, PresetPicker } from '@ydesign/react-editor/theme';

<ModeToggle />
<PresetPicker />

Theme state is a module singleton, persisted in localStorage (ydl-theme). ThemeProvider is optional—only for default values on mount:

import { ThemeProvider } from '@ydesign/react-editor/theme';

<ThemeProvider defaultMode="system" defaultPreset="blue">
<App />
</ThemeProvider>

2. How theming works

setTheme({ mode, preset })


document.documentElement
├─ classList.toggle('dark', isDark)
└─ style.setProperty('--primary', …)
style.setProperty('--primary-foreground', …)
style.setProperty('--sidebar-primary', …)

AxisValuesEffect
mode'light' | 'dark' | 'system'Toggles .dark on <html>; system follows prefers-color-scheme
preset'neutral' | 'blue' | 'orange' | …Writes --primary / --sidebar-primary (separate light/dark values)
resolvedMode'light' | 'dark'Resolved mode when system is selected (read-only)

Presets are listed in PRESETS (neutral, amber, blue, cyan, emerald, fuchsia, green, indigo, lime, orange, pink, purple, red, rose, sky, teal, violet, yellow, …). Defaults: mode: 'light', preset: 'neutral'.

Base tokens live in package globals.css (:root / .dark). Tailwind dark mode uses @custom-variant dark (&:is(.dark *)).


3. Programmatic control

3.1 React: useTheme

import { useTheme } from '@ydesign/react-editor/theme';

function BrandBar() {
const { mode, resolvedMode, preset, color, setMode, setPreset, toggleMode } = useTheme();

return (
<div>
<span>
{resolvedMode} / {preset}
</span>
<button type="button" onClick={() => setMode('dark')}>
Dark
</button>
<button type="button" onClick={() => setPreset('orange')}>
Orange brand
</button>
<button type="button" onClick={toggleMode}>
Cycle light → dark → system
</button>
<span style={{ color }}>Accent</span>
</div>
);
}

3.2 Non-React / boot: setTheme

import { setTheme, getTheme, subscribeTheme } from '@ydesign/react-editor/theme';

setTheme({ mode: 'system', preset: 'blue' });

const current = getTheme();

const unsub = subscribeTheme(state => {
console.log('theme changed', state);
});

4. Brand customization via CSS variables

:root {
--primary: oklch(0.55 0.18 40);
--primary-foreground: oklch(0.99 0 0);
--radius: 0.5rem;
--font-sans: 'Inter', system-ui, sans-serif;
}

.dark {
--primary: oklch(0.72 0.14 40);
--primary-foreground: oklch(0.2 0 0);
}
VariableRole
--background / --foregroundPage surface / body text
--card / --popoverCards, popovers
--primary / --primary-foregroundPrimary actions, highlights
--muted / --muted-foregroundSubtle surfaces / secondary text
--border / --input / --ringBorders, inputs, focus ring
--destructiveDangerous actions
--sidebar*Sidebar tokens
--radiusGlobal radius base

Tailwind semantic colors map to the same tokens: bg-background, text-foreground, bg-primary, border-border, …

If you also use ThemeSwitcher / setTheme({ preset }), presets will overwrite --primary at runtime. Lock a preset, or apply your own setProperty without fighting the picker.


5. Local style overrides

.polotno-app-container {
background: var(--background);
color: var(--foreground);
}

.polotno-side-panel-tab.active {
color: var(--primary);
}

.polotno-workspace-container {
background: var(--muted);
}

Some legacy class names still use the polotno- prefix. Prefer semantic variables and Tailwind in new code.

<div className="flex h-screen bg-background text-foreground">
<aside className="w-64 border-r border-border bg-sidebar text-sidebar-foreground"></aside>
<main className="flex-1"></main>
</div>

6. Fonts

ScopeHow
UI fontOverride --font-sans / --font-heading, or set font-family on a shell
Canvas text fontsIndependent; use store.fonts — see Configuration · Fonts
:root {
--font-sans: 'Inter', system-ui, sans-serif;
}

7. Notes

  1. Always import '@ydesign/react-editor/style.css'.
  2. Consumers need not install Tailwind; style.css already includes compiled utilities. If your app also uses Tailwind v4, avoid clashing semantic color definitions.
  3. Dark mode is activated via <html class="dark">. Prefer setTheme / ThemeSwitcher over ad-hoc class toggles.
  4. SSR: set .dark (and critical variables) before paint to avoid a flash; useTheme has a light/neutral server snapshot.
  5. @ydesign/core canvas is unthemed; page background is design data (workarea fill), not UI --background.

Further reading