Protium Perspectives The Principle of Least Power in the Modern Web Blog Post Cover

The Principle of Least Power in the Modern Web

The Principle of Least Power was first defined in Tim Berners-Lee’s Axioms of Web Architecture back in 1998. In a nutshell, it puts forth the argument that the least powerful language should be used for a task that gets the job done. With powerful languages come more freedom, but that freedom brings along some tradeoffs: greater complexity, larger execution costs, and more room for unintended behavior. Declarative languages like HTML and CSS are constrained by design, which makes them easier for browsers, tools, and developers to reason about. That may all sound a bit abstract, so let me give you a tiny history lesson, and then show some examples of how to leverage this principle in the year of our lord 2026.

In 1995 JavaScript (and myself) arrived on this world. The possibilities that came with this new arrival were astronomical (referring to JavaScript now - my arrival has been somewhat less impactful). Over the past three decades, it has grown to dominate the modern web. Over time though, outside of the spotlight, HTML and CSS have been growing in capability, and now much of what used to require JS to work can be done without it.

I know, I know - talk is cheap, show me the code. Fine - here are some prime candidates for replacing imperative JS behavior with declarative HTML and CSS or as I like to say: unscripting.

The Collapsible Panel

Any frontend developer has at some point had to implement a expandable/ collapsible panel. They probably wrote some code that looked something like this:

Quite a bit of code to perform this action. We needed CSS and JS to help us get there. Then in 2013 the W3C introduced a working draft for the <details> and <summary> elements. It took nearly a decade for it to be widely available across all major browsers, but by golly it was worth the wait. The entirety of the code above can now be replaced with this:

Voila! Isn’t that a treat. Besides the absolute win of having to write far less code, you also get keyboard support, accessible state exposure, and screen reader semantics for free. All things you’d have to wire up manually in the JS version.

We’re not done with collapsible panels yet though. Next we’ll look at transitioning the height smoothly. This is simple enough when you know what the expanded height should be, as CSS can handle transitioning from one static height to another. But very often that’s not the case. We might not know how much content will be in the panel, or what viewport the user is on which would affect how tall the panel should be. There are two older methods to deal with this, neither of which are ideal:

  1. The first requires setting a max-height to 0 on the collapsed content, and then a large enough magic number for the max height in the expanded state to cover the content - like max-height: 9999px. This does work, but we’re here for magic, not magic numbers.
  2. The second requires JS to calculate the height of the panel before it could be expanded. That would look something like this:

Quite the dance, just to animate to auto. Fortunately for us though, there is a CSS trick one can use to achieve the same behavior without any JS. Shout out to Nelson Menezes who originally came up with this technique. Here’s how it goes:

You can’t transition height to auto1 - but you CAN transition grid-template-rows from 0fr to 1fr.

The Popover

If you’ve ever had the pleasure of building a tooltip, dropdown, or ‘floating’ UI element of any sort, you’ll know that the JS tax is steep. You’ve got to handle visibility, controlling z-index so it renders above the surrounding content, detecting clicks outside the element to dismiss it, and managing aria attributes so that screen readers know what’s actually going on. That might look something like this:

Even this stripped-down version has a lot of logic to manage, and that’s without even considering focus trapping, escape key handling, or the famed z-index arms race many developers have come to know. Enter the Popover API.

Three attributes, no script. The browser now takes care of toggle behaviour, click-outside dismissal, escape key handling, and accessibility for FREE. It also solves the stacking issue in a fundamentally better way: by displaying the popover in a completely different layer to the rest of the page. The browsers top layer sits above everything else on the page, so it will always render above other content.

The Theme Switch

There are almost as many ways to handle dark/light mode as there are JS frameworks. A common version starts with JavaScript stamping a data-theme attribute onto the document, then saving the result to localStorage. In React, that often grows into a ThemeProvider, a context, a hook, and state that exists mostly to flip a class. Useful, sure, but it is a lot of application logic for something CSS is increasingly capable of handling itself.

Before we reach for an event listener, let’s start with the version that needs no toggle at all. If the goal is to respect the user’s system preference, CSS already has the pieces: color-scheme tells the browser the page supports both themes, and light-dark() lets our tokens respond to whichever scheme is active.

That’s already useful, but users often want to choose a theme for themselves. This is where :has() gets interesting: CSS can read the state of a checkbox and use it to style the document, even when the checkbox and the styled elements are in completely different parts of the DOM.

Checked means dark: the :has() selector reads that state, flips color-scheme, and every light-dark() value on the page responds instantly. No event listeners, no setAttribute, no framework. The only thing missing is memory: once the page reloads, that checkbox goes back to its default state.

Keeping track of a user’s choice across sessions is exactly the kind of stateful, imperative work JavaScript is built for. Here it genuinely earns its place:

CSS handles the visuals, the browser handles the system preference, and JS handles the thing that actually needs it: remembering a choice.

Conclusion

The goal here is not to remove all JS from your project. The goal is to stop reaching for the most powerful tool first. When HTML or CSS can carry the behavior, let them. If they can’t, check whether the web platform already gives you the behavior natively. You ship more semantic markup, less JavaScript, and fewer little piles of state for your team to manage. Users get pages that load faster and behave more predictably; developers get interfaces with fewer moving parts.

That’s the Principle of Least Power in practice: use the least powerful language that gets the job done, and let the browser do the job it was built to do.

Footnotes

  1. You actually can transition to an auto height, thanks to the new interpolate-size property, although support is still too sparse at the time of writing to use in production.

Related Articles