# 5.1 — Theme System
**Previous Step:** ← [Templates](../../04-file-organization/4.2-templates/4.2-templates.md)
**Next Step:** → [Mobile Experience](../../06-mobile-experience/6.1-mobile-shell/6.1-mobile-shell.md)
---
## Page Metadata
| Property | Value |
|----------|-------|
| **Scenario** | 05 — Theming |
| **Page Number** | 5.1 |
| **Platform** | Web (PWA), portable to macOS/Windows |
| **Page Type** | Dropdown Panel + Settings Section |
---
## Overview
**Page Purpose:** Enable users to personalize their workspace with preset themes and custom accent colors. Theming is a personality differentiator — especially the Matrix theme, which gives CalcText a unique identity in the calculator app market.
**Success Criteria:**
- Theme switches instantly (< 16ms, no flash of unstyled content)
- Matrix theme makes users want to screenshot and share
- Custom accent color gives ownership feeling
- Theme persists across sessions
---
## Theme Architecture
All theming works through CSS custom properties on `:root`. Switching themes = swapping property values. No component changes, no re-renders beyond what CSS handles natively.
```typescript
interface ThemeDefinition {
id: string
name: string
icon: string // emoji or svg
tokens: {
// Backgrounds
bg: string
bgSecondary: string
codeBg: string
// Text
text: string
textH: string
// Borders
border: string
// Accent
accent: string
accentBg: string
accentBorder: string
// Semantic
success: string
warning: string
error: string
// Syntax highlighting
syntaxVariable: string
syntaxNumber: string
syntaxOperator: string
syntaxKeyword: string
syntaxFunction: string
syntaxCurrency: string
syntaxComment: string
syntaxHeading: string
// Result colors
resultNumber: string
resultUnit: string
resultCurrency: string
resultDatetime: string
resultBoolean: string
// Stripes
stripe: string
// Special
fontOverride?: string // for Matrix: force monospace everywhere
specialEffect?: string // CSS class for effects like scanlines
}
}
```
---
## Preset Themes
### Light
| Token | Value |
|-------|-------|
| --bg | #ffffff |
| --bg-secondary | #f8f9fa |
| --text | #6b6375 |
| --text-h | #08060d |
| --border | #e5e4e7 |
| --accent | #6366f1 |
| --stripe | rgba(0, 0, 0, 0.02) |
| Feel | Clean, airy, professional |
### Dark
| Token | Value |
|-------|-------|
| --bg | #16171d |
| --bg-secondary | #1a1b23 |
| --text | #9ca3af |
| --text-h | #f3f4f6 |
| --border | #2e303a |
| --accent | #818cf8 |
| --stripe | rgba(255, 255, 255, 0.025) |
| Feel | Calm, focused, modern |
### Matrix
| Token | Value |
|-------|-------|
| --bg | #0a0a0a |
| --bg-secondary | #0f1a0f |
| --text | #00ff41 |
| --text-h | #33ff66 |
| --border | #003300 |
| --accent | #00ff41 |
| --stripe | rgba(0, 255, 65, 0.03) |
| --syntax-* | Green spectrum (#00cc33 to #39ff14) |
| --result-currency | #ffff00 (yellow — stands out in green) |
| fontOverride | `'Courier New', 'Fira Code', monospace` |
| specialEffect | `matrix-scanlines` |
| Feel | Iconic, hackery, fun |
#### Matrix Special Effects
**Scanlines (optional, subtle):**
```css
.matrix-scanlines::after {
content: '';
position: fixed;
inset: 0;
pointer-events: none;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.08) 2px,
rgba(0, 0, 0, 0.08) 4px
);
z-index: 9999;
}
```
**Cursor glow:**
```css
.matrix-theme .cm-cursor {
border-color: #00ff41;
box-shadow: 0 0 4px #00ff41, 0 0 8px rgba(0, 255, 65, 0.3);
}
```
**Text glow (subtle):**
```css
.matrix-theme .result-value {
text-shadow: 0 0 2px rgba(0, 255, 65, 0.3);
}
```
### Midnight
| Token | Value |
|-------|-------|
| --bg | #0f172a |
| --bg-secondary | #1e293b |
| --text | #94a3b8 |
| --text-h | #e2e8f0 |
| --border | #334155 |
| --accent | #38bdf8 (sky-400) |
| --stripe | rgba(56, 189, 248, 0.03) |
| Feel | Deep blue, serene, late-night coding |
### Warm
| Token | Value |
|-------|-------|
| --bg | #fffbf5 |
| --bg-secondary | #fef3e2 |
| --text | #78716c |
| --text-h | #1c1917 |
| --border | #e7e5e4 |
| --accent | #f97316 (orange-500) |
| --stripe | rgba(249, 115, 22, 0.03) |
| Feel | Paper-like, warm, comfortable for long sessions |
---
## Theme Picker Dropdown
**OBJECT ID:** `theme-picker`
| Property | Value |
|----------|-------|
| Trigger | Click theme icon in header or status bar, or Ctrl+Shift+T |
| Position | Dropdown below header button, right-aligned |
| Width | 240px |
| Background | var(--bg) |
| Border | 1px solid var(--border) |
| Border radius | 8px |
| Shadow | 0 4px 16px rgba(0, 0, 0, 0.15) |
| Padding | space-xs (6px) |
| Animation | Scale from 95% + fade, 150ms ease-out |
| Dismiss | Click outside, Esc, or select theme |
### Theme Picker Layout
```
┌──────────────────────────┐
│ Themes │ section header
│ │
│ ☀️ Light ✓ │ active indicator
│ 🌙 Dark │
│ 💻 Matrix │
│ 🌊 Midnight │
│ 📜 Warm │
│ │
│ ────────────────────── │ separator
│ │
│ Accent Color │ section header
│ [●][●][●][●][●][●][●] │ color swatches
│ │
│ ────────────────────── │
│ ⚙️ System (auto) │ follows OS preference
└──────────────────────────┘
```
### Theme Item
| Property | Value |
|----------|-------|
| Height | 36px |
| Padding | 8px 12px |
| Layout | Icon + name + optional checkmark |
| Hover | var(--accent-bg) background, border-radius 4px |
| Active | Checkmark ✓ on right side, weight 500 |
| Click | Apply theme instantly, close picker |
| Preview | On hover, show a 4-color mini-swatch (bg, text, accent, secondary) |
### Color Swatch Preview (on hover)
| Property | Value |
|----------|-------|
| Size | 4 circles, 12px each, 4px gap |
| Colors | bg, bg-secondary, accent, text — of the hovered theme |
| Position | Inline after theme name |
---
## Accent Color Picker
**OBJECT ID:** `theme-accent-picker`
| Property | Value |
|----------|-------|
| Location | Inside theme picker dropdown, below themes |
| Presets | 7 color swatches in a row |
| Swatch size | 20px circles, 6px gap |
| Active swatch | 2px ring in var(--text-h) |
| Custom | Last swatch is rainbow gradient → opens native color picker |
| Behavior | Overrides --accent, --accent-bg, --accent-border for current theme |
| Persistence | Stored as `accentColor` in localStorage |
### Preset Accent Colors
| Name | Value | Hex |
|------|-------|-----|
| Indigo (default) | Indigo 500/400 | #6366f1 / #818cf8 |
| Teal | Teal 500/400 | #14b8a6 / #2dd4bf |
| Rose | Rose 500/400 | #f43f5e / #fb7185 |
| Amber | Amber 500/400 | #f59e0b / #fbbf24 |
| Emerald | Emerald 500/400 | #10b981 / #34d399 |
| Sky | Sky 500/400 | #0ea5e9 / #38bdf8 |
| Custom | User pick | Via `` |
Each preset has light/dark variants. The correct variant is selected based on current base theme.
---
## System Theme Option
**OBJECT ID:** `theme-system`
| Property | Value |
|----------|-------|
| Behavior | Follows OS `prefers-color-scheme` |
| Matches | Light ↔ Light theme, Dark ↔ Dark theme |
| Override | Selecting any specific theme disables system following |
| Re-enable | Select "System (auto)" in picker |
| Label | "⚙️ System (auto)" with current resolved theme name |
---
## Theme Application Flow
```
1. User clicks theme → store in localStorage
2. Apply CSS class to : `data-theme="matrix"`
3. CSS variables resolve from theme class
4. All components instantly update (CSS-only, no React re-render)
5. CodeMirror theme needs manual reconfiguration (dispatch theme compartment)
6. PWA theme-color meta tag updates for status bar color
```
### CodeMirror Theme Sync
| Step | Action |
|------|--------|
| 1 | Theme changes → dispatch new baseTheme to EditorView |
| 2 | Syntax highlighting colors update via CSS variables (no extension swap) |
| 3 | Stripe colors update via CSS |
| 4 | Active line highlight updates via CSS |
| 5 | Only the base theme extension needs reconfiguration |
---
## Responsive
| Breakpoint | Theme Picker |
|------------|-------------|
| >= 768px | Dropdown panel below header button |
| < 768px | Bottom sheet (slides up from bottom, 60vh max height) |
### Mobile Bottom Sheet
| Property | Value |
|----------|-------|
| Width | 100vw |
| Max height | 60vh |
| Border radius | 12px 12px 0 0 (top corners) |
| Drag handle | 32px × 4px pill, centered |
| Backdrop | 50% black overlay |
| Animation | Slide up 200ms ease-out |
| Close | Swipe down, tap backdrop |
---
## Page States
| State | When | Behavior |
|-------|------|----------|
| **First launch** | No theme preference | Follow OS (system). If OS is dark, use Dark. |
| **Theme selected** | User picked a theme | Applied instantly, persisted. System mode disabled. |
| **System mode** | User selected "System (auto)" | Listens to OS changes in real-time. |
| **Custom accent** | User picked accent color | Overrides accent tokens. Works with any base theme. |
| **Matrix active** | Matrix theme selected | Font override applied. Scanline effect enabled. Green cursor glow. |
---
## Technical Notes
- **No Flash of Unstyled Content (FOUC):** Load theme from localStorage in `