Chapter 1 of ?
css 8 min read

CSS3 Mastery — Chapter 20: CSS Variables, @property (Houdini) & Dark Mode

Chapter 20: CSS Variables, @property (Houdini), env() & Dark Mode Architecture

CSS Custom Properties (Variables) introduce dynamic value scoping, fallback cascades, and component tokenization to modern web stylesheets. Combined with CSS Houdini @property for typed variable gradient & angle animations, environmental variables (env()), attr() attribute reflections, and system theme architecture (prefers-color-scheme), developers can engineer scalable design systems.

20.1 CSS Custom Properties (--var) & Multi-Level Fallbacks

CSS variables are declared using double dashes (--variable-name) on a target element or globally on :root. They support nested fallback defaults via var(--primary, var(--fallback, #38bdf8)):

Scope Level Declaration Example Cascade & Fallback Rule
Global Scope :root { --primary-color: #6366f1; } Available across the entire document cascade.
Component Scope .card { --primary-color: #10b981; } Overrides global value for .card and its DOM descendants.
Nested Fallbacks color: var(--theme-color, var(--brand-accent, #38bdf8)); Evaluates first valid variable in chain before resorting to literal fallback value.
<!DOCTYPE html>
<html>
<head>
  <style>
    *, *::before, *::after { box-sizing: border-box; }
    body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; }
    h4 { margin-top: 0; color: #38bdf8; font-size: 14px; }

    :root {
      --brand-blue: #38bdf8;
      --card-padding: 16px;
    }

    .var-card {
      /* Scoped local variable override */
      --card-bg: #1e293b;
      --card-border: var(--brand-blue);
      background: var(--card-bg);
      border: 2px solid var(--card-border);
      border-radius: 12px;
      padding: var(--card-padding);
      max-width: 380px;
    }
  </style>
</head>
<body>
  <h4>Scoped Custom Properties &amp; Fallbacks</h4>
  <div class="var-card">
    <strong style="color:var(--brand-blue);">Token-Driven Component</strong>
    <p style="font-size:12px;color:#94a3b8;margin:6px 0 0;">Styled using scoped variables and global :root tokens.</p>
  </div>
</body>
</html>

20.2 Typed CSS Variables (@property) & Gradient/Angle Animations

Standard CSS custom properties cannot be animated smoothly because browser engines treat them as untyped raw strings. CSS Houdini @property registers custom properties with strict data types (e.g. syntax: "<color>", "<angle>", "<percentage>"), enabling smooth gradient transitions and rotating borders!

Syntax Type Example Syntax String Animatable Use Case
"<color>" syntax: "<color>"; Smooth CSS gradient color transition on :hover.
"<angle>" syntax: "<angle>"; Rotating linear/conic gradient angle animations ($0^\circ \rightarrow 360^\circ$).
"<percentage>" syntax: "<percentage>"; Animatable gradient stop positions and progress indicators.
"<length>" syntax: "<length>"; Animatable custom spacing, shadow blurs, or border widths.
<!DOCTYPE html>
<html>
<head>
  <style>
    *, *::before, *::after { box-sizing: border-box; }
    body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; display: flex; flex-direction: column; gap: 20px; }
    h4 { margin-top: 0; color: #10b981; font-size: 14px; }

    /* 1. Typed Color Variables for Gradient Transition */
    @property --c1 { syntax: "<color>"; inherits: false; initial-value: #4f46e5; }
    @property --c2 { syntax: "<color>"; inherits: false; initial-value: #06b6d4; }

    .grad-btn {
      background: linear-gradient(135deg, var(--c1), var(--c2));
      color: #fff; border: none; padding: 12px 24px; border-radius: 10px;
      font-weight: 700; font-size: 13px; cursor: pointer; max-width: 260px;
      transition: --c1 0.5s ease, --c2 0.5s ease;
    }
    .grad-btn:hover { --c1: #ec4899; --c2: #f59e0b; }

    /* 2. Typed Angle Variable for Rotating Gradient Border */
    @property --border-angle { syntax: "<angle>"; inherits: false; initial-value: 0deg; }

    .g-card {
      width: 260px; height: 90px; border-radius: 12px; padding: 2px;
      background: conic-gradient(from var(--border-angle), #6366f1, #ec4899, #38bdf8, #6366f1);
      animation: rotateBorder 4s linear infinite;
    }
    .g-card-inner {
      background: #1e293b; width: 100%; height: 100%; border-radius: 10px;
      display: flex; align-items: center; justify-content: center;
      font-size: 12px; font-weight: bold; color: #a5b4fc;
    }

    @keyframes rotateBorder {
      to { --border-angle: 360deg; }
    }
  </style>
</head>
<body>
  <div>
    <h4>1. @property Gradient Color Transition (Hover Button)</h4>
    <button class="grad-btn">Hover for Gradient Morph</button>
  </div>

  <div>
    <h4>2. @property Conic Border Angle Rotation</h4>
    <div class="g-card">
      <div class="g-card-inner">360° Rotating Gradient Border</div>
    </div>
  </div>
</body>
</html>

20.3 Environmental Variables (env()) & Dynamic attr() Content

The env() function reads user-agent environment variables provided by the device operating system (e.g. mobile safe area insets). The attr() function extracts HTML attribute values to display dynamic content without hardcoded text:

Function Syntax Example Purpose & Use Case
env() padding-top: env(safe-area-inset-top, 20px); Reads device environment offsets (notches, home bars) with fallback support.
attr() content: attr(data-tooltip); Retrieves element HTML attributes inside CSS ::before or ::after pseudo-elements.
<!DOCTYPE html>
<html>
<head>
  <style>
    *, *::before, *::after { box-sizing: border-box; }
    body { font-family: system-ui, sans-serif; background: #0f172a; color: #f8fafc; padding: 24px; margin: 0; }
    h4 { color: #f59e0b; margin-top: 0; }

    /* Tooltip using attr(data-tooltip) */
    .tooltip-btn {
      position: relative; background: #3b82f6; color: #fff; border: none;
      padding: 10px 18px; border-radius: 8px; font-weight: bold; cursor: pointer;
    }
    .tooltip-btn:hover::after {
      content: attr(data-tooltip); /* Pulls text directly from HTML attribute */
      position: absolute; bottom: 125%; left: 50%; transform: translateX(-50%);
      background: #1e293b; color: #38bdf8; border: 1px solid #38bdf8;
      padding: 6px 12px; border-radius: 6px; font-size: 11px; white-space: nowrap;
      pointer-events: none; box-shadow: 0 4px 12px rgba(0,0,0,0.4);
    }
  </style>
</head>
<body>
  <h4>Dynamic CSS attr() Tooltip Demo</h4>
  <p style="font-size:12px;color:#64748b;">Hover button below to render tooltip generated via CSS attr(data-tooltip):</p>

  <button class="tooltip-btn" data-tooltip="⚡ Tooltip powered by CSS attr()!">
    Hover Me for Tooltip
  </button>
</body>
</html>

20.4 Dynamic Dark / Light Mode Switcher Architecture

Architecting theme switching using CSS variables scoped under [data-theme="dark"] and [data-theme="light"] attributes, combined with system preference detection via prefers-color-scheme:

<!DOCTYPE html>
<html>
<head>
  <style>
    *, *::before, *::after { box-sizing: border-box; }

    /* Default Light Theme Variables */
    :root, [data-theme="light"] {
      --bg-body: #f8fafc;
      --text-main: #0f172a;
      --card-bg: #ffffff;
      --card-border: #e2e8f0;
      --accent: #3b82f6;
    }

    /* Dark Theme Variable Overrides */
    [data-theme="dark"] {
      --bg-body: #0f172a;
      --text-main: #f8fafc;
      --card-bg: #1e293b;
      --card-border: #334155;
      --accent: #38bdf8;
    }

    body {
      font-family: system-ui, sans-serif;
      background: var(--bg-body);
      color: var(--text-main);
      padding: 24px; margin: 0;
      transition: background 0.3s ease, color 0.3s ease;
    }

    .theme-card {
      background: var(--card-bg);
      border: 2px solid var(--card-border);
      border-radius: 12px; padding: 24px; max-width: 380px; margin: 0 auto;
      box-shadow: 0 8px 24px rgba(0,0,0,0.1); text-align: center;
      transition: background 0.3s ease, border-color 0.3s ease;
    }

    .toggle-btn {
      background: var(--accent); color: #fff; border: none;
      padding: 10px 18px; border-radius: 8px; font-weight: bold; cursor: pointer;
      margin-top: 14px; transition: background 0.3s ease;
    }
  </style>
</head>
<body id="theme-body" data-theme="dark">
  <div class="theme-card">
    <h4 style="margin-top:0;color:var(--accent);">Dynamic Theme Switcher</h4>
    <p style="font-size:12px;">Click button to toggle <code>data-theme</code> attribute between dark and light themes!</p>
    <button class="toggle-btn" onclick="const b = document.getElementById('theme-body'); b.dataset.theme = b.dataset.theme === 'dark' ? 'light' : 'dark';">
      Toggle Dark / Light Mode 🌓
    </button>
  </div>
</body>
</html>

20.5 Interactive Sandbox — Dark Mode, @property & attr() Playground

Test theme variable switching, @property angle rotation, and dynamic attr() tooltips live:

Live Playground — Chapter 20 Sandbox
Done with this chapter?
Mark it complete to track your progress and unlock your certificate.
Next Up

Learner Reviews

Write a Review
Share your experience to help other learners.
Your Rating *