FrontendAtlas
Interview Questions
Home>CSS interview questions> CSS Coding Challenges >Theming with CSS Variables

Theming with CSS Variables: OS Dark Mode + Manual Override

intermediate
By FrontendAtlas Editorial · Updated Jul 14, 2026
Create a themeable panel using CSS custom properties. Define light defaults on :root, redefine the same tokens for OS dark mode via @media (prefers-color-scheme: dark), then add a manual :root:where(.theme-dark) override after the media query. :where() adds no specificity, so the two root selectors tie and later source order decides the winner. The panel and button should consume theme tokens only for colors and shadow values.

What you'll practice:

  • CSS custom properties as live theme tokens
  • Semantic token naming for background, text, surface, border, accent, contrast, and shadow
  • prefers-color-scheme for OS-level dark mode
  • Cascade/source order for predictable theme overrides
  • Manual root-level theme overrides with :root:where(.theme-dark)

Requirements

  • Define light theme tokens on :root: --bg, --text, --surface, --accent, --border, --accent-contrast, and --panel-shadow.
  • Define dark values for the same tokens inside @media (prefers-color-scheme: dark).
  • Define :root:where(.theme-dark) after the media query and repeat the dark token values there.
  • Use token references inside .panel and .btn; do not place literal color values in those component declarations.
  • Use --accent-contrast for button text so the accent background has an explicit readable foreground token.

Acceptance criteria

  • No hard-coded component colors or colored shadows remain inside .panel or .btn.
  • Dark OS preference switches the page, panel, border, accent, contrast, and panel shadow through variables.
  • Adding class="theme-dark" to html forces the dark token set even when the OS preference is light.
  • Button text contrast is handled through --accent-contrast rather than a literal color.

Expected behavior

Your CSS theme should:

  • Render a light theme by default from :root tokens.
  • Switch to dark values when the system prefers a dark color scheme.
  • Let :root:where(.theme-dark) win because it has the same specificity as :root and is declared later.
  • Keep component styles stable while only token values change between themes.

Implementation notes

  • Keep the same variable names across :root, @media (prefers-color-scheme: dark), and :root:where(.theme-dark).
  • Put :root:where(.theme-dark) after the media query. :where(.theme-dark) contributes zero specificity, so both root selectors have equal specificity and source order breaks the tie.
  • Treat --panel-shadow as part of the theme token set because shadows include color.
  • Use rgb(... / alpha) or another color syntax inside token definitions, not inside constrained component declarations.

Common mistakes

  • Placing :root:where(.theme-dark) before the media query, which lets the later equal-specificity OS rule win.
  • Changing body colors while .panel or .btn keep hard-coded backgrounds, borders, text colors, or shadows.
  • Forgetting --accent-contrast and making button text unreadable in one theme.
  • Using a higher-specificity selector and then incorrectly attributing the result to source order.

Interview explanation

This pattern matters because real UI systems need components that consume semantic tokens instead of owning theme colors directly. CSS custom properties let a design system swap values at runtime without recompiling styles or rewriting every component. prefers-color-scheme respects the user's OS preference, while a root class gives product UI a manual override. The interview signal is understanding cascade order, token completeness, and contrast instead of only toggling a body background.

Testing checklist

  • Test with a light OS preference and confirm :root values are used.
  • Test with a dark OS preference and confirm @media (prefers-color-scheme: dark) values are used.
  • Add the theme-dark class manually and confirm the later :root:where(.theme-dark) rule wins over the OS preference.
  • Inspect computed styles for .panel and .btn to verify variables resolve to the final colors and shadow.

Solution

Overview

A high-signal CSS theming setup is tokens on :root, an OS dark override via prefers-color-scheme, then an equal-specificity :root:where(.theme-dark) rule declared later. Because :where() contributes zero specificity, source order decides between the two root selectors. Components consume tokens so theme swaps stay instant and consistent.

1

Approach — Tokens on :root + Dark Media + Equal-Specificity Manual Override

