CSS Variables & Utility Patterns

Custom properties, design tokens, dark mode, and reusable utility classes

Last updated on

CSS Custom Properties (variables) let you define reusable values and build a consistent design system. They're essential for mid-level developers.

CSS Variables (Custom Properties)

Defining Variables

:root {
  --primary: #2563eb;
  --primary-dark: #1d4ed8;
  --text-color: #1a1a1a;
  --text-muted: #6b7280;
  --bg-color: #ffffff;
  --bg-secondary: #f8f9fa;
  --radius: 8px;
  --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
  --font-sans: "Inter", sans-serif;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
}

Using Variables

.card {
  background: var(--bg-color);
  color: var(--text-color);
  border-radius: var(--radius);
  padding: var(--spacing-lg);
  box-shadow: var(--shadow);
  font-family: var(--font-sans);
}

.button {
  background: var(--primary);
  color: white;
  padding: var(--spacing-sm) var(--spacing-md);
  border-radius: var(--radius);
}

.button:hover {
  background: var(--primary-dark);
}

Fallback Values

color: var(--text-color, #333); /* uses #333 if --text-color is not defined */

Scoped Variables

Variables can be scoped to any selector:

.dark-section {
  --text-color: #ffffff;
  --bg-color: #1a1a1a;
}
/* Children of .dark-section use these overridden values */

Dark Mode with CSS Variables

:root {
  --bg: #ffffff;
  --text: #1a1a1a;
  --border: #e2e8f0;
  --card-bg: #ffffff;
}

[data-theme="dark"] {
  --bg: #0f172a;
  --text: #e2e8f0;
  --border: #334155;
  --card-bg: #1e293b;
}

/* Or use the system preference */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0f172a;
    --text: #e2e8f0;
    --border: #334155;
    --card-bg: #1e293b;
  }
}

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

Toggle with JavaScript

const toggle = document.querySelector("#theme-toggle");
toggle.addEventListener("click", () => {
  const current = document.documentElement.getAttribute("data-theme");
  document.documentElement.setAttribute(
    "data-theme",
    current === "dark" ? "light" : "dark"
  );
});

Design Token System

A complete set of variables for a consistent design:

:root {
  /* Colors */
  --color-primary: hsl(220, 90%, 56%);
  --color-success: hsl(142, 70%, 45%);
  --color-warning: hsl(38, 92%, 50%);
  --color-danger: hsl(0, 84%, 60%);

  /* Typography */
  --font-sans: "Inter", sans-serif;
  --font-mono: "Fira Code", monospace;
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;
  --text-2xl: 1.5rem;

  /* Spacing */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-6: 24px;
  --space-8: 32px;

  /* Borders */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-full: 9999px;

  /* Shadows */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
  --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}

Utility Classes

Small, single-purpose classes that can be composed:

/* Text alignment */
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }

/* Display */
.flex { display: flex; }
.grid { display: grid; }
.hidden { display: none; }
.block { display: block; }

/* Flex utilities */
.items-center { align-items: center; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.gap-sm { gap: var(--space-2); }
.gap-md { gap: var(--space-4); }
.gap-lg { gap: var(--space-6); }

/* Spacing */
.mt-1 { margin-top: var(--space-1); }
.mt-2 { margin-top: var(--space-2); }
.mt-4 { margin-top: var(--space-4); }
.p-4 { padding: var(--space-4); }
.p-6 { padding: var(--space-6); }

/* Width */
.w-full { width: 100%; }
.max-w-lg { max-width: 960px; }
.mx-auto { margin-left: auto; margin-right: auto; }

/* Visually hidden (accessible) */
.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;
}

Variables vs Preprocessors (Sass)

FeatureCSS VariablesSass Variables
RuntimeYes (live in browser)No (compiled)
JavaScript accessYesNo
Cascade/ScopeYesNo
Theming/Dark modeEasyHard
Calculationscalc()$var + 10
Browser supportModern browsersAll (compiled to CSS)

Recommendation: Use CSS Variables for theming and runtime values. Sass variables are still useful for build-time constants.

On this page