Custom Themes
How to author a custom Cynosure theme by overriding semantic CSS tokens for a data-theme selector.
Authoring a custom theme
Section titled “Authoring a custom theme”A Cynosure theme is a CSS file that overrides the semantic tokens for a
data-theme selector. That’s the whole API.
Define the overrides
Section titled “Define the overrides”Create src/themes/brand.css:
[data-theme='brand'] { /* Accent family — buttons, links, focus rings. */ --cynosure-color-accent-solid: #0f766e; --cynosure-color-accent-solid-hover: #115e59; --cynosure-color-accent-soft: rgba(15 118 110 / 0.12); --cynosure-color-accent-soft-hover: rgba(15 118 110 / 0.2); --cynosure-color-accent-fg: #0f766e;
/* Surface family — backgrounds, cards, inputs. */ --cynosure-color-surface-default: #ffffff; --cynosure-color-surface-subtle: #f6fbfa; --cynosure-color-surface-muted: #ecf5f3;
/* Foreground family — body text, headings, borders. */ --cynosure-color-fg-default: #042f2e; --cynosure-color-fg-muted: #134e4a; --cynosure-color-fg-subtle: #5eead4; --cynosure-color-border-default: #d6e4e1;
/* Optional: signal the browser colour-scheme so native controls match. */ color-scheme: light;}Only override what you need. Unset tokens inherit from base.css.
Ship it as a side-effect import
Section titled “Ship it as a side-effect import”If you’re authoring a theme package:
{ "name": "my-brand-theme", "exports": { "./cynosure": "./dist/cynosure.css" }, "sideEffects": ["./dist/*.css"]}Consumers import it once:
import 'my-brand-theme/cynosure';Enable the theme
Section titled “Enable the theme”Add the theme name to your ThemeProvider:
<ThemeProvider themes={['light', 'dark', 'brand']} defaultTheme="brand"> {children}</ThemeProvider>ThemeProvider sets data-theme="brand" on <html>, and the override
CSS takes effect.
Suggested coverage
Section titled “Suggested coverage”Minimum tokens you probably want to override:
| Family | Tokens |
|---|---|
| Accent | *-solid, *-solid-hover, *-soft, *-soft-hover, *-fg |
| Surface | *-default, *-subtle, *-muted |
| Foreground | *-default, *-muted, *-subtle |
| Border | *-default, *-muted |
| Feedback | success.solid, warning.solid, danger.solid |
| Focus | shadow-focus-ring |
Testing a custom theme
Section titled “Testing a custom theme”Cynosure ships a contrast audit script that walks compiled CSS and asserts
WCAG 2.1 AA pairs. Add your theme to scripts/audit-contrast.mjs to
run the check against CI:
pnpm audit:contrastScope to a subtree, not the whole page
Section titled “Scope to a subtree, not the whole page”Sometimes you want a theme only inside a specific container (for example a
“preview” area). Set data-theme on an element other than <html>:
<div data-theme="brand"> {/* children here render with the brand palette */}</div>All Cynosure components read CSS custom properties through normal cascade, so scoping “just works”.