Theme System Architecture
Deep dive into the two-layer variable system that powers theme and dark/light mode switching.
The kit uses a two-layer variable architecture that separates brand identity from light/dark mode. This separation enables independent control over both axes—switch themes and modes without duplicating components, blocks, or pages.
Variable Collections
The system uses two separate Figma variable collections working together:
| Collection | Layer | Purpose |
|---|---|---|
| Theme | Brand Layer | Defines semantic tokens using Tailwind primitives and Shadcn CSS variable names |
| Theme Mode | Light/Dark Layer | Routes components to the correct light or dark values from the active theme |
Theme Collection (Brand Layer)
The Theme collection defines design tokens using:
- Hex values
- Tailwind primitive variables
- Standard Shadcn/Tweakcn CSS variable names
Theme Columns
The collection includes multiple theme columns:
| Column | Description |
|---|---|
| Default | Standard Shadcn variables |
| Amber | Tweakcn-style amber theme |
Each column defines the full semantic token set with both light and dark variants:
background/background-darkforeground/foreground-darkprimary/primary-darkprimary-foreground/primary-foreground-darksecondary/secondary-darkmuted/muted-darkaccent/accent-darkdestructive/destructive-darkborder/border-darkring/ring-dark
These match real Shadcn CSS variable names exactly. The -dark suffix tokens are required to interface with the Theme Mode collection for light/dark switching.
Token Examples
| Token | Default | Amber |
|---|---|---|
primary | neutral/900 | amber/400 |
primary-dark | neutral/50 | amber/300 |
background | white | neutral/50 |
background-dark | neutral/950 | neutral/900 |
This layer controls brand identity. Switching from Default → Amber updates the entire semantic color system for both light and dark modes.
Theme Mode Collection (Light/Dark Layer)
This is the key architectural decision that enables simultaneous theme and mode switching.
How It Works
Instead of components directly referencing primary, they reference:
Theme Mode → primary
Inside Theme Mode:
- Light mode → maps to
Theme.primary - Dark mode → maps to
Theme.primary-dark
Theme Mode acts as a routing layer between components and theme values.
Why Two Layers?
Without this mapping layer, you could:
- Switch themes, OR
- Switch light/dark
But not both cleanly at scale.
With this system:
Changing Theme Switch column in the Theme collection → All semantic tokens update
Changing Mode Switch mode in the Theme Mode collection → Pulls the correct light or dark variant from the active theme
Both can be changed independently.
Data Flow
The architecture follows this hierarchy:
Tailwind Primitives / Hex
↓
Theme Collection (Brand Values)
↓
Theme Mode Collection (Light/Dark Mapping)
↓
Components + 500 Pro Blocks
Components never reference:
- Hex values
- Tailwind primitive tokens
- Raw theme variables
They reference Theme Mode variables only.
That abstraction is what makes global switching possible.
Adding a New Theme
To add a new theme:
- Add a new column in the Theme collection
- Define the semantic tokens
- Done
- Click on any page and in the top right use the variable mode to see your new theme and apply it