MGU Computer Science Series — Web Development: HTML, CSS, and JavaScript
Part III: CSS — Style and Layout
Custom Properties · Design Tokens · Themes · Maintainability
Chapter 18 of 30
18
MGU Computer Science Series · Web Development: HTML, CSS, and JavaScript · Library Textbook Edition
Chapter 18:
CSS Variables, Themes,
and Reusable Styles
Turning a stylesheet into a reusable design system — custom properties, color and spacing tokens, typography scales, reusable component classes, utility classes, and theme switching
■ Part III of VII ■
■ 10 Sections • Chapter 18 of 30 ■
Builds on Chapters 11–17 — CSS rules, the cascade, the box model, Flexbox, Grid, and responsive design
Chapter 18 — Front Matter
Chapter Introduction and Learning Objectives
When students first begin writing CSS, they often work one rule at a time. They style a heading, then a paragraph, then a button, adding new rules whenever something needs to change. This approach works for a single small page, but it creates serious problems as a project grows. A brand color repeated across navigation links, button backgrounds, and section borders has to be tracked down and changed everywhere if the design ever shifts. Spacing drifts. Font sizes multiply. The stylesheet becomes a record of decisions rather than a system for making them.
This chapter teaches a better approach. CSS custom properties — commonly called CSS variables — let a developer name a value once and reuse it everywhere. Reusable classes capture recurring visual patterns so they do not need to be rebuilt from scratch for every card, button, or section. Utility classes handle small one-off adjustments cleanly. And once a stylesheet is built entirely from named values, switching between a light theme and a dark theme becomes a matter of overriding a handful of variables rather than rewriting the page.
Opening Question
Imagine a website with forty pages, each containing buttons, cards, and headings in the same brand blue. The design team decides the blue is too dark and asks for a lighter shade everywhere. If that blue was typed directly into dozens of separate CSS rules, how long would the fix take, and how easy would it be to miss a spot? Now imagine that same blue was defined once, in one place, with a name like --color-primary. How does the fix change? This chapter is about building stylesheets the second way.
10Sections
12Objectives
14Key Terms
30Total Chapters
Learning Objectives
By the end of this chapter, you should be able to:
- 1Explain why a CSS design system improves consistency, maintainability, readability, and scalability.
- 2Define a custom property and explain the syntax for declaring one and retrieving its value with
var().
- 3Explain the role of the
:root selector and why site-wide variables are declared there.
- 4Build a role-based color variable system distinguishing primary, neutral, and feedback colors.
- 5Build a consistent spacing scale and apply it to padding, margin, and gap.
- 6Build a typography variable system covering font families, sizes, line heights, and weights.
- 7Write reusable component classes such as
.card and base-plus-modifier button classes.
- 8Distinguish reusable classes from utility classes and explain when each should be used.
- 9Explain how
prefers-color-scheme and a toggled class can each drive theme switching.
- 10Organize a stylesheet into a clear, numbered, maintainable structure.
- 11Identify and correct common custom property mistakes.
- 12Apply variables, reusable classes, and theme preparation to the Student Learning Site running project.
Chapter 18 — Contents
Sections at a Glance
- 18.1CSS as a Design SystemWhy scattered CSS becomes unmaintainable; consistency, maintainability, readability, and scalability as design-system goals
- 18.2Custom PropertiesDeclaring variables with double hyphens; retrieving values with var(); case sensitivity; what values a custom property can hold
- 18.3The :root SelectorWhy :root is the standard home for site-wide tokens; variable scope and inheritance; fallback values
- 18.4Color VariablesPrimary, neutral, and feedback color categories; role-based naming versus appearance-based naming
- 18.5Spacing VariablesBuilding a spacing scale; applying it to padding, margin, and gap; why consistent spacing creates visual rhythm
- 18.6Typography VariablesFont family, size, line-height, and weight tokens; modular type scales and visual hierarchy
- 18.7Reusable ClassesComponent patterns such as .card, .container, .section; base-plus-modifier button classes
- 18.8Utility ClassesSmall single-purpose classes; the .sr-only accessibility utility; using utilities sparingly
- 18.9Theme PreparationToggled dark-theme classes; prefers-color-scheme; theming beyond dark mode
- 18.10Maintaining a StylesheetA numbered stylesheet structure; maintenance habits; naming by purpose rather than appearance
Sections 18.1 – 18.3
Foundations: From Scattered Rules to a Variable System
Design Systems · Custom Properties · :root
Section 18.1
CSS as a Design System
When students first begin writing CSS, they often work one rule at a time. They style a heading, then a paragraph, then a button, adding new rules whenever something needs to change. This approach works for a single small page, but it creates serious problems as a project grows.
Consider a website that uses a particular shade of blue throughout: navigation links, button backgrounds, section borders, and call-to-action text might all share that same blue. If that blue value is written separately in each rule, changing the brand color means searching through every file and updating every occurrence. Miss one, and the page looks inconsistent.
Now consider a different approach: what if that blue were defined once, in one place, with a clear name? Every rule that needed blue would simply reference that name. Changing the color in one place would update the entire site instantly. This is what CSS variables make possible. They allow developers to treat a stylesheet not as a list of one-off rules, but as a design system: a set of named, consistent values that govern the look and feel of the entire site.
A design system does not need to be complex. Even a small project benefits from organizing decisions into reusable named values. Color, spacing, and typography are the three areas where consistent naming pays off most quickly.
Goal 1
Consistency
The same values are used throughout the site, not similar-but-different values scattered everywhere.
Goal 2
Maintainability
Changing a value in one place updates the entire site, instead of requiring a search through every file.
Goal 3
Readability
Named values communicate meaning. --color-primary is easier to understand than #2c5f8a.
Goal 4
Scalability
Adding new pages or components requires only referencing existing values, not inventing new ones.
Study Note
Chapter 18 introduces three tools that support a design-system approach to CSS: custom properties (variables), reusable class systems, and structured stylesheet organization. None of these tools require a framework or build process — they work in plain CSS, exactly as you have been writing it since Chapter 11.
Section 18.2
Custom Properties
CSS custom properties, also called CSS variables, are values you define once and reference throughout a stylesheet. They follow a specific syntax.
Declaring a Custom Property
Custom property names must start with two hyphens (--). The name can be anything meaningful. The value is any valid CSS value.
Using a Custom Property
color: var(--property-name);
The var() function retrieves the value of a custom property. Here is a complete small example:
:root {
--color-primary: #2c5f8a;
--color-text: #1a1a1a;
--font-size-base: 1rem;
}
h1 {
color: var(--color-primary);
font-size: calc(var(--font-size-base) * 2);
}
p {
color: var(--color-text);
font-size: var(--font-size-base);
}
a {
color: var(--color-primary);
}
In this example, --color-primary appears in two rules. If the design ever calls for a different primary color, the developer changes only the declaration in :root, and both the h1 and every anchor element update automatically.
Two Things to Remember
Custom properties are case-sensitive. --Color-Primary and --color-primary are two different properties. By convention, most developers use lowercase letters with hyphens.
Custom properties can hold any CSS value, including colors, lengths, font names, and even partial values used with calc().
Section 18.3
The :root Selector
Custom properties can be declared on any element. When declared on an element, the variable is available to that element and all its descendants. When declared on :root, the variable is available everywhere in the document.
:root is a pseudo-class selector that matches the root element of the document. In HTML, this is always the <html> element. Using :root is therefore slightly more specific than html, but for practical purposes they target the same element.
Because :root is the topmost element, variables declared there cascade down to every other element in the page. This makes it the standard location for site-wide design tokens.
:root {
--color-primary: #2c5f8a;
--color-secondary: #4a9e6f;
--color-accent: #e8a020;
--color-text: #1a1a1a;
--color-background: #ffffff;
}
By declaring all primary variables in :root, a developer creates a single section of the stylesheet that serves as a reference for the entire site's visual language.
Fallback Values
The var() function accepts an optional second argument: a fallback value to use if the custom property is not defined.
color: var(--color-primary, #2c5f8a);
Fallbacks are useful in larger projects where some variables might not be defined in every context. For most beginner projects, fallbacks are optional but good to know.
Where to Put Your :root Block
Place the :root block at the very top of your main CSS file, before any other rules. This makes it easy to find and read at a glance, and it clearly signals to any reader that these are the site-wide design values.
Sections 18.4 – 18.6
Design Tokens: Color, Spacing, and Typography
Role-Based Naming · Spacing Scales · Type Scales
Section 18.4
Color Variables
Color is the most immediate candidate for a variable system because colors repeat constantly across a website. Navigation, headings, buttons, borders, backgrounds, links, and highlights may all draw from the same palette.
A thoughtful color variable system starts with clear categories:
- Primary colors — the main brand colors used for headings, links, and call-to-action elements.
- Neutral colors — text colors, background colors, border colors, and subtle backgrounds that appear everywhere.
- Feedback colors — colors used for success messages, error messages, warning notices, and information callouts.
Here is a realistic beginner color system:
:root {
/* Primary palette */
--color-primary: #2c5f8a;
--color-primary-dark: #1e4266;
--color-primary-light: #5a8fbf;
/* Secondary / accent */
--color-secondary: #4a9e6f;
--color-accent: #e8a020;
/* Neutral */
--color-text: #1a1a1a;
--color-text-muted: #5a5a5a;
--color-background: #ffffff;
--color-background-soft: #f4f6f8;
--color-border: #d0d5da;
/* Feedback */
--color-success: #2e7d32;
--color-warning: #e65100;
--color-error: #c62828;
--color-info: #01579b;
}
Once these variables are defined, every rule in the stylesheet can reference them by name:
header {
background-color: var(--color-primary);
}
nav a {
color: var(--color-background);
}
nav a:hover {
color: var(--color-accent);
}
footer {
background-color: var(--color-background-soft);
border-top: 1px solid var(--color-border);
color: var(--color-text-muted);
}
.card {
background-color: var(--color-background);
border: 1px solid var(--color-border);
}
.error-message {
color: var(--color-error);
}
Reading this CSS, a developer immediately understands what each color does, without needing to decode hex values.
Naming Convention Tip
Name variables by their role, not their appearance. --color-primary is a better name than --color-blue because the role stays accurate even if the brand color changes from blue to green someday. --color-blue would then be a confusing name for a green color.
Section 18.5
Spacing Variables
Spacing is the second area that benefits most from variables. Padding, margin, and gap values tend to repeat throughout a stylesheet in a way that can easily become inconsistent.
A spacing scale defines a limited set of spacing values. Pages built on a consistent scale look more polished than pages where spacing values are invented freely on an element-by-element basis. A simple spacing scale might look like this:
:root {
/* Spacing scale */
--space-xs: 0.25rem; /* 4px */
--space-sm: 0.5rem; /* 8px */
--space-md: 1rem; /* 16px */
--space-lg: 1.5rem; /* 24px */
--space-xl: 2rem; /* 32px */
--space-2xl: 3rem; /* 48px */
--space-3xl: 4rem; /* 64px */
}
These values are based on a consistent scale where each step roughly doubles or adds a fixed increment. The exact values can be adjusted, but the important thing is that every spacing decision in the stylesheet comes from this set rather than from arbitrary numbers. Once defined, spacing variables are used for margin, padding, and gap:
.card {
padding: var(--space-lg);
margin-bottom: var(--space-md);
}
.section {
padding-top: var(--space-2xl);
padding-bottom: var(--space-2xl);
}
.nav-list {
display: flex;
gap: var(--space-md);
}
.form-field {
margin-bottom: var(--space-sm);
}
Why Consistent Spacing Matters
Pages where spacing is inconsistent feel visually noisy even when the design choices seem individually reasonable. Spacing scales create visual rhythm: the eye learns to expect and trust the distances between elements. Consistent spacing also makes it much easier to adjust spacing globally — if a design needs tighter spacing on mobile, adjusting the variables inside a media query changes the whole layout without touching individual rules.
Section 18.6
Typography Variables
Typography variables cover font families, font sizes, line heights, and font weights. Text appears on almost every element of a page, so centralizing typography decisions prevents gradual drift where similar elements end up slightly different sizes with slightly different font stacks.
A basic typography variable system:
:root {
/* Font families */
--font-body: Georgia, 'Times New Roman', serif;
--font-heading: 'Trebuchet MS', Verdana, sans-serif;
--font-mono: 'Courier New', Courier, monospace;
/* Font sizes */
--text-xs: 0.75rem; /* 12px */
--text-sm: 0.875rem; /* 14px */
--text-base: 1rem; /* 16px */
--text-lg: 1.125rem; /* 18px */
--text-xl: 1.25rem; /* 20px */
--text-2xl: 1.5rem; /* 24px */
--text-3xl: 1.875rem; /* 30px */
--text-4xl: 2.25rem; /* 36px */
/* Line heights */
--leading-tight: 1.25;
--leading-normal: 1.6;
--leading-loose: 1.8;
/* Font weights */
--weight-normal: 400;
--weight-medium: 500;
--weight-bold: 700;
}
body {
font-family: var(--font-body);
font-size: var(--text-base);
line-height: var(--leading-normal);
color: var(--color-text);
}
h1, h2, h3 {
font-family: var(--font-heading);
font-weight: var(--weight-bold);
line-height: var(--leading-tight);
}
h1 { font-size: var(--text-4xl); }
h2 { font-size: var(--text-3xl); }
h3 { font-size: var(--text-2xl); }
h4 { font-size: var(--text-xl); }
code, pre {
font-family: var(--font-mono);
font-size: var(--text-sm);
}
small, .text-small {
font-size: var(--text-sm);
color: var(--color-text-muted);
}
How Type Scales Work
The sizes above follow a modular scale, where each step is proportionally larger than the previous. This creates visual hierarchy that feels natural rather than arbitrary. You do not need to calculate your scale precisely, but you should observe that heading sizes differ meaningfully from body size, and that the differences feel consistent.
Sections 18.7 – 18.9
Reusable Patterns: Classes, Utilities, and Themes
Component Classes · Utility Classes · Theme Switching
Section 18.7
Reusable Classes
Variables store values. Reusable classes store patterns — combinations of properties that appear on many different elements across a site.
Consider the .card pattern that appears in almost every real website. A card is a bordered, padded container, often with a subtle shadow and slightly rounded corners, used for blog posts, product items, team members, lessons, resources, and many other content types. Rather than writing card styles fresh for each use, a developer writes a single .card class and applies it wherever a card is needed.
Reusable Card Component
.card {
background-color: var(--color-background);
border: 1px solid var(--color-border);
border-radius: 8px;
padding: var(--space-lg);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
}
In HTML, the class is applied wherever a card is needed:
<article class="card">
<h3>Introduction to HTML</h3>
<p>Learn how web pages are structured using HTML elements.</p>
</article>
<article class="card">
<h3>CSS Fundamentals</h3>
<p>Discover how CSS controls the visual presentation of web pages.</p>
</article>
Both articles look identical in terms of card styling. If the card border needs updating, one rule change in the CSS updates every card on the site.
Common Reusable Class Patterns
Container classes control maximum width and horizontal centering:
.container {
max-width: 1100px;
margin-left: auto;
margin-right: auto;
padding-left: var(--space-lg);
padding-right: var(--space-lg);
}
.container-narrow {
max-width: 720px;
margin-left: auto;
margin-right: auto;
padding-left: var(--space-lg);
padding-right: var(--space-lg);
}
Section classes handle vertical spacing between major page sections:
.section {
padding-top: var(--space-3xl);
padding-bottom: var(--space-3xl);
}
.section-sm {
padding-top: var(--space-xl);
padding-bottom: var(--space-xl);
}
Button classes standardize button appearance:
.button {
display: inline-block;
padding: var(--space-sm) var(--space-lg);
border-radius: 4px;
font-size: var(--text-base);
font-weight: var(--weight-bold);
cursor: pointer;
text-decoration: none;
border: none;
}
.button-primary {
background-color: var(--color-primary);
color: var(--color-background);
}
.button-primary:hover {
background-color: var(--color-primary-dark);
}
.button-secondary {
background-color: transparent;
color: var(--color-primary);
border: 2px solid var(--color-primary);
}
.button-secondary:hover {
background-color: var(--color-primary);
color: var(--color-background);
}
<a href="lessons.html" class="button button-primary">Browse Lessons</a>
<a href="about.html" class="button button-secondary">Learn More</a>
Base Plus Modifier
Notice that .button holds the shared structure, while .button-primary and .button-secondary each provide the visual variant. This is a common pattern called base plus modifier classes. The base class always applies; the modifier class provides the variation.
Section 18.8
Utility Classes
Utility classes are small, single-purpose classes that apply one or two specific CSS properties. They are used sparingly for minor adjustments without writing new, custom rules.
/* Text alignment */
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }
/* Text color variations */
.text-muted { color: var(--color-text-muted); }
.text-primary { color: var(--color-primary); }
.text-error { color: var(--color-error); }
.text-success { color: var(--color-success); }
/* Font weight */
.font-normal { font-weight: var(--weight-normal); }
.font-bold { font-weight: var(--weight-bold); }
/* Margin helpers */
.mt-md { margin-top: var(--space-md); }
.mb-md { margin-bottom: var(--space-md); }
.mt-lg { margin-top: var(--space-lg); }
.mb-lg { margin-bottom: var(--space-lg); }
/* Display */
.hidden { display: none; }
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
The .sr-only class is a special accessibility utility. It hides an element visually while keeping it readable by screen readers. It is commonly used for text labels that provide context to assistive technology users but would appear redundant to sighted users.
Using Utility Classes Wisely
Utility classes are most useful when a component needs a small one-off adjustment that does not warrant a new semantic class. For example, if one card on a page needs to be centered but other cards are not centered, adding class="card text-center" is cleaner than creating a new card-centered rule. Utility classes should not replace meaningful class names for major components. A navigation menu built entirely from utility classes becomes hard to read in HTML and hard to maintain. Reserve reusable classes for significant, recurring patterns; use utilities for small adjustments.
Section 18.9
Theme Preparation
CSS variables make it possible to define two complete sets of design values — a light theme and a dark theme — and switch between them with minimal code.
At the beginner level, understanding how theme switching works is more important than implementing a fully polished theme system. The principle is simple: the variables declared in :root represent the default (light) theme. A second set of variable declarations, triggered by a class applied to the html or body element, overrides those values with the dark theme values.
/* Default: light theme */
:root {
--color-background: #ffffff;
--color-background-soft: #f4f6f8;
--color-text: #1a1a1a;
--color-text-muted: #5a5a5a;
--color-primary: #2c5f8a;
--color-border: #d0d5da;
}
/* Dark theme: applied when html has class="theme-dark" */
.theme-dark {
--color-background: #121212;
--color-background-soft: #1e1e1e;
--color-text: #e8e8e8;
--color-text-muted: #a0a0a0;
--color-primary: #5a8fbf;
--color-border: #333333;
}
Because every CSS rule in the stylesheet uses variables, switching from light to dark requires only toggling the theme-dark class on the <html> element. All rules that reference --color-background, --color-text, and --color-primary instantly inherit the new values.
A Minimal JavaScript Toggle for Testing
const toggleButton = document.querySelector('#theme-toggle');
toggleButton.addEventListener('click', function () {
document.documentElement.classList.toggle('theme-dark');
});
<button id="theme-toggle">Toggle Dark Mode</button>
Using the System Preference
Modern browsers also expose a user's operating system preference for light or dark mode through the prefers-color-scheme media query. A well-built site can honor this preference automatically:
@media (prefers-color-scheme: dark) {
:root {
--color-background: #121212;
--color-background-soft: #1e1e1e;
--color-text: #e8e8e8;
--color-text-muted: #a0a0a0;
--color-primary: #5a8fbf;
--color-border: #333333;
}
}
Users who have set their device to dark mode will automatically see the dark version of the page without needing to click anything.
Theming Beyond Dark Mode
Theme preparation is not just about dark mode. The same technique can be used for high-contrast modes, seasonal themes, brand variations for different sections of a site, or accessibility adjustments for users who prefer larger text or reduced motion. Once a stylesheet uses variables consistently, any aspect of the visual design can be overridden cleanly for different contexts.
Section 18.10
Putting It Together: A Maintainable Stylesheet
Structure · Naming · Cleanup
Section 18.10
Maintaining a Stylesheet
As a stylesheet grows, organization becomes critical. A stylesheet that lacks structure becomes difficult to navigate, understand, and update. Developers working on large projects often spend more time reading CSS than writing it, which means readable organization pays off quickly.
Recommended Stylesheet Structure for a Student Project
/* ==========================================================
PROJECT NAME — main.css
========================================================== */
/* ----------------------------------------------------------
1. DESIGN TOKENS (Custom Properties)
---------------------------------------------------------- */
:root {
/* Colors, Spacing, Typography ... */
--color-primary: #2c5f8a;
--space-md: 1rem;
--text-base: 1rem;
}
/* ----------------------------------------------------------
2. RESET AND BASE STYLES
---------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
body {
margin: 0;
font-family: var(--font-body);
font-size: var(--text-base);
color: var(--color-text);
background-color: var(--color-background);
}
img { max-width: 100%; display: block; }
a { color: var(--color-primary); }
/* ----------------------------------------------------------
3. TYPOGRAPHY
---------------------------------------------------------- */
h1, h2, h3, h4 {
font-family: var(--font-heading);
font-weight: var(--weight-bold);
}
/* ----------------------------------------------------------
4. LAYOUT UTILITIES
---------------------------------------------------------- */
.container { max-width: 1100px; margin: 0 auto; }
.section { padding-top: var(--space-3xl); padding-bottom: var(--space-3xl); }
/* ----------------------------------------------------------
5. COMPONENTS
---------------------------------------------------------- */
.site-header { /* ... */ }
.site-nav { /* ... */ }
.hero { /* ... */ }
.card { /* ... */ }
.button { /* ... */ }
.site-footer { /* ... */ }
/* ----------------------------------------------------------
6. UTILITY CLASSES
---------------------------------------------------------- */
.text-center { text-align: center; }
.text-muted { color: var(--color-text-muted); }
.hidden { display: none; }
/* ----------------------------------------------------------
7. RESPONSIVE OVERRIDES
---------------------------------------------------------- */
@media (max-width: 768px) {
h1 { font-size: var(--text-3xl); }
}
/* ----------------------------------------------------------
8. THEME OVERRIDES
---------------------------------------------------------- */
@media (prefers-color-scheme: dark) {
:root { --color-background: #121212; }
}
This structure uses comments as section dividers, numbered sections for quick navigation, and an ordering that moves from the most fundamental (tokens, reset) to the most specific (utilities, overrides).
Maintenance Habits to Develop
Before adding a new color, check whether an existing variable already covers the need. Before writing a new component class, check whether the new component is actually a variation of an existing one. Remove unused rules periodically — CSS that is never applied silently slows down stylesheet loading and makes the file confusing to read. Keep specificity as low as possible: rules built on simple class selectors are much easier to override and debug than rules built on long selector chains. Name things by their purpose, not their appearance — .notification-error is a better name than .red-text-box.
Worked Example
Building a Complete Variable System for the Student Learning Site
The running project for this textbook is a Student Learning Site. Here is how a complete variable system and initial stylesheet would be built for that project.
Project Structure
student-learning-site/
index.html
about.html
lessons.html
resources.html
contact.html
css/
main.css
js/
main.js
images/
logo.png
hero.jpg
data/
resources.json
Planning the Design System
Questions to answer before writing any CSS
- Brand colorsA calm, academic blue as the primary color; green for success and confirmation; warm gold as an accent.
- FontsA readable serif for body text; a clean sans-serif for headings.
- SpacingGenerous spacing that does not feel cramped on any device.
- Dark modeYes — supported via
prefers-color-scheme.
The Resulting main.css Beginning
/* ==========================================================
Student Learning Site — main.css
========================================================== */
/* ----------------------------------------------------------
1. DESIGN TOKENS
---------------------------------------------------------- */
:root {
/* Colors */
--color-primary: #2c5f8a;
--color-primary-dark: #1e4266;
--color-primary-light: #dbe8f5;
--color-secondary: #4a9e6f;
--color-accent: #c68a1a;
--color-text: #1a1a1a;
--color-text-muted: #5a5a5a;
--color-background: #ffffff;
--color-background-soft: #f4f6f9;
--color-border: #d0d5da;
--color-success: #2e7d32;
--color-error: #c62828;
/* Spacing */
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--space-xl: 2rem;
--space-2xl: 3rem;
--space-3xl: 4rem;
/* Typography */
--font-body: Georgia, 'Times New Roman', serif;
--font-heading: 'Trebuchet MS', Verdana, sans-serif;
--font-mono: 'Courier New', Courier, monospace;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 1.875rem;
--text-4xl: 2.25rem;
--leading-tight: 1.25;
--leading-normal: 1.6;
--weight-normal: 400;
--weight-bold: 700;
/* Borders and Radius */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
/* Shadows */
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.12);
}
Notice that --radius-sm, --radius-md, --radius-lg, and --shadow-sm, --shadow-md have been added. These were not mentioned in the earlier sections, but they follow the same logic: any value that repeats across multiple components benefits from a name.
With these variables defined, the rest of the stylesheet can be written entirely in terms of named values. New contributors to the project — or a future version of the same student — can read var(--color-primary) and immediately understand what color is intended, without consulting a color palette or decoding a hex value.
Debugging Guidance
Common Mistakes in Chapter 18
Mistake 1
Forgetting the double hyphens
Custom properties must start with two hyphens. A single hyphen does not create a custom property.
/* Wrong — this is not a custom property */
-color-primary: #2c5f8a;
/* Correct */
--color-primary: #2c5f8a;
Mistake 2
Using a variable that was never declared
If
var(--color-accent) is referenced before
--color-accent is declared, the browser will use the fallback value if one exists, or simply produce no value (which may result in the property being ignored or inheriting its value). Check for typos in variable names carefully.
/* Declared */
--color-accent: #e8a020;
/* Referenced correctly */
color: var(--color-accent);
/* Typo — will not work */
color: var(--color-acent);
Mistake 3
Naming variables by appearance rather than role
/* Fragile — if the brand color changes to green, this name is misleading */
--blue: #2c5f8a;
/* Better — role-based name remains accurate even if the color changes */
--color-primary: #2c5f8a;
Mistake 4
Declaring a global variable somewhere other than :root
/* Wrong — this makes the variable only available inside .card elements */
.card {
--color-primary: #2c5f8a;
}
/* Correct — available everywhere */
:root {
--color-primary: #2c5f8a;
}
Mistake 5
Overusing utility classes
Utility classes are useful for small adjustments. They become a problem when developers use them for everything, resulting in HTML that is cluttered and hard to read.
<!-- Hard to read — too many utility classes for a major component -->
<article class="bg-white border-gray p-lg radius-md shadow-sm mb-md">
...
</article>
<!-- Cleaner — a meaningful component class does the heavy lifting -->
<article class="card">
...
</article>
Chapter 18 — Back Matter
Chapter Summary
Chapter 18 introduced CSS custom properties and the practice of organizing a stylesheet into a reusable, maintainable design system. Custom properties are declared with a --name: value syntax and retrieved with the var(--name) function. Declared in :root, they are available throughout the entire stylesheet, so changing a design decision requires editing only one line rather than searching through an entire file. CSS variables also enable theme switching: a second set of variable values, triggered by a class or a prefers-color-scheme media query, can change a site's entire appearance without touching component rules. A stylesheet organized this way is not just code — it is a design system that encodes a site's visual decisions in a readable, flexible form.
- Section 18.1A CSS design system organizes values into named, consistent tokens, improving consistency, maintainability, readability, and scalability compared to scattered, one-off rules.
- Section 18.2Custom properties are declared with two leading hyphens and retrieved with
var(). They are case-sensitive and can hold any valid CSS value.
- Section 18.3Declaring variables on
:root makes them available everywhere in the document, since :root matches the topmost <html> element. var() accepts an optional fallback value.
- Section 18.4Color variables should be grouped into primary, neutral, and feedback categories and named by role rather than appearance.
- Section 18.5A spacing scale defines a limited, consistent set of values used for padding, margin, and gap throughout a stylesheet, creating visual rhythm.
- Section 18.6Typography variables centralize font families, sizes, line heights, and weights, typically following a modular scale that creates clear visual hierarchy.
- Section 18.7Reusable classes such as
.card, .container, and base-plus-modifier button classes capture recurring component patterns so they need not be rebuilt for every use.
- Section 18.8Utility classes apply one or two properties for small, one-off adjustments, including the accessibility utility
.sr-only, but should not replace meaningful component classes.
- Section 18.9Theme switching can be driven by a toggled class (such as
.theme-dark) or by the prefers-color-scheme media query, and the same technique supports more than dark mode.
- Section 18.10A maintainable stylesheet follows a clear, numbered structure from tokens and resets through components, utilities, and overrides, with regular cleanup and purpose-based naming.
Key Terms
custom property
A CSS variable declared with a name beginning with -- that stores a reusable value.
CSS variable
A common name for a CSS custom property.
var() function
The CSS function used to retrieve the value of a custom property.
:root
A pseudo-class selector that matches the root element of the document; the standard location for site-wide custom property declarations.
design token
A named design decision stored as a variable — a color value, a spacing unit, a font size — used consistently throughout a design system.
design system
An organized set of reusable values, components, and patterns that governs the visual language of a website or application.
color variable
A custom property that stores a color value and gives it a role-based name.
spacing scale
A defined set of spacing values used consistently for padding, margin, and gap throughout a stylesheet.
typography scale
A defined set of font size and line height values used consistently for text elements throughout a stylesheet.
reusable class
A CSS class that captures a recurring pattern, such as a card or button, and can be applied to many different elements.
utility class
A small, single-purpose CSS class that applies one or two properties for minor adjustments.
.sr-only
A utility class that hides an element visually while keeping it accessible to screen readers.
theme switching
The practice of defining alternate sets of variable values that can be applied to change the entire visual appearance of a page.
prefers-color-scheme
A CSS media query that detects whether the user's operating system is set to light or dark mode.
Read each example and answer the question below it.
Exercise 18-CR-1
:root {
--color-primary: #3a5f8a;
--color-text: #1a1a1a;
}
h2 {
color: var(--color-primray);
}
Question: This rule contains an error. Identify what is wrong and explain what the browser will do when it encounters this rule.
Exercise 18-CR-2
:root {
--space-md: 1rem;
--space-lg: 1.5rem;
}
.card {
padding: var(--space-lg);
margin-bottom: var(--space-md);
}
Question: Suppose the design team decides that all spacing values should increase by 25% to give the site more breathing room. How many lines of CSS need to change, and which lines are they?
Exercise 18-CR-3
:root {
--color-background: #ffffff;
--color-text: #1a1a1a;
}
.theme-dark {
--color-background: #121212;
--color-text: #e8e8e8;
}
body {
background-color: var(--color-background);
color: var(--color-text);
}
Question: What HTML change is needed to activate the dark theme? What will happen to the body element's background and text colors when that change is made?
Exercise 18-CR-4
.button {
display: inline-block;
padding: var(--space-sm) var(--space-lg);
border-radius: var(--radius-sm);
font-weight: var(--weight-bold);
cursor: pointer;
text-decoration: none;
border: none;
}
.button-primary {
background-color: var(--color-primary);
color: white;
}
<a href="lessons.html" class="button">Browse Lessons</a>
Question: How will this link appear in the browser? What is missing from the HTML to make the link look like a primary button?
- Define a color system. Create a
variables.css file (or add a :root block to an existing stylesheet) that defines at least: a primary color and a darker variation, a neutral text color and a muted variation, a background color and a soft background variation, a border color, and an error color and a success color. Use role-based names for all variables. Apply at least five of these variables in existing CSS rules.
- Build a spacing scale. Add a spacing scale to your
:root block with at least six named values from --space-xs to --space-3xl. Refactor an existing page's CSS so that all padding, margin, and gap values use variables from this scale instead of hard-coded pixel or rem values.
- Write a reusable card class. Write a
.card class that uses variables for its background color, border color, padding, border-radius, and shadow. Apply the class to at least three different HTML elements across your project and confirm that they all receive the same base styling.
- Add a .button base class. Write a
.button base class and two modifier classes: .button-primary and .button-secondary. Apply both types to links and actual <button> elements in an HTML file. Test that hover states work for both types.
- Prepare a dark theme. Add a
.theme-dark rule set that overrides your core color variables with dark theme equivalents. Write a small JavaScript toggle that adds and removes the theme-dark class from the <html> element when a button is clicked. Test the toggle in the browser.
- Add a prefers-color-scheme media query. Add a
@media (prefers-color-scheme: dark) block to your stylesheet that overrides your color variables. Use your browser's developer tools to simulate a dark mode preference and confirm the page adjusts correctly.
Objective
Build a small webpage that demonstrates a complete variable-driven design system with light and dark theme support.
- Step 1Create
index.html and css/main.css.
- Step 2Define a complete
:root block with color, spacing, and typography variables.
- Step 3Style the page using only variables — no hard-coded colors, spacing, or font sizes in any rule below the
:root block.
- Step 4Include a header with navigation, a hero section, at least two cards, a button, and a footer.
- Step 5Add a
.theme-dark class with overriding color variables.
- Step 6Add a
prefers-color-scheme: dark media query using the same dark color values.
- Step 7Add a theme toggle button that uses JavaScript to add and remove
theme-dark from the <html> element.
- Step 8Verify the page looks correct in both light and dark mode.
Stretch Goal
Add a third theme (for example, a high-contrast accessibility theme) by creating a .theme-high-contrast class and adding a second toggle button.
Accessibility Check
Before completing Chapter 18 work, review the following:
- Do color variables maintain sufficient contrast between text and background? Light text on a light background, or dark text on a dark background, fails accessibility contrast requirements. Check your color choices with a contrast checker tool.
- Does your dark theme maintain the same contrast ratios as your light theme? A dark theme that saves development time is not worth it if it becomes difficult to read.
- Does the
.sr-only class exist in your stylesheet for content that should be available to screen readers but not visually displayed?
- Are button and link text colors readable against their backgrounds in both light and dark modes?
- Does your form feedback use color and text together, not color alone? A user who cannot distinguish colors must be able to read error messages without relying on the color of the message.
These questions draw on material from earlier chapters.
- (Chapter 12) A button has both
.button and .button-primary classes applied. Both classes set background-color. Which background color will apply, and why?
- (Chapter 14) A
.card element has padding: var(--space-lg) and a declared width of 300px with box-sizing: border-box. If --space-lg is 1.5rem and the base font size is 16px, what is the width of the content area inside the card?
- (Chapter 15) A navigation bar uses
display: flex with gap: var(--space-md). If --space-md is changed from 1rem to 1.5rem, what changes on the page?
- (Chapter 17) If dark theme color variables are declared inside a
@media (prefers-color-scheme: dark) block, do they also apply when the .theme-dark class is toggled? Explain.
- (Chapter 11) If two stylesheets are linked in the same HTML file and both declare
--color-primary, which declaration wins? What CSS rule governs this?
Chapter 18 Checklist
Use this to confirm your understanding before moving to Chapter 19
Concepts
- I can explain why a CSS design system improves consistency, maintainability, readability, and scalability.
- I can declare a custom property and retrieve its value with
var().
- I can explain why site-wide variables are declared in
:root rather than on individual elements.
- I can name color variables by role rather than appearance.
- I can build and apply a consistent spacing scale.
- I can build and apply a typography variable system.
- I can write a reusable component class and a base-plus-modifier button system.
- I can distinguish a reusable class from a utility class and choose correctly between them.
- I can explain how a toggled class and
prefers-color-scheme each drive theme switching.
Applications
- I can organize a stylesheet into a clear, numbered structure from tokens through overrides.
- I can identify and fix the common custom property mistakes covered in this chapter.
- I can build a small page using only variables for color, spacing, and typography.
- I can add a working light/dark theme toggle to a webpage.
Study Habits
- I checked existing variables before inventing new color or spacing values.
- I tested my dark theme for contrast, not just appearance.
- I reviewed my HTML for utility-class clutter that a reusable class could replace.
Preview — Chapter 19
What JavaScript Does in the Browser
Chapter 18 closes out Part III by showing how CSS can be organized into a reusable design system. Chapter 19 introduces JavaScript as the third and final core language of the browser. So far, HTML has given web pages structure and meaning, and CSS has given them visual presentation and layout. JavaScript adds the third layer: behavior and interaction. Chapter 19 begins with what JavaScript can do in the browser, how to link a JavaScript file, and how to use the browser console. You will write your first JavaScript statements and see how a small amount of code can make a page respond to user actions — and you will see how HTML, CSS, and JavaScript divide responsibilities, and why keeping those responsibilities separate makes web projects easier to build and maintain.
MGU Library Connection
- Web Development: HTML, CSS, and JavaScript — Appendix F: CSS Variables and Theme Reference
- Web Development: HTML, CSS, and JavaScript — Appendix D: CSS Reference
- Companion resource: CSS Variables Guide
- Companion resource: Theme System Guide
- Companion resource: Accessibility Checklist
- Next chapter: Chapter 19 — What JavaScript Does in the Browser