Themes
ploxum ships two themes in the first cut, with full light + dark support on the non-brand theme. Themes use a two-layer token model so any tweakcn-generated CSS drops in as a single new file with no translation step.
Included themes
| Theme | Modes | Identity |
|---|---|---|
cinematic | dark-only | Warm amber on near-black. The locked ploxum brand. NASA mission control × Bloomberg Terminal × Linear polish. |
amber-minimal | light + dark | Shadcn-style amber on white / near-black. Light: #ffffff / #262626. Dark: #171717 / #e5e5e5. |
The Cinematic theme is locked dark-only: the mode toggle is hidden when it's active. All other themes may opt into a light variant by defining a .dark override block.
Two-layer token model
Every theme defines tokens at two layers inside a single [data-theme="<id>"] selector:
Outer shadcn alias layer
Every theme defines the standard shadcn semantic names — --background, --foreground, --card, --card-foreground, --popover, --popover-foreground, --primary, --primary-foreground, --secondary, --secondary-foreground, --muted, --muted-foreground, --accent, --accent-foreground, --destructive, --destructive-foreground, --border, --input, --ring, --chart-1..5, plus the full sidebar group.
This lets you drop any tweakcn export into packages/tokens/src/themes/<name>.css and have it Just Work alongside the ploxum components.
Inner ploxum derivation layer
Each theme also re-expresses the richer ploxum hierarchy — --ploxum-color-text-tertiary, four amber tones (-default, -bright, -dim, -tint), four surface tiers (-canvas, -surface, -elevated, -overlay), and four status palettes — in terms of the shadcn names. Every shipping component keeps reading --ploxum-color-* and doesn't need to migrate.
Provider API
PloxumProvider accepts two theme-related props alongside the existing density prop:
<PloxumProvider
theme="amber-minimal" // "cinematic" | "amber-minimal"; default "cinematic"
mode="system" // "light" | "dark" | "system"; default "system"
density="comfortable"
>
{children}
</PloxumProvider>mode is coerced to the theme's single mode when that's the only option (e.g. always "dark" when theme="cinematic"). Selection persists in localStorage under ploxum.theme and ploxum.mode. The provider sets data-theme on <html> and toggles the .dark class to match the resolved mode.
useTheme
Read or set the current theme + mode from any descendant:
import { useTheme } from "@roger-emerson/ploxum-primitives";
function Toolbar() {
const { theme, mode, resolvedMode, setTheme, setMode, themes } = useTheme();
return (
<span>
Current: {theme} · {mode === "system" ? `system → ${resolvedMode}` : mode}
</span>
);
}resolvedMode always resolves "system" to either "light" or "dark" via prefers-color-scheme.
Picker primitives
Three composables ship from @roger-emerson/ploxum-primitives:
<ThemeSwitcher variant="compact" /> {/* dropdown — icon-only chip */}
<ThemeSwitcher variant="full" /> {/* dropdown — labelled with theme name */}
<ModeToggle /> {/* sun/moon — returns null on single-mode themes */}
<ThemePalette value={theme} onChange={setTheme} /> {/* inline card rail */}Drop them anywhere inside a <PloxumProvider>. The compact <ThemeSwitcher> + <ModeToggle> pair fits naturally in a TopBar utility cluster. <ThemePalette> is the rail used in the live demo's themes catalog.
Adding a tweakcn theme
Drop a new theme into the workspace in four steps:
- Paste the tweakcn CSS into
packages/tokens/src/themes/<name>.css. Wrap the light defaults in[data-theme="<name>"]and the dark override in[data-theme="<name>"].dark. - Append the ploxum derivation block to each selector (the template lives in
packages/tokens/src/themes/amber-minimal.css). - Add the entry to
packages/tokens/src/themes/index.mjs(withid,label,description,modes,defaultMode,swatches) andindex.d.ts. - Run
pnpm tokens.
The theme is now selectable in <ThemeSwitcher> and renders across every component without a single component change.
Cinematic-only effects
Visual effects that are only correct under the cinematic palette — the bokeh stage wash on the venue canvas, certain ambient gradients — wrap in the .cinematic-only class. A single CSS rule gates them:
:where(:not([data-theme="cinematic"])) .cinematic-only {
display: none;
}This lives in apps/demo/src/styles/globals.css and ships as a convention for ploxum consumers. The venue map drops its warm wash under Amber Minimal but keeps zones, gates, stadium rings, and the stage marker — same component, different visual register.
See it in action
The live demo's themes catalog shows the active palette swatches on the left and a live-rendered preview on the right. Switching themes there updates the entire demo instantly with no layout shift.
