Theme System

Complete guide to the Green Goods CSS variables-based theming system.

Overview

The Green Goods admin dashboard uses a CSS variables-based theme system that provides:

  • Instant theme switching with no visual flash

  • Semantic color tokens that automatically adapt to light/dark modes

  • Single source of truth for all color definitions

  • Type-safe theme API with React hooks

Architecture

Core Components

  1. Theme CSS (packages/shared/src/styles/theme.css)

    • Defines all CSS variables

    • Maps semantic tokens to base colors

    • Handles light/dark mode switching

  2. Theme Controller (packages/shared/src/theme/index.ts)

    • Pure JavaScript API for theme management

    • Handles localStorage persistence

    • Manages system preference detection

  3. React Hook (packages/shared/src/hooks/app/useTheme.ts)

    • React integration for theme state

    • Provides reactive theme updates

    • Cleanup on unmount

  4. Tailwind Integration (packages/admin/tailwind.config.ts)

    • Maps CSS variables to Tailwind utilities

    • Enables bg-*, text-*, border-* classes

    • Supports opacity modifiers

Theme System

Data Attribute Approach

The system uses data-theme attributes instead of class-based dark mode:

Why data-theme:

  • More semantic than .dark class

  • Explicit theme state in markup

  • Easy to extend (high-contrast, etc.)

  • Clean selector syntax in CSS

Pre-Hydration Script

Prevents flash of wrong theme on page load:

Color Token System

Semantic Background Tokens

Token
Light Mode
Dark Mode
Usage

bg-white-0

White (#FFFFFF)

Black (#000000)

Primary backgrounds

bg-weak-50

Gray 50

Gray 900

Subtle backgrounds

bg-soft-200

Gray 200

Gray 700

Soft backgrounds

bg-sub-300

Gray 300

Gray 600

Medium backgrounds

bg-surface-800

Gray 100

Gray 800

Surface backgrounds

bg-strong-950

Black (#000000)

White (#FFFFFF)

Strong backgrounds

Semantic Text Tokens

Token
Light Mode
Dark Mode
Usage

text-strong-950

Gray 950

Gray 50

Primary text

text-sub-600

Gray 600

Gray 400

Secondary text

text-soft-400

Gray 400

Gray 500

Tertiary text

text-disabled-300

Gray 300

Gray 600

Disabled text

Semantic Stroke Tokens

Token
Light Mode
Dark Mode
Usage

stroke-strong-950

Gray 950

Gray 50

Strong borders

stroke-sub-300

Gray 300

Gray 600

Medium borders

stroke-soft-200

Gray 200

Gray 700

Soft borders

State Color Tokens

Each state has 4 variants:

State
Dark
Base
Light
Lighter

Success (Green)

success-dark

success-base

success-light

success-lighter

Error (Red)

error-dark

error-base

error-light

error-lighter

Warning (Orange)

warning-dark

warning-base

warning-light

warning-lighter

Information (Blue)

information-dark

information-base

information-light

information-lighter

Usage guidelines:

  • -darker: Text on light backgrounds

  • -base: Icons, borders, primary state color

  • -light: Borders, accents

  • -lighter: Backgrounds

Usage

JavaScript/TypeScript

React Hook

Tailwind CSS

Raw CSS

Common Patterns

Card Component

Button Variants

Form Input

Status Badge

Alert Banners

Migration Guide

Migrating Existing Components

Before:

After:

Common Replacements

Old (Hardcoded)
New (Semantic Token)

bg-white dark:bg-gray-800

bg-bg-white

bg-gray-50 dark:bg-gray-900

bg-bg-weak

bg-gray-100

bg-bg-weak

text-gray-900 dark:text-gray-100

text-text-strong

text-gray-600 dark:text-gray-400

text-text-sub

text-gray-500

text-text-soft

border-gray-200 dark:border-gray-700

border-stroke-soft

border-gray-300

border-stroke-sub

Brand Colors vs Semantic Tokens

Keep as brand colors:

  • Primary CTAs: bg-green-600 hover:bg-green-700

  • Active tab indicators: border-green-500 text-green-600

  • Links: text-green-600 hover:text-green-900

Convert to semantic tokens:

  • Status badges: bg-success-lighter text-success-dark

  • Error states: bg-error-lighter text-error-dark

  • Form validation: border-error-base focus:ring-error-lighter

  • Remove buttons: text-error-base hover:bg-error-lighter

Testing Checklist

When adding new components:

Troubleshooting

Colors Not Changing

Problem: Classes don't change colors between themes.

Solution: Ensure CSS variables are defined in theme.css and mapped in tailwind.config.ts.

Theme Flash on Load

Problem: Wrong theme briefly shows on page load.

Solution: Verify pre-hydration script is in index.html before React loads.

Opacity Modifiers Not Working

Problem: bg-text-strong/50 doesn't work.

Solution: Ensure colors use rgb(var(--color) / <alpha-value>) format in Tailwind config.

State Colors Not Available

Problem: bg-success-lighter class not found.

Solution: Add state color tokens to tailwind.config.ts:

Best Practices

  1. Always use semantic tokens for neutral colors (gray scale)

  2. Use state tokens for status indicators and alerts

  3. Keep brand colors for primary actions and navigation

  4. Test both themes during development

  5. Avoid hardcoded colors in component styles

  6. Use opacity modifiers for subtle variations

  7. Document custom patterns in this guide

  8. Maintain accessibility contrast ratios in both themes

References

  • Shared theme CSS: packages/shared/src/styles/theme.css

  • Theme controller: packages/shared/src/theme/index.ts

  • React hook: packages/shared/src/hooks/app/useTheme.ts

  • Admin Tailwind config: packages/admin/tailwind.config.ts

  • Implementation doc: /CSS_VARIABLES_THEME_IMPLEMENTATION.md

Last updated