Theme a scaffolded app to your brand
Start state: a scaffolded app in its default look. End state: an app that looks like YOUR product — a chosen design language, brand accent, dark/light stance, and CSS-level overrides — without touching the shell package. Theming has three layers: scaffold-time defaults, the in-app design panel, and the per-app theme.css escape hatch.
1. Pick the look at scaffold time
pnpm new northstar --title "Northstar" --type webapp \
--design darkluxe --accent gold --mode dark --layout sidebar --features all
There are 100 designs across 14 families (Minimal, Soft, Glass, Brutalist, Retro, Deco, Futuristic, Editorial, Organic, Luxe, Playful, Corporate, Gradient, Dark) and 18 accents. List everything with:
pnpm new --list # or --list --json for the machine manifest
These flags become the app's defaults — what every visitor sees before they touch anything.
2. Explore live with the design panel
Run the app and open the in-app design menu. It drives eight independent axes: mode (light/dark), design, accent, font, radius (sharp→pill), density (compact→spacious), animation (none/subtle/wild), and marketing layout. Changes apply instantly — no rebuild — and there is a shuffle button when you want to be surprised.
Only the axes a user explicitly changes are persisted (a partial override in localStorage). Every untouched axis keeps following the app default — so if you later change the default accent, returning visitors who never picked an accent get the new one.
3. Understand the CSS-variable contract
Every design is a [data-design="name"] block in the shell's stylesheets that restyles the app through CSS custom properties; the other axes overlay as data attributes and two inline variables. Concretely, the chrome sets on <html>:
/* what the chrome writes */
<html data-mode="dark" data-design="darkluxe" data-font="auto"
data-radius="soft" data-density="cozy" data-animation="subtle">
:root { --lab-accent: #d4af37; --lab-accent-contrast: #1a1400; }
All shell components read tokens like --lab-accent, --lab-radius, --lab-font, --lab-border. That contract is why your own pages should style with the same variables (border: 1px solid var(--lab-border), color: var(--lab-accent)) — they then respond to every design switch for free.
4. Override tokens for exact brand colors
The 18 accents are curated, but your brand color is probably none of them. Every app ships src/theme.css, loaded AFTER the shell stylesheet so anything in it wins:
/* src/theme.css — per-app theme overrides. Loaded AFTER the shell stylesheet,
so anything here wins. Override chrome variables or add brand CSS without
ever touching the shell package. */
:root {
--lab-accent: #ff6a00; /* your exact brand orange */
--lab-accent-contrast: #ffffff;
--lab-radius: 4px;
}
Note the interaction: the accent picker sets --lab-accent as an inline style on <html>, and inline styles beat stylesheets — so once a user picks an accent in the panel, it overrides your theme.css value. For a hard brand lock, keep the accent axis at its default and put the color in theme.css; for softer branding, treat theme.css as the default users may personalize away from.
theme.css is also the place for brand CSS beyond tokens: a logo font @font-face, a custom hero background, tweaks to a specific lab- class.
5. Dark and light
mode is its own axis: --mode dark|light sets the default, users flip it in the panel, and every design supports both (data-mode switches surface luminance under the same design). Check your theme.css overrides in both modes — if a brand color needs to differ per mode, scope it:
[data-mode="light"] { --lab-accent: #c2410c; }
[data-mode="dark"] { --lab-accent: #ff6a00; }
What you built
- A branded default look chosen at scaffold time (design + accent + mode + layout)
- Live theming across eight axes via the design panel, persisted per-axis
- Exact brand colors through the src/theme.css override layer — shell untouched, upgrades safe