For AI agents: fetch the complete documentation index at /llms.txt, the full documentation corpus at /llms-full.txt, the agent skill at /skill.md, or this page as Markdown at /docs/web/theming.md (or send Accept: text/markdown).
Product theme
Customize kit product chrome for your SaaS — without editing the marketing brand theme.
Setup and configuration
Before any step, read Before you start and Terminal basics. These instructions assume you opened the full supastack monorepo in VS Code; its root package.json marks the repository root. In pnpm commands, a workspace package is an app or shared-code folder; in the product, a workspace is a team's data container and security boundary. corepack pnpm --filter ... selects one package from the monorepo while commands run from its root. If you opened only a synced mobile or desktop folder, it does not contain the Web backend, Docker setup, or root scripts: run the backend from this monorepo or a reachable hosted server, and use the platform folder's own scripts. An environment variable is a named setting stored in a local .env file or your hosting dashboard; restart the relevant app after changing it, and keep server secrets out of NEXT_PUBLIC_ and EXPO_PUBLIC_ values.
1. In VS Code open apps/web/app/app-theme.css for signed-in product screens and apps/web/src/theme/catalog.ts to add a color pack. Do not edit apps/web/app/theme.css for product colors; it controls the marketing brand.
2. From the repository root run corepack pnpm --filter @supastack/web dev, then visit http://localhost:3000/settings/appearance.
3. Change a token in your local branch and refresh the preview. Expected: the dashboard controls update while the homepage keeps its selected marketing theme.
Marketing brand and product theme are separate files. Edit apps/web/app/app-theme.css for dashboard, auth, settings, billing, chat, and org chrome. Add color packs in apps/web/src/theme/catalog.ts. Extend density, radius, font, and layout axes in apps/web/src/theme/presets.ts plus matching html[data-*] rules in app-theme.css. Pick the marketing identity in apps/web/src/theme/marketing-theme.ts (MARKETING_THEME_MODE: locked | inherit | a pack id) instead of editing apps/web/app/theme.css — that file's lock via .marketing-theme:not(.app-theme):not([data-theme]):not([data-marketing-theme="inherit"]) keeps the supastack Obsidian homepage brand by default.
At runtime, Settings → Appearance → ThemePicker stores color, density, radius, font, and layout in browser cookie + localStorage so theme-boot.ts restores all axes before first paint, and syncs the signed-in user's full appearance bundle across browsers through user_appearance (GET/PUT /api/account/appearance). The same page mounts the kit UI gallery so packs and presets live-update buttons, inputs, cards, badges, and dialogs. Design detail and copy-paste examples live in docs/design/kit-product-theme.md and docs/design/kit-ui-gallery.md.
/* Edit product chrome here — not theme.css */
.app-theme {
--radius-card: 14px;
background: var(--page);
color: var(--text);
}
.app-theme .button-primary {
background: var(--accent);
color: var(--accent-ink);
}
/* catalog.ts — append a ThemeDefinition with every THEME_TOKEN_NAMES entry */
{
id: "coral",
name: "Coral",
colorScheme: "light",
tokens: { "--page": "#fff7f4", "--accent": "#e06b4f" /* …full token set… */ }
}Commands to copy
Follow the comments to see which folder a command runs in. If two terminals are labeled, open two terminal windows and keep the first command running while you use the second.
corepack pnpm --filter @supastack/web dev
# In a second terminal, after changing a theme token:
corepack pnpm test -- apps/web/src/theme/contrast.test.tsImplementation workflow
Prefer var(--page|--section|--raised|--text|--muted|--border|--accent|…) in app consumers (app-theme.css, globals.css aliases, WorkspaceShell routes). Do not reintroduce teal/pale hex shells or hardcode Obsidian hex into product UI.
When adding a pack, keep contrast.test.ts green. When adding a preset id, update both presets.ts and the html[data-density|data-radius|data-font|data-layout] remaps in app-theme.css so ThemePicker and FOUC boot stay aligned.
Preserve the shared four-tile brand geometry in marketing/brand-geometry.ts for marketing surfaces. Header marks stay transparent; favicon/app icons keep the rounded black backing. Pure marketing pages keep looking Obsidian while MARKETING_THEME_MODE stays "locked" (the kit default); products may pin another pack or inherit the visitor pick.
Verification and troubleshooting
If a color change affects the marketing homepage, check that you edited app-theme.css and review apps/web/src/theme/marketing-theme.ts. If text becomes hard to read, restore the previous value and run corepack pnpm test -- apps/web/src/theme/contrast.test.ts from the root before trying again.
After editing kit theme tokens or packs, verify ThemePicker on /settings/appearance updates the kit UI gallery plus /dashboard, /auth, /billing, /chat, and org shells. Confirm / stays on the Obsidian marketing lock (unless MARKETING_THEME_MODE says otherwise). Run theme unit contracts (catalog, presets, contrast, theme-ownership, kit-ui-gallery) and application-theme Playwright when available. Check keyboard focus, contrast, 320px, and reduced motion.