Codeskill

Learn to code, step by step

Theming (light, dark, and brand variants)

Theming with SCSS: light and dark modes, optional brand skins, and CSS custom properties so the browser can switch without recompiling.

Start with semantic tokens

Theme tokens describe roles, not colours:

$theme-light: (
  'bg': #ffffff,
  'bg-subtle': #f5f5f5,
  'text': #1a1a1a,
  'text-muted': #5c5c5c,
  'border': #d9d9d9,
  'brand': #2563eb,
);

$theme-dark: (
  'bg': #121212,
  'bg-subtle': #1e1e1e,
  'text': #f5f5f5,
  'text-muted': #a3a3a3,
  'border': #333333,
  'brand': #60a5fa,
);

Emit CSS variables per theme

@mixin emit-theme($name, $tokens) {
  @each $key, $value in $tokens {
    --color-#{$key}: #{$value};
  }
}

:root {
  @include emit-theme('light', $theme-light);
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root {
    @include emit-theme('dark', $theme-dark);
    color-scheme: dark;
  }
}

Components consume variables, not SCSS colour variables directly:

.card {
  background: var(--color-bg);
  color: var(--color-text);
  border: 1px solid var(--color-border);
}

.button--primary {
  background: var(--color-brand);
  color: var(--color-bg);
}

Manual theme override

Let users pick a theme with a data attribute:

[data-theme='light'] {
  @include emit-theme('light', $theme-light);
  color-scheme: light;
}

[data-theme='dark'] {
  @include emit-theme('dark', $theme-dark);
  color-scheme: dark;
}

A small JavaScript toggle sets document.documentElement.dataset.theme. SCSS already compiled the rules; the browser swaps variable values.

Brand variants

Second brand? Add another map and scope it:

$theme-brand-b: (
  'brand': #059669,
  'bg-subtle': #ecfdf5,
);

.theme-brand-b {
  @include emit-theme('brand-b', $theme-brand-b);
}

Wrap a section or the whole page in .theme-brand-b for a microsite or white-label preview.

What SCSS cannot do alone

Runtime theme switching needs custom properties in the output CSS. SCSS variables alone compile away – fine for build-time brand builds, not for a user toggle. Combine both: SCSS builds the variable blocks; the browser applies them.

Try it

  • Define light and dark maps and emit variables on :root.
  • Style a card and button with var(--color-*) only.
  • Add [data-theme='dark'] override and test with DevTools.
PreviousResponsive mixins that stay readable