CSS Custom Properties (CSS Variables)

  • Home
  • CSS
  • CSS Custom Properties (CSS Variables)
Front
Back
Right
Left
Top
Bottom
CSS VARIABLES
Dynamic Theming at Scale

CSS Custom Properties (CSS Variables)

"CSS Custom Properties are runtime variables. They live in the browser's CSSOM and can be changed, inherited, or overridden dynamically — something Sass variables will never be able to do."
Martin Metodiev, Design Systems Collective
MOMENT CHANGES

The Moment Everything Changes

It’s 11pm. Your designer sends a Slack message: “Can we add a dark mode? And the client wants to be able to customise the brand colours at runtime.”

Before CSS custom properties: three days of work, possibly a full CSS architecture overhaul.

With CSS custom properties: one afternoon.

This is why, in 2027, CSS variables aren’t a nice-to-have. They’re the foundation of every scalable UI system — FrontendTools, CSS Variables Guide 2027.
VARIABLES
The Critical Difference

CSS Variables vs Sass Variables

This distinction trips up almost every developer who comes from a Sass background.
Feature Sass Variables CSS Custom Properties
Scope Compile-time only Runtime (browser)
Dynamic updates ❌ Impossible ✅ Yes — via JS
Cascade & inheritance ❌ No ✅ Yes — like any CSS property
Media query reactivity ❌ No ✅ Yes
JS interaction ❌ No ✅ Yes
DevTools visibility ❌ Compiled away ✅ Visible, editable
"Sass variables are compile-time constants — they disappear once Sass compiles to CSS. CSS Custom Properties are runtime variables. Conflating these two concepts is dangerous."
Martin Metodiev, Design Systems Collective
Use Sass variables for
build-time constants that never change at runtime (breakpoint values, build flags, loop utilities).
Use CSS custom properties for
everything that defines visual appearance — colours, spacing, typography, radii, shadows.
MDN

The MDN Reference

MDN Web Docs — Using CSS Custom Properties (Variables):

The official MDN page covers the `@property` at-rule (for typed, animated custom properties), inheritance, and the `var()` function with fallbacks.
MDN

Building a Token System from Scratch

The most powerful pattern is the two-tier token system: primitive tokens → semantic tokens → components.

Primitive Tokens (Raw Values)

🎨
/* tokens/primitives.css */
:root {
  /* Color palette — raw values */
  --color-blue-50:  #eff6ff;
  --color-blue-100: #dbeafe;
  --color-blue-500: #3b82f6;
  --color-blue-600: #2563eb;
  --color-blue-700: #1d4ed8;

  --color-gray-50:  #f9fafb;
  --color-gray-100: #f3f4f6;
  --color-gray-500: #6b7280;
  --color-gray-900: #111827;

  /* Spacing scale */
  --space-1:  0.25rem;   /* 4px  */
  --space-2:  0.5rem;    /* 8px  */
  --space-3:  0.75rem;   /* 12px */
  --space-4:  1rem;      /* 16px */
  --space-6:  1.5rem;    /* 24px */
  --space-8:  2rem;      /* 32px */
  --space-12: 3rem;      /* 48px */

  /* Border radius */
  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-xl: 1rem;
  --radius-full: 9999px;
}

Semantic Tokens (Contextual Meaning)

🎨
/* tokens/semantic.css — light theme (default) */
:root {
  --color-background:        var(--color-gray-50);
  --color-surface:           white;
  --color-surface-elevated:  white;
  --color-border:            var(--color-gray-100);

  --color-text-primary:      var(--color-gray-900);
  --color-text-secondary:    var(--color-gray-500);

  --color-brand-primary:     var(--color-blue-600);
  --color-brand-hover:       var(--color-blue-700);
  --color-brand-subtle:      var(--color-blue-50);
}
"Separate primitive and semantic tokens: keep raw color values (like `--color-blue-600`) separate from their semantic usage (like `--color-brand-primary`)."
FrontendTools, CSS Variables Guide 2027

Component Styles Using Tokens

🎨
/* components/button.css */
.btn-primary {
  background-color: var(--color-brand-primary);
  color: white;
  padding: var(--space-3) var(--space-6);
  border-radius: var(--radius-md);
  font-weight: 500;
  transition: background-color 150ms ease;
}

.btn-primary:hover {
  background-color: var(--color-brand-hover);
}
Now update `–color-brand-primary` in one place and every button, link, badge, and accent in the entire app updates instantly.

Light / Dark Mode in 15 Lines

🎨
/* Dark theme — override semantic tokens */
[data-theme="dark"],
@media (prefers-color-scheme: dark) {
  :root {
    --color-background:        var(--color-gray-900);
    --color-surface:           #1f2937;
    --color-surface-elevated:  #374151;
    --color-border:            #374151;

    --color-text-primary:      var(--color-gray-50);
    --color-text-secondary:    var(--color-gray-500);

    --color-brand-primary:     var(--color-blue-500);
    --color-brand-hover:       var(--color-blue-400);
    --color-brand-subtle:      #1e3a5f;
  }
}
And your JavaScript toggle:
📄
// Toggle theme with one DOM attribute
const toggleTheme = () => {
  const current = document.documentElement.getAttribute('data-theme');
  document.documentElement.setAttribute(
    'data-theme',
    current === 'dark' ? 'light' : 'dark'
  );
  localStorage.setItem('theme', current === 'dark' ? 'light' : 'dark');
};

