# Theme System Guide **Project**: WorkRoot IT Solutions — Company Site **Date**: 2026-03-21 **Author**: documentation-writer agent **Status**: Production-ready --- ## Table of Contents 1. [Overview](#1-overview) 2. [How Theme Selection Works (User Guide)](#2-how-theme-selection-works-user-guide) 3. [Architecture & Implementation](#3-architecture--implementation) 4. [Design Tokens Reference](#4-design-tokens-reference) 5. [Developer Guide: Adding Theme Support to New Components](#5-developer-guide-adding-theme-support-to-new-components) 6. [Maintaining Theme Consistency](#6-maintaining-theme-consistency) 7. [Troubleshooting Guide](#7-troubleshooting-guide) 8. [Accessibility & WCAG Compliance](#8-accessibility--wcag-compliance) 9. [Performance Notes](#9-performance-notes) --- ## 1. Overview The site supports **dark and light themes** with: - Zero flash-of-unstyled-content (FOUC) — theme applies before first paint - Automatic detection of the OS/browser dark mode preference - Manual override persisted in `localStorage` - Automatic sync when the OS preference changes (if no manual override) - Full WCAG 2.1 AA/AAA contrast compliance in both themes - Screen reader announcements on theme change ### Files Involved | File | Role | |------|------| | `src/styles/design-tokens.css` | Single source of truth for all color tokens | | `src/components/ThemeToggle.astro` | Toggle button component (sun/moon icons) | | `src/layouts/BaseLayout.astro` | Inline blocking script that prevents FOUC | | `src/styles/global.css` | Component classes that consume tokens | | `tailwind.config.mjs` | `dark:` variant configuration | --- ## 2. How Theme Selection Works (User Guide) ### Automatic Detection When you first visit the site, the theme is set automatically based on your operating system or browser preference: - **macOS**: System Preferences → Appearance → Dark/Light - **Windows**: Settings → Personalization → Colors → Choose your color - **Android/iOS**: Display settings → Dark mode No action needed — the site matches your system setting out of the box. ### Manual Toggle A **sun/moon icon button** in the navigation header lets you manually switch themes at any time: - **Moon icon** (shown in light mode) → click to enable dark mode - **Sun icon** (shown in dark mode) → click to return to light mode Your choice is **saved in your browser** (via `localStorage`) and will persist across page navigations and browser restarts on the same device. ### Priority Order ``` 1. Manual user choice (localStorage) ← highest priority 2. OS/browser system preference 3. Light mode fallback ← default ``` ### Resetting to System Default To go back to automatic system-based theming, clear your browser's site data for this domain: - **Chrome**: DevTools → Application → Storage → Clear site data - **Firefox**: DevTools → Storage → Local Storage → delete the `theme` key - **Safari**: Develop → Website Data → remove site entry --- ## 3. Architecture & Implementation ### FOUC Prevention (Blocking Inline Script) The most critical piece — lives in `BaseLayout.astro` inside `
`, runs synchronously before any CSS or HTML renders: ```html ``` **Why inline?** An external script would require a round-trip to the server, causing a visible flash. The inline IIFE runs synchronously as the browser parses the HTML. ### CSS Custom Properties (Semantic Tokens) Theme switching works entirely via CSS custom properties defined in `src/styles/design-tokens.css`. No JavaScript re-renders required. ```css /* Light mode (default) */ :root { --color-surface: #f8fafc; --color-text-primary: #1e293b; --color-primary: #0891b2; /* cyan */ } /* Dark mode override — applied when html.dark exists */ html.dark { --color-surface: #0f172a; --color-text-primary: #f1f5f9; --color-primary: #22d3ee; /* brighter cyan for dark bg */ } ``` Components use semantic tokens, never raw color values: ```css /* ✅ Correct — adapts to theme automatically */ .card { background: var(--color-surface-raised); } /* ❌ Wrong — hardcoded, breaks in dark mode */ .card { background: #ffffff; } ``` ### Tailwind Dark Variant Tailwind classes with the `dark:` prefix apply when `html.dark` is present: ```htmlContent