Theme contract
Every Crema theme (lib-theme-*) must declare these CSS variables so the
shell, AI surfaces, and shadcn primitives render correctly. Missing a variable
won't crash anything — it'll just look wrong (transparent backgrounds, missing
borders, broken focus rings, etc.).
A theme is a single CSS file that declares variables under :root for light
mode and under .dark for dark mode. Themes may also @import url(...) font
packages at the top of the file — they ship self-contained.
The shell expects the theme to be the first import in the consuming app's
entry CSS, so any embedded font URLs land at the top of the resolved output
(CSS spec requires @import before any other rules).
Required tokens
Surfaces
| Token |
Used for |
--background |
Page background |
--foreground |
Default text color |
--card |
Card surfaces, inputs, dropdowns |
--card-foreground |
Text on cards |
--popover |
Popover/menu surfaces |
--popover-foreground |
Text on popovers |
--muted |
Subtle backgrounds (hover states, toolbars, code blocks) |
--muted-foreground |
Subtle text (timestamps, hints, labels) |
--accent |
Hover/focus highlight on items |
--accent-foreground |
Text on accent backgrounds |
Brand & status
| Token |
Used for |
--primary |
Brand color, primary buttons, active nav, ripple |
--primary-foreground |
Text on primary surfaces |
--secondary |
Secondary buttons |
--secondary-foreground |
Text on secondary |
--destructive |
Errors, sign-out, destructive menu items |
--success |
Confirmations |
--success-foreground |
Text on success surfaces |
Borders, inputs, focus
| Token |
Used for |
--border |
Dividers, default borders |
--input |
Input field borders |
--ring |
Focus rings |
| Token |
Used for |
--sidebar |
Rail background |
--sidebar-foreground |
Rail text |
--sidebar-primary |
Active nav background |
--sidebar-primary-foreground |
Active nav text |
--sidebar-accent |
Rail hover |
--sidebar-accent-foreground |
Text on rail hover |
--sidebar-border |
Rail borders |
--sidebar-ring |
Rail focus ring |
Charts & syntax (optional but recommended)
| Token |
Used for |
--chart-1 … --chart-5 |
Chart series colors; --chart-3 doubles as warning amber |
--syntax-keyword, --syntax-string, --syntax-number, --syntax-comment, --syntax-function, --syntax-type |
Code-block highlighting |
Typography
| Token |
Used for |
--font-heading |
Heading font stack |
--font-sans |
Default UI font stack |
--font-ai-prose |
Assistant prose (the AI surface uses this for replies) |
--font-mono |
Code, tabular numbers |
Type scale
Each pair declares a size and matching line height:
--text-display-size / --text-display-lh
--text-headline-size / --text-headline-lh
--text-title-size / --text-title-lh
--text-body-size / --text-body-lh
--text-label-size / --text-label-lh
--text-caption-size / --text-caption-lh
Motion
| Token |
Used for |
--duration-fast, --duration-base, --duration-slow, --duration-slower, --duration-spring |
Animation lengths |
--ease-standard, --ease-emphasized, --ease-decelerate, --ease-accelerate |
Standard easing curves |
--ease-spring-gentle, --ease-spring-snappy, --ease-spring-bouncy |
Spring curves |
Radii & elevation
| Token |
Used for |
--radius |
Base radius (sm/md/lg/xl/2xl computed from this in app.css) |
--elevation-0 … --elevation-5 |
Shadow scale |
--border-width-thin, --border-width-base, --border-width-thick, --border-width-heavy |
Stroke widths |
Scoping conventions
:root { … } — theme defaults (light mode).
.dark { … } — dark-mode overrides.
[data-theme="<name>"] { … } — alternate-scoped theme. Only needed if a
theme should coexist with another on the same page (e.g. an AI surface uses
a different theme than the rest of the app). Single-theme apps don't need
this scope.
Adding a new theme
- Clone any existing theme as a starting point —
lib-theme-mightypix is a
good reference because it's complete.
- Edit tokens (and font
@import url if changing fonts).
- Update the consuming app's
app.css:
- Done. Components automatically pick up the new look — no changes needed in
any TSX file.
Multi-theme apps (e.g. mightypix on AI surfaces only)
If a theme defines [data-theme="<name>"] instead of (or in addition to)
:root, you can switch surfaces per route via the shell's theme prop:
The shell wraps itself in data-theme={theme} so the alt theme's tokens
cascade through that subtree only.