Skip to main content

Advanced CSS Custom Properties Patterns

Beyond simple variable declarations, CSS custom properties enable powerful runtime theming, component APIs, and JavaScript integration patterns.

1. Component API Pattern

Design your components with a CSS "API" — intentionally exposed variables that consumers can override.

/* The component defines its defaults */
.button {
--btn-bg: var(--color-primary, #6c63ff);
--btn-color: white;
--btn-radius: var(--radius-md, 8px);
--btn-padding: 0.75rem 1.5rem;

background: var(--btn-bg);
color: var(--btn-color);
border-radius: var(--radius);
padding: var(--btn-padding);
border: none;
cursor: pointer;
transition: filter 0.2s;
}
.button:hover { filter: brightness(1.12); }

/* Consumers customize without touching the component rules */
.button--danger { --btn-bg: #e53e3e; }
.button--ghost { --btn-bg: transparent; --btn-color: var(--color-primary); }
.button--large { --btn-padding: 1rem 2.5rem; }

2. Theming with Data Attributes

Switch entire themes by changing a single attribute on :root or a section.

:root, [data-theme="light"] {
--bg: #ffffff;
--text: #111111;
--card: #f7f7f7;
--border: #e0e0e0;
}

[data-theme="dark"] {
--bg: #0f0f0f;
--text: #f0f0f0;
--card: #1c1c1e;
--border: #333333;
}

/* Works for scoped sections too! */
.hero[data-theme="dark"] {
--bg: #0a0a0a;
--text: #ffffff;
}

3. JavaScript + CSS Variables Bridge

CSS custom properties are readable and writable from JavaScript — creating a clean JS/CSS bridge.

// Read a variable
const primary = getComputedStyle(document.documentElement)
.getPropertyValue('--color-primary').trim();

// Write / update a variable at runtime
document.documentElement.style.setProperty('--color-primary', '#00ff87');

// Update per-element
const card = document.querySelector('.card');
card.style.setProperty('--card-bg', 'rgba(0, 255, 135, 0.1)');

Use cases:

  • Theme toggle (dark / light mode at button click)
  • User preference persistence (save to localStorage, restore on load)
  • Parallax effects (update --scroll-y on scroll for CSS-driven parallax)
  • Mouse tracking (update --mouse-x / --mouse-y for glow effects)

4. Parallax with CSS Variables

window.addEventListener('scroll', () => {
document.documentElement.style.setProperty('--scroll-y', `${window.scrollY}px`);
});
.parallax-bg {
transform: translateY(calc(var(--scroll-y) * 0.4));
}

5. Mouse-Tracking Glow Effect

document.addEventListener('mousemove', (e) => {
document.documentElement.style.setProperty('--mouse-x', `${e.clientX}px`);
document.documentElement.style.setProperty('--mouse-y', `${e.clientY}px`);
});
.glow-card::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(
400px at var(--mouse-x) var(--mouse-y),
rgba(108, 99, 255, 0.15),
transparent 70%
);
border-radius: inherit;
pointer-events: none;
}

6. @property: Typed CSS Variables (Modern)

@property lets you define typed, animatable custom properties:

@property --gradient-angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}

.rotating-gradient {
background: conic-gradient(from var(--gradient-angle), #6c63ff, #00ff87, #6c63ff);
animation: rotate 4s linear infinite;
}

@keyframes rotate {
to { --gradient-angle: 360deg; }
}