The user-defined site theme — palette, styleGuide, fonts, modifiers — lives on ROOT.props (the [Background](components/background.md) node). The SDK reads it at runtime to generate CSS variables, scope them to the page container, and pai…
The user-defined site theme — palette, styleGuide, fonts, modifiers — lives on ROOT.props (the Background node). The SDK reads it at runtime to generate CSS variables, scope them to the page container, and pair with the FOUC pipeline so the first paint matches the design system.
| Field | Notes |
|---|---|
ROOT.props.pallet | Array of { name, color }. Drives palette CSS vars. |
ROOT.props.styleGuide | Style tokens — radius, depth, font families, link/input colors. Built-in keys are catalogued in STYLE_TOKEN_REGISTRY; unknown keys are user-created (custom) tokens. |
ROOT.props.theme.styleGuideMeta | Sparse sidecar Record<string, { appliesTo?, custom? }>. Marks user-created keys as custom: true so the picker shows Delete; optional appliesTo overrides the value-heuristic category for relevance filtering. |
ROOT.props.theme | The effective theme (palette + styleGuide merged). Read via resolveTheme(). |
ROOT.props.theme.typography | Array of typography presets — see text-styles for the picker / editor + storage shape. |
ROOT.props.modifiers | Site-wide modifier classes applied at root. |
End-user CRUD — every token (palette + typography + built-in styleGuide + custom) is created / edited / deleted via the unified VarPicker. The deprecated per-row dropdowns in StylesTab were removed; StylesTab now hosts only the global Density slider, the "Manage Tokens →" entry button, Breakpoints, and Editor preferences.
generateDesignSystemCSSVariables, injectDesignSystemVars, toCSSVarName, toPaletteCSSVarName, toStyleCSSVarNamepaletteToCSSVar)injectTheme for SDK consumers (init-time CSS var injection)@pagehub/sdk/theme.cssSTYLE_TOKEN_REGISTRY: built-in styleGuide key catalogue (label / category / type / quickPicks). Single source of truth for the picker UI.DesignVar[] (palette + typography + every styleGuide key + custom) consumed by VarPicker.var(--name) references across the tree; powers the rename / delete in-use guard.index.tsx orchestrator + VarList + VarEditor).toCSSVarName() converts camelCase to hyphenated CSS var names:
pallet[].name: "BrandAccent" → --brand-accentstyleGuide.radiusBox → --radius-boxHeading + Body fonts live in theme.typography[] as Heading and Body tokens. The orchestrator (generateDesignSystemCSSVariables) derives --heading-font-family, --heading-font-weight, --body-font-family, --body-font-weight from those tokens. Tailwind tokens font-heading / font-body reference the family vars directly. Any other *FontFamily key in styleGuide (e.g. accentFontFamily) auto-generates a CSS var and is recognized by extractGoogleFontsUrl for weight pairing.
CSS vars are scoped to:
| Surface | Selector |
|---|---|
| Editor canvas | #viewport |
Viewer (/view/, custom domains, static) | #ph-site-preview |
Both surfaces also have fallback CSS vars in styles/editor.css matching the SDK default theme — these are safety nets so the canvas never flashes if SSR vars are absent. Values must match packages/sdk/src/theme.css.
generatePageFOUCData() in utils/generatePageFOUCData.ts extracts palette/styleGuide from CraftJS JSON, generates CSS vars, compiles the Tailwind classes used on the page, and returns Google Fonts URLs. <FOUCPreventionHead> renders the result into <Head>. Full pipeline in .claude/rules/css-architecture.md.
External SDK consumers call init({ theme: {...} }). The injectTheme helper writes a <style id="pagehub-sdk-theme"> with CSS vars (--primary, --secondary, --accent, plus cssVariables) into the host container. This is for non-PageHub consumers — PageHub itself uses the FOUC pipeline.
If Primary and Base Content are both very dark neutrals (similar OKLCH lightness), btn btn-outline plus text-base-content reads as dark-on-dark — DaisyUI drives outline button foreground/border from --btn-color (often resolving to primary). Fix in theme data: separate lightness so primary is darker for fills and base-content is lighter for body. See .claude/rules/templates.md (Palette vs outline CTAs).
packages/daisyui-spatial/src/index.css — spatial tokens layered on top