Theming Philosophy

Core Principles

Our theming system is built on three fundamental principles:

  1. Semantic Color Tokens - Colors are defined by their purpose, not their appearance
  2. Build-time vs Runtime - Understanding when design decisions are made vs. when themes are applied
  3. Component Cooperation - Components must actively participate in theming, not just receive it passively

Adaptive vs. Design Choice

A critical distinction in our theming system is between adaptive sections and design choices:

Adaptive Sections naturally adjust to feel appropriate in both themes:

  • Sections without explicit isDark: true configuration
  • Text and background colors automatically invert with theme
  • Screen overlays (light/dark) invert to maintain proper contrast

Design Choices maintain their intentional styling regardless of theme:

  • Sections with isDark: true are deliberate design decisions
  • These sections stay "dark" in both light and dark themes
  • They don't invert because they represent the designer's intent

Design Tokens System

Semantic Variables

The foundation of our theming system is semantic CSS variables defined in _design-tokens.css:

/* Light theme (default) */
:root {
  --color-text: #161616;
  --color-text-light: #f4f4f4;
  --color-background: #fff;
  --color-background-light: #f4f4f4;

  /* Contrast colors - automatically select appropriate contrast */
  --contrast-text-on-light: var(--color-text);
  --contrast-text-on-dark: var(--color-text-light);
}

/* Dark theme */
body.dark-theme {
  --color-text: #e5e5e5;
  --color-text-light: #161616;
  --color-background: #1a1a1a;
  --color-background-light: #2d2d2d;

  /* Contrast colors - inverted for dark theme */
  --contrast-text-on-light: var(--color-text-light);
  --contrast-text-on-dark: var(--color-text);
}

Fixed vs. Variable Colors

Some colors remain fixed regardless of theme:

/* Screen overlays - always use same values */
--light-screen: rgb(255 255 255 / 90%);
--dark-screen: rgb(0 0 0 / 60%);

/* Button text colors - hardcoded for consistency */
--primary-button-text-color: #f4f4f4;
--secondary-button-text-color: #f4f4f4;
--tertiary-button-text-color: #161616;

Smart Inversion Logic

Section Wrapper Behavior

The section wrapper in commons.css handles adaptive theming:

/* Default behavior - light theme */
.section-wrapper {
  &.has-dark-screen {
    color: var(--contrast-text-on-dark);
    .background-image::after {
      background: var(--dark-screen);
    }
  }

  &.has-light-screen {
    color: var(--contrast-text-on-light);
    .background-image::after {
      background: var(--light-screen);
    }
  }
}

/* Dark theme: invert ONLY if not a design choice */
.dark-theme {
  .section-wrapper {
    &.has-light-screen:not(.is-dark) {
      /* Invert: light screen becomes dark */
      color: var(--contrast-text-on-dark);
      .background-image::after {
        background: var(--dark-screen);
      }
    }
    /* Sections with .is-dark stay dark (don't invert) */
  }
}

The :not(.is-dark) selector is crucial - it prevents inverting sections that are intentionally dark as a design choice.

Component-Level Patterns

Using Semantic Variables

Components should use semantic variables instead of hardcoded colors:

/* Don't use hardcoded colors */
.component {
  color: #161616;
  background: #f8f8f8;
}

/* Use semantic variables */
.component {
  color: var(--color-text);
  background: var(--color-background-light);
}

Inheriting Parent Colors

For nested elements, inherit when appropriate:

.podcast-info h2 {
  color: inherit; /* Inherits from parent section */
}

.podcast-description {
  color: inherit;
  opacity: 0.9; /* Use opacity for hierarchy */
}

Locked Styling for Design Integrity

Some components need to maintain their styling regardless of theme:

/* Hero slider slides lock colors per-slide */
.dark-theme .hero-slide {
  &.is-dark {
    /* Always light text on dark slides */
    color: #f4f4f4;
  }

  &:not(.is-dark) {
    /* Always dark text on light slides */
    color: #161616;
  }
}

Special Cases & Solutions

