This page is the theme source of truth for Ghost UI. Components consume semantic roles. Hosts may change the active palette or density in one place. Components do not target a named gray, a page-specific surface, or a compatibility alias.
Contract
The contract has two layers:
- Semantic roles are the component API:
--background,--foreground,--card,--popover,--primary,--secondary,--muted,--accent,--destructive,--border,--input, and--ring. - Raw scales are palette inputs:
--color-{palette}-{step}, with steps from 50 to 950. Components do not reference these steps directly.
Neutral is the default active palette. Neutral, Stone, Zinc, Mauve, Olive, Mist, and Taupe are supported through data-ui-palette. Dark mode changes the semantic mapping; component code stays unchanged.
Default palette: Neutral
Each role follows light/dark mode and the active palette.
Use a semantic role in a component or host override:
Do not replace this with --ink, --paper, --canvas, --surface, --elevated, or a component-specific gray. Those names describe an implementation target. The semantic role already describes the job and keeps the system portable.
Backgrounds
Use background roles by purpose. They are shared across components.
| Role | Light | Dark | Use |
|---|---|---|---|
--background-primary | White | Neutral 950 | Page and main content |
--background-primary-subtle | Neutral 50 | Neutral 900 | Sidebar, toolbar or grouped section |
--background-secondary | Neutral 100 | Neutral 800 | Filled tab container and supporting surfaces |
--background-primary-hover | Accent | Accent | Interactive highlight on the main background |
--background-primary-subtle-hover | Accent | Accent | Interactive highlight on a subtle background |
--background-secondary-hover | Secondary at 80% opacity | Secondary at 80% opacity | Secondary surface hover |
Tailwind exposes these as bg-background-primary, bg-background-primary-subtle, bg-background-secondary and their hover:bg-…-hover utilities. Existing --background, --secondary and --muted roles use these same backgrounds. Cards and popovers remain separate roles for host overrides.
Hover follows shadcn: secondary actions use their color at 80% opacity; primary actions use their color at 90%. The parent background shows through. Text and icons keep their opacity.
Palette scales
Every neutral family exposes the same 50–950 scale. The active family feeds the semantic roles. A palette switch changes the system without changing component markup.
The public switch is setUiPalette("stone") or data-ui-palette="stone". Omit the attribute to use Neutral. Keep raw steps for palette specimens, charts, or deliberate data scales only.
Spacing
Spacing is a portable 4px scale. Semantic roles name recurring relationships: card padding, control padding, heading-to-description, heading-to-helper, list gaps, and section rhythm. Use gap for sibling relationships.
4 · 8 · 16 · 32 · 64. Use semantic roles for recurring jobs.
Token names: --space-1 through --space-16, --space-card-x, --space-card-y, --space-control-x, --space-control-y, --gap-heading-desc, --gap-heading-helper, --gap-list, --gap-section-x, and --gap-section-y.
Radius and borders
Radius uses the shadcn base --radius and derived steps. Controls normally use --radius-md; cards use --radius-lg; nested surfaces step down one level. Borders use --border. Focus uses --ring.
Type and motion
Typeset owns HTML reading rhythm. This page only proves the shared type and motion inputs. See Typeset for the content contract.
IBM Plex Sans is the shared heading and body voice.
Motion uses --motion-short, --motion-standard, and --motion-long; reduced motion sets them to zero.
Focus and states
Every interactive surface keeps a visible 2px --ring outline. Status is never color-only. Pair color with text, an icon, or structure. Coarse-pointer targets meet 44px.
Primitive examples
These small components consume the same roles. They are not a second theme.
Main action color
Keep backgrounds Neutral and set --primary plus its foreground at a theme root. Hover follows the chosen color automatically.
Host customization
Customize the theme by changing a palette or semantic role at the host boundary. Do not fork component CSS.
Set the same roles in your .dark scope when dark mode needs a different main color. Primary hover and pressed colors derive from --primary; no component CSS edits are needed. Use --accent for menu and quiet-action highlights, independently from the primary action color.