Choosing the Weakest Tool That Works

Nearly 25 years ago, Tim Berners-Lee published a paper titled “Web Architecture from 50,000 feet,” outlining the fundamental design principles of the web. Among the early observations was a concept called the “Rule of Least Power.” The rule is straightforward: when you have multiple languages available to express something, use the least powerful one that can still do the job.

When designing computer systems, one is often faced with a choice between using a more or less powerful language for publishing information, for expressing constraints, or for solving some problem. […] The “Rule of Least Power” suggests choosing the least powerful language suitable for a given purpose.

Applied to front-end development, this means exhausting what HTML can do before reaching for CSS, and exhausting CSS before pulling in JavaScript. This is not a return to table-based layouts or <font> tags—Berners-Lee’s paper also stressed the separation of form and content. Instead, operating lower in the stack tends to produce more robust results.

In the web front-end stack — HTML, CSS, JS, and ARIA — if you can solve a problem with a simpler solution lower in the stack, you should. It’s less fragile, more foolproof, and just works.

Why JavaScript Isn’t Always the Answer

The message hasn’t fully landed. Developers regularly rebuild what the browser already provides, sometimes with serious consequences. One developer described encountering a utility website where the form had been entirely reimplemented in JavaScript, breaking a basic function the browser has handled for decades.

My browser has been perfectly competent at submitting HTML forms for the past 28 years, but for some stupid reason some developer decided to reimplement the form in JavaScript, and now I can’t pay my electricity bill without opening up the dev tools.

Many common problems have platform-native solutions that are often overlooked:

  • Smooth scrolling is achievable with CSS, no script required.
  • API errors should be communicated with proper HTTP status codes, not an HTTP 200 with a JSON error field.
  • Closing a <dialog> can be done with a <form method="dialog">.
  • Lazy-loading images is now a native HTML attribute, broadly supported.
  • Preventing invalid characters can be handled with the right input type and a pattern attribute.
  • Collapsing content sections are what <details> and <summary> are for.

Failing Gracefully Is a Feature

Choosing lower-level technologies also buys resilience. JavaScript fails catastrophically; a single missing script or unexpected argument can take down an entire app. An error like “Cannot read property x of undefined” is all too familiar. HTML and CSS, on the other hand, are forgiving languages—a syntax error in one rule doesn’t stop the rest of the stylesheet from applying.

We tend to ask “How well does it work?”, but what you should really be asking is “How well does it fail?”

Consider the experience of visiting a major commerce site with JavaScript disabled: no product images, no way to place an order. This over-reliance is unnecessary. Product photos can be plain <img> elements; item selection and checkout can be a standard form with a <select> for size and a submit button. And for users without JavaScript, it’s often not an active choice—corporate firewalls or network issues can strip scripts out, leaving users with- nothing but a blank page that could have been simple text and images.

Enhance, Don’t Require

This principle applies to smaller components too. A tabbed interface, commonly implemented with a JavaScript library, can be built astonishingly well with just HTML, CSS, and ARIA. Once that base is solid, JavaScript can layer on improvements. This is progressive enhancement—treating JavaScript as an upgrade rather than a prerequisite.

It extends beyond JavaScript. Certain CSS capabilities may be missing in a given browser. You can provide a baseline experience and then use @supports to detect if a feature is present, enhancing the design only when possible. For browsers that lack a newer feature, you can attempt to fill the gap with a polyfill.

When the Platform Catches Up

The web platform has a history of absorbing solutions that originally required external libraries. jQuery was once a mandatory first step for any project, providing document.querySelectorAll(), Element.classList and more. Now, those are native browser APIs.

That cycle continues. There are native alternatives for many things that have historically been polyfills or third-party libraries:

  • The long-criticized Date() API is being superseded by the new Temporal API.
  • Animation is now handled by the built-in Web Animations API.
  • Styling form controls has become simpler with the CSS accent-color property.
  • Preprocessor-only features like variables are now native CSS Custom Properties.

Some libraries still bridge the gap for developer convenience—Redaxios offers an Axios-compatible interface over fetch, for example—but the trend is clear and consistent as the platform increasingly provides these features natively, without any dependency on middleware.

The web platform is more capable than ever. Embracing that is an act of building a more resilient web—with HTML, CSS, and JavaScript, in the order the platform was designed for it to be used.