Rethinking UI State: CSS Range Syntax vs Class Toggling

Published: (February 26, 2026 at 12:05 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

The Traditional Pattern: JavaScript Controls Visual State

Imagine a calendar where users select a start and end date. A typical implementation looks like this:

days.forEach(day => {
  const value = Number(day.dataset.day);

  if (value >= start && value 12

Now CSS performs the comparison using the emerging Range Syntax:

.day-now {
  background-color: if(
    style(--day-start <= --day <= --day-end): #8b0000;
    else: rgba(255, 255, 255, 0.05);
  );
}

No loops, no class toggling, no DOM mutation. JavaScript updates state; CSS evaluates presentation.

Live Demo (CSS Range Syntax)

In this version, JavaScript only updates --day-start and --day-end. CSS evaluates the range condition directly. Requires Chrome 142+ (experimental support). Try changing the start and end values in the JS panel and see how the UI responds.

Why This Is Architecturally Interesting

JavaScript

  • handles interaction
  • updates state values

CSS

  • evaluates visual conditions
  • renders based on state

The logic that determines how something looks lives where it belongs – in CSS.

DOM Refactors Become Less Fragile

In the traditional approach, JS might depend on a selector such as:

document.querySelectorAll('.calendar .row .day');

Restructuring the DOM can break that logic. In the CSS‑driven model, JS doesn’t care about structure; it only sets --day-start and --day-end. As long as each day exposes --day, styling works, reducing structural coupling.

Browser Support – And a Practical Fallback

Range Syntax is still emerging and not fully supported in stable browsers. The architectural pattern, however, does not depend on it. Today we can emulate similar logic using clamp() and arithmetic with custom properties:

.day-now {
  --gte-start: clamp(0, calc(var(--day) - var(--day-start) + 1), 1);
  --lte-end:   clamp(0, calc(var(--day-end) - var(--day) + 1), 1);
  --in-range: calc(var(--gte-start) * var(--lte-end));

  background-image: linear-gradient(
    rgba(139, 0, 0, var(--in-range)),
    rgba(139, 0, 0, var(--in-range))
  );
}

What’s happening

  • Values below zero clamp to 0; above one clamp to 1.
  • Multiplication simulates a logical AND.
  • The result controls opacity.

This fallback is more verbose than Range Syntax but fully workable today. Range Syntax mainly improves clarity.

Live Demo (Clamp Fallback)

Same architectural pattern—implemented today using clamp(). JavaScript still only updates state values.

Trade‑Offs and Constraints

This pattern isn’t universally better. Consider:

  • Readability – Some teams may find class toggling clearer than CSS arithmetic.
  • Debugging – Conditional logic inside CSS may be less familiar to developers used to imperative control flow.
  • Large‑scale state – If UI state becomes deeply interdependent or complex, JavaScript may still be the better coordination layer.

The key isn’t “replace JS with CSS.” It’s redefining which layer owns what.

A Broader Direction

Custom properties, container queries, :has(), scroll‑driven animations, advanced attr() – CSS keeps gaining expressive power. Range Syntax fits into that trajectory. It doesn’t eliminate JavaScript, but it reduces how often we need it for visual state management. And that’s worth rethinking.

0 views
Back to Blog

Related posts

Read more »