HTML/CSS - 25

HTML/CSS - 25: Dark Mode & Theming

A theme is a collection of design choices that work together, such as background colors, text colors, borders, and surfaces.

CSS custom properties make themes easier to manage because you can store those design values in one place and reuse them throughout the page.

You can also use the visitor's system preference with prefers-color-scheme, or let them switch themes manually with a button.

Your challenge: Create a page with light and dark themes, detect the visitor's preferred color scheme, and add a button that switches between the two.

✦ HTML Editor
▶ Live Output

How does theming work?

The page starts by storing its main design values inside :root.

For example: --page-bg controls the page background, while --text controls the main text color.

The rest of the stylesheet uses those values with var().

This means the page does not need completely separate CSS rules for every theme. Instead, the theme can change the variables themselves.

System preference:

The media query: @media (prefers-color-scheme: dark) can apply different values when the visitor's device or browser is set to prefer a dark appearance.

This gives the page a sensible theme automatically before the visitor presses anything.

Now create the manual dark theme:

Add this selector: :root[data-theme="dark"]

Inside it, give the variables their dark values. For example: --page-bg: #000; and --surface: #111;.

The JavaScript toggle sets a data-theme attribute on the root <html> element.

When it becomes: data-theme="dark", the matching CSS selector can replace the normal theme variables.

But what about switching back to light?

For a complete manual toggle, you can also add: :root[data-theme="light"] with the original light-theme values.

That manual choice can override the system preference while the page is open.

Experiment: Change only --surface in the dark theme.

Any cards or buttons using var(--surface) should update together. This shows why CSS variables are useful for theming.

Try changing more than color:

A theme can also change values such as border colors, shadows, or other visual details. However, keeping the overall layout consistent usually makes switching themes feel smoother.

Why use a real button?

The theme switch is an action, so <button> is the appropriate HTML element. It already supports keyboard interaction without needing to recreate button behavior manually.

Accessibility tip: Light and dark themes should both keep enough contrast between text and backgrounds. A dark theme is not automatically accessible just because the background is black.

Also avoid making the theme change depend only on color if the interface needs to communicate important state.

Good theming habit: Keep your theme values together and let components use variables. This makes it much easier to adjust an entire visual system without rewriting every individual CSS rule.

👀 Show Solution
<style>

  :root {
    --page-bg: #f4f4f4;
    --surface: #ffffff;
    --text: #222222;
    --border: #666666;
  }

  :root[data-theme="light"] {
    --page-bg: #f4f4f4;
    --surface: #ffffff;
    --text: #222222;
    --border: #666666;
  }

  :root[data-theme="dark"] {
    --page-bg: #000000;
    --surface: #111111;
    --text: #dddddd;
    --border: #8A8A8A;
  }

  @media (prefers-color-scheme: dark) {

    :root {
      --page-bg: #000000;
      --surface: #111111;
      --text: #dddddd;
      --border: #8A8A8A;
    }

  }

  body {
    background-color: var(--page-bg);
    color: var(--text);

    transition:
      background-color 0.3s ease,
      color 0.3s ease;
  }

  .card {
    background-color: var(--surface);
    border: 2px solid var(--border);
    padding: 25px;
    border-radius: 10px;
  }

  button {
    background-color: var(--surface);
    color: var(--text);
    border: 2px solid var(--border);
    padding: 10px 18px;
  }

</style>

<main class="card">

  <h1>Theme Workshop</h1>

  <p>
    This page can switch between two visual themes.
  </p>

  <button id="theme-toggle" type="button">
    Switch Theme
  </button>

</main>

<script>

  const button =
    document.getElementById('theme-toggle');

  button.addEventListener('click', () => {

    const currentTheme =
      document.documentElement.getAttribute('data-theme');

    if (currentTheme === 'dark') {

      document.documentElement.setAttribute(
        'data-theme',
        'light'
      );

    } else {

      document.documentElement.setAttribute(
        'data-theme',
        'dark'
      );

    }

  });

</script>

The page uses variables for its main visual values. The system preference supplies an automatic default, while the button allows the visitor to choose a theme manually.

Because the card, button, text, and background all depend on shared variables, changing the theme only requires changing those variable values rather than rewriting the individual component styles.