Lab 12: CSS Variables & Theming

Objective

Master CSS custom properties (variables) to build maintainable stylesheets, implement dark/light theme switching, and create a design token system.

Background

CSS custom properties are variables native to CSS β€” no preprocessor needed. They're dynamic (changeable with JavaScript at runtime), inheritable (flow through the DOM), and scope-aware. They're the foundation of modern design systems and theme switching.

Time

30 minutes

Prerequisites

  • Lab 09: Responsive Design

Tools

docker run --rm -it -v /tmp:/workspace zchencow/innozverse-htmlcss:latest bash

Lab Instructions

Step 1: CSS Variables Basics

Write this file:

πŸ’‘ CSS variables syntax: Define with --variable-name: value; inside a selector. Use with var(--variable-name) or var(--variable-name, fallback). :root is the document root β€” variables defined there are globally accessible. They're case-sensitive (--Color β‰  --color).

πŸ“Έ Verified Output:


Step 2: Variable Scope

Write this file:

πŸ’‘ Variable scope is DOM-based. A variable defined on .card.danger is only available within .card.danger and its descendants. Children inherit parent variables. This means you can override a single variable at the component level and all nested elements using it update.

πŸ“Έ Verified Output:


Step 3: Dark/Light Theme Switching

Write this file:

πŸ’‘ Theme switching technique: Define all colors as CSS variables, then redefine the variables under a [data-theme="dark"] attribute selector. Toggle document.documentElement.dataset.theme with JavaScript. The CSS transition creates a smooth animated switch.

πŸ“Έ Verified Output:


Step 4: Component Theming

Write this file:

πŸ’‘ Component-scoped variables β€” define the base component using var(--name, default). Override the variables on the variant class (.btn-danger, .alert-warning). The component's CSS never changes β€” only the variable values change. This is exactly how design systems like MUI and Chakra UI work.

πŸ“Έ Verified Output:


Step 5: JavaScript + CSS Variables

Write this file:

πŸ’‘ element.style.setProperty('--var', value) updates a CSS variable from JavaScript. The change propagates instantly to every element using that variable β€” no need to update individual DOM elements. This makes CSS variables the bridge between dynamic JavaScript state and static CSS.

πŸ“Έ Verified Output:


Step 6: Design Token System

Write this file:

πŸ’‘ Design token hierarchy: Raw primitives (--color-blue-500) β†’ Semantic meaning (--color-primary) β†’ Component use. This separation means rebrand = change primitive, dark mode = change semantic tokens, component = untouched. This is how Figma, Tailwind, and MUI handle theming.

πŸ“Έ Verified Output:


Step 7: Responsive Spacing Scale with Variables

Write this file:

πŸ’‘ Cascading calc() with variables β€” define a base unit and derive all other spacing with calc(). At a breakpoint, change only the base and every derived spacing updates. This creates proportionally consistent scaling across screen sizes.

πŸ“Έ Verified Output:


Step 8: Capstone β€” Themeable UI Component Library

Write this file:

πŸ’‘ Capstone recap: This is a complete mini design system β€” tokens β†’ components β†’ themes. All components use variables exclusively. Dark mode works with a single attribute. Every spacing, color, and radius value has a name and semantic meaning. This is production-grade CSS architecture.

πŸ“Έ Verified Output:


Verification

Summary

Concept
Syntax
Use Case

Define variable

--name: value

In any selector

Use variable

var(--name)

Any CSS value

With fallback

var(--name, fallback)

Safe usage

Global scope

:root { --name: value }

Design tokens

Component scope

.card { --color: red }

Variant theming

JS update

el.style.setProperty('--name', val)

Dynamic theming

Dark mode

[data-theme="dark"] { --bg: dark }

Theme switching

Further Reading

Last updated