Logo Visibility

Dark logos can disappear on dark backgrounds. Solution: subtle background in dark theme:

.logo {
  padding: var(--space-s);
  border-radius: 8px;
  transition: background-color 0.3s ease;
}

.dark-theme .logo {
  background-color: rgb(255 255 255 / 15%);
}

SVG Icons

SVG icons should use theme-aware stroke/fill colors:

svg {
  stroke: var(--color-text);
  fill: none;
}

svg * {
  stroke: var(--color-text);
  fill: none;
}

Third-Party Components

External components (like Shikwasa player) need explicit dark theme overrides:

/* Override Shikwasa when our dark theme is active */
.dark-theme .shk[data-theme='auto'] {
  --color-theme: #333;
  --color-text: #fff;
  --color-title: #fff;
}

Gradient Masks

Gradient overlays (like on the Logos List page) need to match the background color:

/* Light theme - white gradient */
.mask::before {
  background-image: linear-gradient(
    to right,
    rgb(255 255 255 / 100%) 0%,
    transparent 100%
  );
}

/* Dark theme - dark gradient */
.dark-theme .mask::before {
  background-image: linear-gradient(
    to right,
    rgb(26 26 26 / 100%) 0%,
    transparent 100%
  );
}

Implementation Guide

Step 1: Audit Components

Review each new component for:

  1. Hardcoded colors
  2. Context-specific requirements (always light/dark elements)
  3. Third-party dependencies with theme support

Step 2: Replace Hardcoded Colors

Convert hardcoded values to semantic variables:

Step 3: Add Dark Theme Overrides

Create dark theme rules for special cases:

.dark-theme .component {
  /* Only add overrides for special cases */
  /* Most styling should work via semantic variables */
}

Step 4: Test Thoroughly

  • Test all components in both themes
  • Check sections with isDark: true don't invert inappropriately
  • Verify logo/icon visibility in dark theme
  • Test third-party components (audio players, etc.)

Best Practices

DO

  • Use semantic CSS variables
  • Distinguish between adaptive sections and design choices
  • Use inherit and opacity for text hierarchy
  • Test logos and icons in dark theme
  • Evaluate build-time vs. runtime decisions
  • Document special cases in component CSS
  • Use theme-aware variables for borders, shadows, and backgrounds

DON'T

  • Hardcode colors in component CSS
  • Assume all sections should invert in dark theme
  • Use colors directly from design mockups
  • Ignore about third-party components styling
  • Mix build-time configuration with runtime theme switching
  • Create per-component color tokens (use global semantic tokens)
  • Add dark theme overrides when semantic variables would work

Known Limitations

Build-time vs. Runtime Trade-offs

Some configurations are made at build time (isDark: true, imageScreen: 'dark') while theme switching happens at runtime. This creates limitations:

  • A section with isDark: true and dark screen stays dark in both themes
  • This can look out of place in dark theme but maintains design intent
  • The alternative (runtime inversion) would break the original design

Logo Assets

Logos are often designed for specific backgrounds:

  • Dark logos may be hard to see in dark theme
  • Solutions include subtle backgrounds or CSS filters
  • Ideally, provide light/dark logo variants in the design system

Third-Party Components

External components may have limited theme support:

  • CSS variable overrides may be needed
  • Some components use inline styles that can't be themed
  • Test thoroughly and document workarounds

Conclusion

The Reality of Theming

A dark theme implementation inevitably touches component-level code. While the goal of keeping theming separate is admirable, practical considerations require component cooperation:

  • Components need semantic variables
  • Special cases (fixed colors, logos, third-party components) need attention
  • The distinction between adaptive and design choice sections requires understanding

Pragmatic Approach

The approach demonstrated in this library is pragmatic:

  • Core theme logic lives in design tokens and commons.css
  • Components cooperate by using semantic variables
  • Special cases are documented and handled explicitly
  • Build-time and runtime concerns are clearly separated

This balance between centralized theming and component-level awareness creates a maintainable, flexible theming system that respects both design intent and user preference.