Skip to content

Commit 529c500

Browse files
feat: theming with css variables (#2386)
* feat: css vars theming * fix: add backward compat aliases, fix hardcoded values, add theming docs (#2441) * fix: add backward compat aliases, fix hardcoded values, add theming docs - Add compat.css with aliases mapping old CSS variable names to new ones (prevents breaking change for customers using --sd-comment-*, --sd-track-*, etc.) - Fix hardcoded #ffffff in CommentDialog.vue input backgrounds (use token) - Fix hardcoded #dbdbdb in CommentHeader.vue hover (use token) - Add --sd-ui-comments-panel-input-background token to variables.css and neon-night theme - Extract cssToken() helper in renderer.ts to eliminate fallback duplication - Simplify applyDevTheme() to single template literal - Add theming overview page (getting-started/theming.mdx) - Add custom themes guide with full per-component variable reference - Add CSS variable migration guide with old-to-new mapping table * fix: wire CSS variable cascade so semantic tier changes propagate to all components Component-specific variables (dropdown, context menu, comments panel, content controls, layout) now reference semantic-tier variables instead of hardcoding hex values. This means setting --sd-ui-surface, --sd-ui-text, --sd-ui-border, --sd-ui-action, and --sd-ui-font-family cascades through every component automatically. Shadows and intentional deviations (tooltip inverse palette, comment card tint, highlight colors) remain component-specific. * refactor: rename CSS variables for consistency and DX Standardize naming convention across all CSS variables: - surface/background → bg (consistent property naming) - context-menu → menu (shorter, no disambiguation needed) - comments-panel → comments (drop redundant -panel-) - State placement: standardize to {state}-{property} pattern (e.g., dropdown-hover-bg not dropdown-surface-hover) Average variable name length drops from ~42 to ~28 chars. 62 variables renamed across 29 files. Backward compat aliases in compat.css already point old (pre-PR) names to new names. * docs: rewrite theming docs to match CLAUDE.md voice and style guidelines - Sentence case headings throughout - Developer register: show code first, explain after - "You" framing, short sentences, no buzzwords - Getting Started page is high-level overview, links to detail - Custom themes guide: resolved default values (not var() refs), "inherits X" for cascading vars, scannable tables - Migration guide: tighter tables, less prose - Remove Tips/Warnings from Getting Started page per page depth rules * fix: add tokens to link popover and comment action buttons - LinkInput: title text, input icon, high-contrast mode, and submit button now use --sd-ui-* tokens instead of hardcoded colors - CommentDialog: reply button text and cancel button use tokens * test: add tests for cssToken helper and compat alias integrity - Extract cssToken() to css-token.ts for testability - Test that cssToken builds correct var() strings with synced fallbacks - Test that every compat alias points to a variable defined in variables.css - Test that compat doesn't re-declare any variable from variables.css - Test key old→new mappings (comment-bg, surface-card, track-insert, etc.) * fix(docs): theme class must be on <html>, not any wrapper element Some SuperDoc elements (popovers, dropdowns) are appended to <body>, so they only inherit CSS variables from <html>. * fix: make old CSS variable overrides actually work (two-way compat) The compat aliases were one-way: setting --sd-comment-bg on a wrapper had no effect because components read --sd-ui-comments-card-bg directly. Fix: new tokens in variables.css now fall back to old names via var(): --sd-ui-comments-card-bg: var(--sd-comment-bg, #f3f6fd); CSS resolves var() at computed-value time, so customer overrides of old names propagate automatically. Setting the new name still works too — it overrides the whole expression. compat.css is trimmed to only aliases where the old name maps to a different concept (no direct fallback possible). All 1:1 renames are handled by the fallbacks in variables.css. * docs: update migration guide to reflect two-way compat mechanism The compat approach changed from one-way aliases in compat.css to var() fallbacks in variables.css. Updated the explanation and code examples. * feat: add createTheme() and buildTheme() helpers for JS-based theming createTheme() takes a typed config object and returns a CSS class name. Apply it to <html> to theme the entire UI. Maps a small set of high-level options (colors, font, radius) to the full CSS variable system. - colors: action, bg, text, border cascade to every component - Component overrides: toolbar, dropdown, menu, comments, highlights, etc. - buildTheme() returns { className, css } for SSR - Style element auto-injected, idempotent on re-call with same name - Exported from superdoc package: import { createTheme } from 'superdoc' - 11 unit tests - Docs updated to lead with createTheme(), CSS variables as advanced option * Revert "feat: add createTheme() and buildTheme() helpers for JS-based theming" This reverts commit 2bf71ff. * feat(theme): add createTheme() and buildTheme() helpers (SD-2255) (#2445) * feat(theme): add createTheme() and buildTheme() helpers (SD-2255) Shadcn-inspired JS theming API. Set ~10 semantic color properties and the entire UI updates. A `vars` escape hatch covers any CSS variable not in the semantic layer. API: createTheme({ colors, font, radius, shadow, vars }) → className buildTheme(config) → { className, css } (for SSR) - colors: action, bg, text, border, etc. map to --sd-ui-* variables - font/radius/shadow: top-level shortcuts - vars: raw CSS variable overrides for power users - Style element auto-injected, idempotent on re-call - Exported from superdoc package - 17 unit tests * refactor(theme): extract generateTheme() to eliminate _lastCss side-channel buildTheme() was reading CSS from a mutable property on createTheme(), which returned stale data when called with an empty config. Both functions now call a shared generateTheme() that returns { className, css } directly. Also extracted injectThemeStyle() for clarity. * docs: update theming pages with createTheme() as primary API Theming overview now leads with createTheme() JS helper. CSS variables moved to "advanced" section. Custom themes guide shows full API options including the vars escape hatch and a dark theme example. * feat: ship AGENTS.md with npm package for AI agent discovery Bun-style approach: agents working in projects that use SuperDoc read node_modules/superdoc/AGENTS.md and instantly know how to use createTheme(), configure the editor, and find all CSS variables. * feat: add theming example and update CLAUDE.md for agent discovery - Add examples/features/theming/ — React + TypeScript demo with 7 themes (3 via createTheme(), 3 presets, 1 default). Follows existing example pattern (comments, track-changes, etc.) - Add theming section to packages/superdoc/CLAUDE.md — agents working in the repo or reading node_modules/superdoc/ find createTheme() API, file pointers, and common vars keys - Add theming rows to root CLAUDE.md "Where to Look" table * chore: update lock file * fix(theme): make buildTheme() pure, document CSP nonce limitation - buildTheme() no longer injects styles into the DOM. It returns { className, css } without side effects, matching its SSR purpose. - createTheme() JSDoc notes that strict CSP environments should use buildTheme() + manual injection with a nonce attribute. - Added test verifying buildTheme does not inject styles. * fix(examples): fix Laravel and Nuxt smoke test failures - Laravel: bind artisan serve to 0.0.0.0 so Playwright can reach it in CI containers (was only listening on 127.0.0.1) - Nuxt: increase body visibility timeout to 10s and use domcontentloaded wait strategy (Nuxt hydration briefly hides body during init) * refactor(theme): convert createTheme to TypeScript Replace JSDoc types with native TypeScript interfaces (ThemeColors, ThemeConfig, ThemeResult). Exports typed interfaces for consumers. Also fix theming example height and restore smoke test assertion. * test: add consumer type checks for theme exports and preset theme validation (#2469) * test: add consumer type checks for theme exports and preset theme validation - Add createTheme/buildTheme imports to consumer-types smoke test so broken .d.ts exports are caught during CI - Add preset theme structural validation to compat.test.ts: verifies all three presets exist and every variable they declare is defined in variables.css * fix(test): use constrained generic so type assertions actually fail on mismatch `type X = never` compiles without error, so the previous assertions were no-ops. `AssertExtends<false>` violates the `extends true` constraint and produces a real compile error. * fix(theme): sanitize theme names and add --sd-ui-action-text variable - Sanitize `name` in createTheme() to strip characters invalid in CSS class names (spaces, slashes, etc.) — prevents classList.add() crash and invalid CSS selectors - Add `--sd-ui-action-text` variable (default #ffffff) for text on action-colored buttons. Replaces incorrect use of `--sd-ui-bg` which produced poor contrast in dark themes - Add `actionText` to ThemeColors interface and COLORS_TO_VARS mapping - Update docs variable reference and dark theme example * docs: add actionText to createTheme() examples --------- Co-authored-by: Caio Pizzol <97641911+caio-pizzol@users.noreply.github.com> Co-authored-by: Caio Pizzol <caio@harbourshare.com>
1 parent 2e10e26 commit 529c500

62 files changed

Lines changed: 2390 additions & 532 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ tests/visual/ Visual regression tests (Playwright + R2 baselines)
6767
| Visual regression tests | `tests/visual/` (see its CLAUDE.md) |
6868
| Document API contract | `packages/document-api/src/contract/operation-definitions.ts` |
6969
| Adding a doc-api operation | See `packages/document-api/README.md` § "Adding a new operation" |
70+
| Theming (`createTheme()`) | `packages/superdoc/src/core/theme/create-theme.js` |
71+
| CSS variable defaults | `packages/superdoc/src/assets/styles/helpers/variables.css` |
72+
| Preset themes | `packages/superdoc/src/assets/styles/helpers/themes.css` |
73+
| Consumer-facing agent guide | `packages/superdoc/AGENTS.md` (ships with npm package) |
7074

7175
## Style Resolution Boundary
7276

@@ -193,12 +197,15 @@ Pixel-level before/after comparison for documents that failed layout comparison.
193197

194198
## Brand & Design System
195199

196-
Brand guidelines, voice, and design tokens live in `brand/`. Token values are defined in `packages/superdoc/src/assets/styles/tokens.css`.
200+
Brand guidelines, voice, and design tokens live in `brand/`.
201+
Token contract source is `packages/superdoc/src/assets/styles/helpers/variables.css` (`:root` defaults).
202+
Preset theme overrides are defined in `packages/superdoc/src/assets/styles/helpers/themes.css`.
197203

198204
**When creating or modifying UI components:**
199-
- Use `--sd-*` CSS custom properties — never hardcode hex values. See `tokens.css` for all available variables.
200-
- Tokens follow three tiers: primitive (`--sd-color-blue-500`) → semantic (`--sd-action-primary`) → component (`--sd-comment-bg`). Components reference semantic or component-level variables.
201-
- Expose component-specific variables as `--sd-{component}-*` so consumers can customize via CSS.
202-
- Document component CSS variables in `apps/docs/ui-components/` (Mintlify docs).
205+
- Use `--sd-*` CSS custom properties — never hardcode hex values.
206+
- Treat `variables.css` as the canonical token contract; add new tokens there.
207+
- Keep preset themes in `themes.css` (`.sd-theme-*`) and override only the tokens that need theme-specific values.
208+
- Tokens are organized by layers: primitive (`--sd-color-blue-500`) → UI/document tokens (`--sd-ui-*`, `--sd-comments-*`, etc.) → component usage.
209+
- Expose UI component-specific variables as `--sd-ui-{component}-*` so consumers can customize via CSS.
203210

204211
**When writing copy or content:** see `brand/brand-guidelines.md` for voice, tone, and the dual-register pattern (developer vs. leader). Product name is always **SuperDoc** (capital S, capital D).

apps/docs/docs.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@
6565
]
6666
},
6767
"getting-started/import-export",
68-
"getting-started/fonts"
68+
"getting-started/fonts",
69+
"getting-started/theming"
6970
]
7071
},
7172
{
@@ -238,6 +239,7 @@
238239
"pages": [
239240
"guides/general/storage",
240241
"guides/general/stable-navigation",
242+
"guides/general/custom-themes",
241243
{
242244
"group": "Collaboration",
243245
"pages": [
@@ -251,7 +253,8 @@
251253
"pages": [
252254
"guides/migration/prosemirror",
253255
"guides/migration/breaking-changes-v1",
254-
"guides/migration/typescript-migration"
256+
"guides/migration/typescript-migration",
257+
"guides/migration/css-variables"
255258
]
256259
}
257260
]
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
---
2+
title: Theming
3+
sidebarTitle: Theming
4+
keywords: "theming, css variables, custom theme, dark mode, design tokens, branding, css customization, createTheme, styling"
5+
---
6+
7+
Set your brand colors in JavaScript and every SuperDoc component updates — toolbar, comments, dropdowns, everything.
8+
9+
```javascript
10+
import { createTheme } from 'superdoc';
11+
12+
const theme = createTheme({
13+
colors: { action: '#6366f1', bg: '#ffffff', text: '#1e293b', border: '#e2e8f0' },
14+
font: 'Inter, sans-serif',
15+
});
16+
17+
document.documentElement.classList.add(theme);
18+
```
19+
20+
Five properties theme the entire UI.
21+
22+
## `createTheme()`
23+
24+
Pass a config object, get back a CSS class name. Apply it to `<html>`.
25+
26+
```javascript
27+
import { createTheme } from 'superdoc';
28+
29+
const theme = createTheme({
30+
name: 'my-brand', // optional — generates sd-theme-my-brand
31+
font: 'Inter, sans-serif',
32+
radius: '8px',
33+
colors: {
34+
action: '#6366f1', // buttons, links, active states
35+
actionHover: '#4f46e5',
36+
bg: '#ffffff', // panels, cards, dropdowns
37+
text: '#1e293b', // primary text
38+
textMuted: '#64748b', // secondary text
39+
border: '#e2e8f0', // all borders
40+
},
41+
// Escape hatch — any CSS variable
42+
vars: {
43+
'--sd-ui-toolbar-bg': '#f8fafc',
44+
'--sd-ui-comments-card-bg': '#f0f0ff',
45+
},
46+
});
47+
48+
document.documentElement.classList.add(theme);
49+
```
50+
51+
`colors` controls the semantic tier — every component inherits from it. The `vars` escape hatch lets you set any `--sd-*` CSS variable directly for fine-grained control.
52+
53+
## SSR support
54+
55+
Use `buildTheme()` to get the raw CSS string for server-side rendering:
56+
57+
```javascript
58+
import { buildTheme } from 'superdoc';
59+
60+
const { className, css } = buildTheme({
61+
colors: { action: '#6366f1', bg: '#ffffff', text: '#1e293b' },
62+
});
63+
64+
const html = `
65+
<html class="${className}">
66+
<head><style>${css}</style></head>
67+
<body><div id="editor"></div></body>
68+
</html>
69+
`;
70+
```
71+
72+
## Preset themes
73+
74+
Three presets ship out of the box. Add the class to `<html>` — some SuperDoc elements (popovers, dropdowns) are appended to `<body>`, so they need to inherit from `<html>`.
75+
76+
<CardGroup cols={3}>
77+
<Card title="Docs" icon="google">
78+
Google Docs aesthetic. Clean blues, compact toolbar, subtle shadows.
79+
```html
80+
<html class="sd-theme-docs">
81+
```
82+
</Card>
83+
<Card title="Word" icon="microsoft">
84+
Microsoft Word aesthetic. Fluent-style borders and surfaces.
85+
```html
86+
<html class="sd-theme-word">
87+
```
88+
</Card>
89+
<Card title="Blueprint" icon="compass-drafting">
90+
High-contrast technical preset. Teal accents, structured layout.
91+
```html
92+
<html class="sd-theme-blueprint">
93+
```
94+
</Card>
95+
</CardGroup>
96+
97+
## CSS variables (advanced)
98+
99+
`createTheme()` generates CSS variables under the hood. You can also set them directly:
100+
101+
```css
102+
:root {
103+
--sd-ui-action: #6366f1;
104+
--sd-ui-bg: #ffffff;
105+
--sd-ui-text: #1e293b;
106+
--sd-ui-border: #e2e8f0;
107+
--sd-ui-font-family: Inter, sans-serif;
108+
}
109+
```
110+
111+
See the [full variable reference](/guides/general/custom-themes#variable-reference) for every token.
112+
113+
## Next steps
114+
115+
<CardGroup cols={2}>
116+
<Card
117+
title="Custom themes guide"
118+
icon="palette"
119+
href="/guides/general/custom-themes"
120+
>
121+
Full API reference, dark theme starter, all CSS variables by component.
122+
</Card>
123+
<Card
124+
title="Migration guide"
125+
icon="arrow-right-arrow-left"
126+
href="/guides/migration/css-variables"
127+
>
128+
Old-to-new variable name mapping. Backward compatibility details.
129+
</Card>
130+
</CardGroup>

0 commit comments

Comments
 (0)