// On load — respect saved preference
const saved = localStorage.getItem('theme');
if (saved) document.documentElement.setAttribute('data-theme', saved);
Every component using `var(–color-background)`, `var(–color-text-primary)`, etc. switches themes instantly — without a single JavaScript style injection.
JavaScript

Runtime Theming with JavaScript

Because CSS custom properties live in the browser at runtime, you can read and write them from JavaScript:
📄
// Read a CSS variable
const brandColor = getComputedStyle(document.documentElement)
  .getPropertyValue('--color-brand-primary').trim();

// Write a CSS variable — live theme customisation
document.documentElement.style.setProperty('--color-brand-primary', '#10b981');
document.documentElement.style.setProperty('--color-brand-hover', '#059669');
This is how white-label SaaS products implement per-tenant branding. The client’s hex codes get set once on page load, and the entire UI responds.
@PROPERTY

Typed Custom Properties with `@property`

The `@property` at-rule (part of CSS Houdini) lets you define the type and animation behaviour of custom properties:
🎨
/* Define a typed custom property — enables CSS transitions on the value */
@property --brand-hue {
  syntax: '<number>';
  inherits: true;
  initial-value: 220;
}

:root {
  --brand-hue: 220;
  --color-brand: hsl(var(--brand-hue) 85% 55%);
}

/* Now you can animate the hue shift */
.logo:hover {
  --brand-hue: 270;
  transition: --brand-hue 400ms ease;
}
TYPOGRAPHY

Responsive Fluid Typography with CSS Variables

One of the most practical uses of custom properties + `clamp()`:
🎨
:root {
  /* Fluid type scale — scales between viewport widths */
  --text-sm:   clamp(0.875rem, 0.8rem + 0.3vw, 1rem);
  --text-base: clamp(1rem,     0.9rem + 0.5vw, 1.125rem);
  --text-lg:   clamp(1.125rem, 1rem   + 0.6vw, 1.375rem);
  --text-xl:   clamp(1.25rem,  1.1rem + 0.8vw, 1.75rem);
  --text-2xl:  clamp(1.5rem,   1.2rem + 1.5vw, 2.25rem);
  --text-4xl:  clamp(2rem,     1.5rem + 2.5vw, 3.5rem);
}

h1 { font-size: var(--text-4xl); }
h2 { font-size: var(--text-2xl); }
p  { font-size: var(--text-base); line-height: 1.6; }
One token definition. Perfectly fluid typography from mobile to 4K. Zero media queries.
Business Leader
Theme-Ready Products Ship Faster

For Business Leaders

White-label products, multi-tenant SaaS, enterprise apps with custom branding requirements — all of these traditionally required significant engineering effort to implement per-client theming.
With a properly built CSS token system, adding a new client’s brand colours requires one JSON file update and a 5-minute deploy. The engineering cost of “we need our logo colour in the product” drops from days to minutes.
"Teams that simplify CSS with custom properties ship faster, reduce regressions, and make designers and developers smile." Brand Nexus Studios, 2027
CSS custom properties are the closest thing CSS has to a true design system API. They bridge the gap between design decisions and implementation, enable runtime theming without JavaScript style injection, and make every responsive, theme-aware, white-label UI dramatically simpler to build and maintain.
Set up your token system on day one. Your future self — and your client’s brand team — will thank you.
A well-designed CSS token system has that same quality — one variable describes a purpose, and that purpose flows naturally through every corner of your product.

Explore project snapshots or discuss custom web solutions.

Simplicity is not the absence of clutter, that's a consequence of simplicity. Simplicity is somehow essentially describing the purpose and place of an object and product.

Jonathan Ive, former Chief Design Officer, Apple

Thank You for Spending Your Valuable Time

I truly appreciate you taking the time to read blog. Your valuable time means a lot to me, and I hope you found the content insightful and engaging!
Front
Back
Right
Left
Top
Bottom
FAQ's

Frequently Asked Questions

Yes. CSS custom properties have had excellent browser support since 2016 (Chrome 49, Firefox 31, Safari 9.1). The `@property` at-rule has slightly lower but growing support — check for current status.

Gradually, for new code. For values that only matter at build time (breakpoint numbers used in `@media`, build-time loop variables), Sass is still appropriate. For anything visual that might need runtime theming or dynamic behaviour, CSS custom properties are the right choice.

Use the second argument of `var()`: `color: var(--color-brand-primary, #2563eb)`. The fallback is used if the property is undefined. You can also chain: `var(--component-color, var(--color-brand-primary, #2563eb))`.

You can use them as *values* inside media queries, but you cannot use them as the *condition* of a media query (e.g., `@media (min-width: var(--breakpoint-lg))` doesn't work — this is a known limitation). Use Sass variables or hardcoded values for media query thresholds.

Dramatically disseminate real-time portals rather than top-line action items. Uniquely provide access to low-risk high-yield products without dynamic products. Progressively re-engineer low-risk high-yield ideas rather than emerging alignments.

Follow a consistent pattern: `--[category]-[variant]-[state]`. Example: `--color-text-primary`, `--color-text-disabled`, `--space-4`, `--radius-md`. Avoid generic names like `--blue` or `--primary` — semantic names communicate usage, not just values.

Comments are closed