DEV Community

Franklin
Franklin

Posted on

CSS Doesn't Need More Power — It Needs Better Boundaries

Also available in Español

The Problem

A team hits a styling problem that CSS won't cooperate with. Something needs to change based on a click, a scroll position, a piece of application state. So they reach for more power.

A heavier selector. An !important. Sometimes CSS gets skipped entirely — element.style.display = 'none', set directly from a click handler, because it felt more reliable than trusting the cascade to do what was asked.

None of the last three problems in this series were solved by giving CSS more capability. Resets weren't broken by CSS being too weak to normalize browsers — they were broken by opinion and normalization sharing a boundary that should never have been shared. Specificity wasn't broken by selectors being too weak — it was broken by having no way to declare intent independent of selector weight. Entropy wasn't caused by CSS lacking some feature — it was caused by nothing marking the boundary between what's safe to remove and what isn't.

Every one of those was a boundary problem. Not one of them was a power problem.

Why the Problem Exists

CSS earned its reputation for unpredictability honestly — that's the last three articles. And the natural response to something unpredictable is to route around it. JavaScript is right there. Imperative, powerful, willing to set exactly the value you want on exactly the element you want, the instant you want it.

But every line of presentation logic that moves into JavaScript is a line the cascade can no longer see or reason about. It's now a value competing with real CSS rules, in ways specificity was never built to arbitrate and layers, once they're introduced, won't be able to see either. The unpredictability doesn't get fixed. It gets relocated somewhere even less structured — spread across click handlers and component lifecycles instead of contained in one stylesheet where at least the problem was visible.

The First Principle

A boundary is not a limitation. It's a guarantee.

CSS already owns four things outright, with real browser-granted authority: presentation, transition, layout, rendering. That's not a small list — the browser hands CSS genuine control over how nearly everything on a page looks and moves. The undersold fact is that this is already enough. The instinct to reach past CSS for more power isn't usually because CSS lacks capability inside its actual job. It's because the edges of that job were never explicitly drawn.

CSS shouldn't own application state. It shouldn't encode business logic. The moment either of those creep in — a class name that secretly represents a data value, a selector chosen based on what the backend returned rather than what the element is — the guarantee breaks. And it doesn't just break for the person who wrote the crossing. It breaks for everyone who touches that code afterward, because now presentation can no longer be trusted to live in exactly one place.

Demonstrating the Principle

One toggle, done two ways.

Set directly from a click handler:

button.addEventListener('click', () => {
  panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
});
Enter fullscreen mode Exit fullscreen mode

Or the same interaction, with the boundary intact — JavaScript changes state, CSS owns the resulting presentation:

button.addEventListener('click', () => {
  panel.classList.toggle('is-open');
});
Enter fullscreen mode Exit fullscreen mode
.panel {
  display: none;
}

.panel.is-open {
  display: block;
}
Enter fullscreen mode Exit fullscreen mode

Same outcome, from the user's side. But in the second version, presentation stays fully legible from the stylesheet alone. Nobody has to go hunting through JavaScript to understand how .panel behaves.

quell as the Case Study

quell draws this boundary explicitly, and treats it as non-negotiable: CSS owns presentation, JavaScript owns behavior, HTML owns structure. Nothing in the system is permitted to quietly cross that line — a rule enforced by convention as firmly as it's enforced by architecture.

The Broader Lesson

This closes the first four pieces of this series, and it's worth naming the shape underneath all of them.

Resets: something claiming neutrality that wasn't neutral. Specificity: something calculating that should have been declaring. Entropy: something growing that should have been governable. And now this: something reaching for power when what it actually needed was a line nobody had drawn yet.

Four different symptoms. One underlying fix, named plainly for the first time here: boundaries, not power. The rest of this series exists to show exactly how the browser lets you build boundaries for real — starting with the mechanism that gives the cascade an explicit ordering axis of its own.

Top comments (0)