Define a minimal token set (bg/text/surface/accent/border/accent-contrast/panel-shadow). Redefine the same tokens in the dark media query for OS-level theming. Then repeat the dark token values in :root:where(.theme-dark) after the media query. Because :where() adds zero specificity, it ties :root and the later source order makes the manual override win. The sample HTML includes class="theme-dark" to demonstrate the override; remove it to test the OS-only branch.

<html>
<!doctype html>
  <html class="theme-dark" lang="en">
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>
        Theme Demo
      </title>
    </head>
    <body>
      <div class="panel">
        <h2>
          Theme Demo
        </h2>
        <p>
          This panel and button read colors from CSS variables.
        </p>
        <button class="btn" type="button" aria-label="Static themed button sample">
          Tokenized button
        </button>
      </div>
    </body>
  </html>
# CSS
:root {
  --bg: #fafafa;
  --text: #111111;
  --surface: #ffffff;
  --accent: #3b82f6;
  --border: rgba(0, 0, 0, 0.12);
  --accent-contrast: #ffffff;
  --panel-shadow: 0 1px 2px rgb(0 0 0 / 0.06);
}
@media (prefers-color-scheme: dark) {
  :root {
  --bg: #0b0b0c;
  --text: #f3f4f6;
  --surface: #16181a;
  --accent: #60a5fa;
  --border: rgba(255, 255, 255, 0.14);
  --accent-contrast: #0b0b0c;
  --panel-shadow: 0 1px 2px rgb(0 0 0 / 0.4);
}
}
/* Equal-specificity manual override declared after the media query */
:root:where(.theme-dark) {
  --bg: #0b0b0c;
  --text: #f3f4f6;
  --surface: #16181a;
  --accent: #60a5fa;
  --border: rgba(255, 255, 255, 0.14);
  --accent-contrast: #0b0b0c;
  --panel-shadow: 0 1px 2px rgb(0 0 0 / 0.4);
}
html, body {
  height: 100%;
}
body {
  font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
  margin: 0;
  line-height: 1.6;
  background: var(--bg);
  color: var(--text);
  display: grid;
  place-items: center;
  padding: 2rem;
}
.panel {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1.25rem;
  box-shadow: var(--panel-shadow);
  max-width: 520px;
}
.btn {
  background: var(--accent);
  color: var(--accent-contrast);
  border: 0;
  padding: .5rem .9rem;
  border-radius: .6rem;
  font-weight: 600;
}

Notes & Pitfalls

Pitfalls
  • Placing :root:where(.theme-dark) before the media query makes the later equal-specificity OS rule win.
  • Hard-coding a component color (e.g., button text) breaks themeability—add a token like --accent-contrast instead.
  • Leaving a literal rgba() shadow inside .panel contradicts the component-token constraint; use --panel-shadow.
Techniques
  • Keep the token set small but complete (include contrast and shadow tokens).
  • Use the exact same variable names across all theme layers (:root, media query, manual override).
  • Use :where(.theme-dark) to keep specificity tied with :root, then let later source order decide.

Resources

  • MDN — Using CSS custom properties (variables)
  • MDN — prefers-color-scheme
  • MDN — Specificity

Related links

Practice CSS custom properties with dark modeConceptReview prefers-color-scheme and media queriesConceptBuild a responsive CSS navbarCSSBrowse CSS interview questionsHubFlexbox: Responsive Navbarintermediate

Guides

Frontend interview preparation guideGuideFrontend coding interview questions and prep guideBlueprintJavaScript Problems That Actually Show UpBlueprintBuild Great UI in 60 MinutesBlueprint

Preparing for interviews? Use Frontend Coding Challenges first, then move into a concrete Study Plan before targeted Company Prep.

Open frontend interview questionsBrowse CSS interview questionsOpen Essential 60Open Machine Coding HubOpen Frontend Coding ChallengesOpen System DesignOpen Interview Prep GuideOpen System Design BlueprintOpen Framework Prep PathsOpen Study PlansOpen 7-day crash study planOpen Company Prep
↗Incidents hub
← Prev←5 / 10Next →→