Rethinking UI State: CSS Range Syntax vs Class Toggling
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 to1. - 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.