The Web Platform Has Caught Up

Between 2022 and 2026, the web platform crossed a capability threshold. Native CSS and HTML features now provide the functionality that historically justified adopting a CSS preprocessor, a utility framework, a CSS-in-JS library, or a JavaScript UI component system. No single feature is transformative. The cumulative effect is that the problems requiring these tools in 2020 can be solved with the platform itself in 2026.

This section catalogues what changed and why it matters for the architectural choice described in Why Hypermedia-Driven Architecture. The HDA model depends on the platform being capable enough that server-rendered HTML, plain CSS, and minimal JavaScript can deliver a production-quality experience. That dependency is now met.

The Interop Project

Cross-browser inconsistency was a primary driver of framework and preprocessor adoption. Developers reached for jQuery, Sass, Autoprefixer, and eventually React because writing to the platform directly meant writing to four different platforms with different bugs. The Interop Project has largely eliminated this rationale.

Interop is a joint initiative of Apple, Google, Igalia, Microsoft, and Mozilla, running annually since 2021 (initially as “Compat 2021”). Each year, the participants agree on a set of web platform features, write shared test suites via the Web Platform Tests project, and publicly track each browser engine’s pass rate. The Interop dashboard reports a single “interop score”: the percentage of tests that pass in all browsers simultaneously.

The scores tell the story:

YearStarting interop scoreEnd-of-year (stable)End-of-year (experimental)
Compat 202164-69%>90%
Interop 2022~49%83%~97%
Interop 2023~48%75%89%
Interop 202446%95%99%
Interop 202529%97%99%

The low starting scores each year reflect the selection of new focus areas, not regression. Each iteration targets harder, more recent features. That Interop 2025 started at 29% and finished at 97% in stable releases means the browser vendors are converging on new features within a single calendar year.

WebKit’s review of Interop 2025 described the result directly: “Every browser engine invested heavily, and the lines converge at the top. That convergence is what makes the Interop project so valuable, the shared progress that means you can write code once and trust that it works everywhere.”

Interop 2026 launched in February 2026 with 20 focus areas including cross-document view transitions, scroll-driven animation timelines, and continued anchor positioning alignment. The initiative is now in its fifth consecutive year with no signs of winding down.

The practical consequence: if you write CSS and HTML to the current specifications, it works in Chrome, Firefox, Safari, and Edge. The “works in my browser but not yours” problem that drove an entire generation of tooling adoption is, for the features that matter most, solved.

CSS features that replace frameworks

Eight CSS features, all shipping between 2022 and 2026, collectively address the problems that justified Sass, Less, PostCSS, Tailwind, CSS-in-JS, and JavaScript positioning libraries.

Cascade Layers (@layer)

Cascade Layers provide explicit control over cascade priority, independent of selector specificity or source order. All major browsers shipped support within five weeks of each other in early 2022. @layer reached Baseline Widely Available in September 2024.

@layer reset, base, components, utilities;

@layer reset {
  * { margin: 0; box-sizing: border-box; }
}

@layer components {
  .card { padding: 1rem; border: 1px solid #ddd; }
}

@layer utilities {
  .hidden { display: none; }
}

Styles in later-declared layers always win over earlier layers, regardless of specificity. This replaces the specificity arms race that led to !important abuse, strict BEM naming conventions, and CSS-in-JS libraries whose primary value proposition was specificity isolation. Styles outside any @layer have the highest priority, which allows third-party CSS to be layered below application styles without modification.

CSS Nesting

CSS Nesting reached Baseline Newly Available in December 2023, when Chrome 120 and Safari 17.2 shipped the relaxed syntax (Firefox 117 had shipped in August 2023).

.card {
  padding: 1rem;

  h2 {
    font-size: 1.25rem;
  }

  &:hover {
    box-shadow: 0 2px 8px rgb(0 0 0 / 0.1);
  }

  @media (width >= 768px) {
    padding: 2rem;
  }
}

This is the feature that eliminated the most common reason for using Sass or Less. The relaxed nesting syntax (no & required before element selectors) matches what preprocessor users expect. Media queries and other at-rules can nest directly inside selectors.

Container Queries

Container Queries reached Baseline Widely Available in August 2025. Firefox 110 was the last browser to ship, completing Baseline in February 2023.

.card-container {
  container-type: inline-size;
}

@container (inline-size > 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
  }
}

Media queries respond to the viewport. Container queries respond to the size of the containing element. This makes components genuinely reusable: a card component that switches from stacked to horizontal layout based on its container width, not the window width. Previously, achieving this required JavaScript ResizeObserver workarounds or abandoning the idea entirely.

Size container queries are the Baseline part. Style container queries (@container style(...)) remain Chromium-only as of early 2026.

The :has() selector

:has() reached Baseline Newly Available in December 2023, when Firefox 121 shipped (Safari had led in March 2022, Chrome followed in August 2022).

/* Style a card differently when it contains an image */
.card:has(img) {
  grid-template-rows: 200px 1fr;
}

/* Style a form group when its input is invalid */
.form-group:has(:invalid) {
  border-color: var(--color-error);
}

/* Style a section when it has no content */
section:has(> :only-child) {
  padding: 0;
}

:has() is the long-requested “parent selector,” though it is more general than that name implies. It selects an element based on its descendants, siblings, or any relational condition expressible as a selector. Before :has(), selecting a parent based on its children required JavaScript DOM traversal. Entire categories of conditional styling that needed classList.toggle() or framework-level reactivity can now be expressed in CSS alone.

@scope

@scope reached Baseline Newly Available in December 2025, when Firefox 146 shipped (Chrome 118 had led in October 2023, Safari 17.4 followed in March 2024).

@scope (.card) to (.card-footer) {
  p { margin-bottom: 0.5rem; }
  a { color: var(--card-link-color); }
}

@scope provides proximity-based style scoping with both an upper bound (the scope root) and an optional lower bound (the scope limit), creating a “donut scope” that prevents styles from leaking into nested sub-components. This addresses the problem that CSS Modules, BEM, and Shadow DOM each solved partially: keeping component styles from colliding. Unlike Shadow DOM, @scope does not create hard encapsulation boundaries, so styles remain inspectable and overridable when needed.

The cumulative effect

No single feature here replaces a framework. The replacement is structural.

In 2020, a developer building a component library needed: a preprocessor for nesting and variables (Sass), a naming convention or tooling for specificity management (BEM or CSS Modules), JavaScript for responsive component behaviour (ResizeObserver hacks), JavaScript for parent-based conditional styling (no :has()), and either strict discipline or a CSS-in-JS library to prevent style collisions.

In 2026, native CSS handles all of this. Nesting and custom properties replace the preprocessor. @layer replaces specificity management tooling. Container queries replace JavaScript resize detection. :has() replaces JavaScript conditional styling. @scope replaces CSS-in-JS scoping. The developer writes CSS, and it works across browsers.

HTML features that replace JavaScript UI primitives

The historical justification for React’s component model arose partly because HTML lacked native primitives for modals, tooltips, menus, and rich selects. Three of those gaps are now closed at Baseline. Two more are closing.

The <dialog> element

<dialog> reached Baseline Widely Available in approximately September 2024. Firefox 98 and Safari 15.4 completed cross-browser support in March 2022.

<dialog id="confirm-dialog">
  <h2>Delete this item?</h2>
  <p>This action cannot be undone.</p>
  <form method="dialog">
    <button value="cancel">Cancel</button>
    <button value="confirm">Delete</button>
  </form>
</dialog>

A modal <dialog> (opened via showModal()) provides focus trapping, top-layer rendering, backdrop styling via ::backdrop, the Escape key to close, and <form method="dialog"> for declarative close actions. These are the behaviours that every custom modal library (Bootstrap Modal, React Modal, a11y-dialog) reimplements in JavaScript. The native element provides them with correct accessibility semantics, including the dialog ARIA role and proper focus restoration on close, out of the box.

The Popover API

The Popover API reached Baseline Newly Available in January 2025 (Safari 18.3 resolved a light-dismiss bug on iOS that had delayed the designation).

<button popovertarget="menu">Options</button>
<div id="menu" popover>
  <a href="/settings">Settings</a>
  <a href="/profile">Profile</a>
  <a href="/logout">Log out</a>
</div>

The popover attribute gives any element top-layer rendering, light dismiss (click outside or press Escape to close), and automatic accessibility wiring. popover="auto" provides light dismiss; popover="manual" requires explicit close. This replaces Tippy.js, Bootstrap Popovers, and the custom JavaScript that every dropdown menu previously required.

The popover="hint" variant (for hover-triggered tooltips) is an Interop 2026 focus area and not yet Baseline.

Invoker Commands

Invoker Commands (command and commandfor attributes) reached Baseline Newly Available in early 2026, with Safari 26.2 completing cross-browser support after Chrome 135 (April 2025) and Firefox 144.

<button commandfor="my-dialog" command="show-modal">Open</button>
<dialog id="my-dialog">
  <p>Dialog content</p>
  <button commandfor="my-dialog" command="close">Close</button>
</dialog>

Invoker Commands connect a button to a target element declaratively: commandfor names the target, command specifies the action. Built-in commands include show-modal, close, and request-close for dialogs, and toggle-popover, show-popover, hide-popover for popovers. No JavaScript required for these interactions.

Combined with <dialog> and the Popover API, Invoker Commands eliminate the last bit of JavaScript glue that modals and popovers previously required. A dialog can be opened, populated, and closed entirely through HTML attributes and server-rendered content, which is exactly what HDA needs.

Gaps still closing

Two features listed in the outline remain Chromium-only as of February 2026:

Customizable <select> (appearance: base-select). Chrome 134+ and Edge 134+ ship full CSS styling of <select> elements, including custom option rendering via exposed pseudo-elements (::picker(select), selectedoption). Firefox and Safari are implementing but have not shipped to stable. This feature replaces React Select, Select2, and the entire category of custom dropdown libraries that exist because native <select> has been unstyled. The opt-in (appearance: base-select) means browsers without support simply show the default <select>, making it safe to adopt as progressive enhancement.

Speculation Rules API. Chrome 121+ supports declarative prefetch and prerender rules via <script type="speculationrules">. WordPress and Shopify have deployed it at scale. Firefox’s standards position is positive for prefetch but neutral on prerender; Safari has published no position. Non-supporting browsers ignore the <script> block entirely, so it can be deployed today without harm. For HDA applications, speculation rules offer the multi-page navigation speed that SPA prefetching provides, without any client-side routing framework.

Both features work as progressive enhancement: they improve the experience in supporting browsers without breaking others.

Progressive enhancement as the architectural default

The features above share a property: they degrade gracefully. A <dialog> without JavaScript still renders its content. A popover without support becomes a static element. A <select> without appearance: base-select falls back to the native control. This is not accidental. The web platform is designed around progressive enhancement.

Native HTML elements carry built-in ARIA semantics, focus management, and keyboard handling. A <dialog> opened with showModal() traps focus, responds to Escape, announces itself to screen readers, and restores focus to the triggering element on close. A <button> with commandfor and command attributes communicates its relationship to the target element through the accessibility tree. These behaviours are defined by the specification and implemented by the browser.

SPA component libraries must reimplement all of this. A React modal component needs explicit focus-trap logic, an Escape key handler, ARIA attributes, a portal to render in the correct DOM position, and focus restoration on unmount. Libraries like Radix UI and Headless UI exist specifically because implementing accessible interactive components in React is difficult. The native elements provide the same behaviours correctly by default.

In HDA, progressive enhancement is the structural default. The baseline is server-rendered HTML with standard links and forms. htmx attributes enhance but are not required; a form with hx-post and hx-swap still submits normally via the browser’s native form handling if htmx fails to load. In SPA frameworks, progressive enhancement is opt-in and, under deadline pressure, frequently abandoned.

No-build JavaScript

ES Modules (<script type="module">) have been supported in all major browsers since 2018 and are Baseline Widely Available. Import Maps reached Baseline Widely Available in approximately September 2025, with Safari 16.4 completing cross-browser support in March 2023.

Together, they enable npm-style bare specifier imports in the browser without npm, Node.js, or a bundler:

<script type="importmap">
{
  "imports": {
    "htmx": "/static/js/htmx.min.js",
    "alpinejs": "/static/js/alpine.min.js"
  }
}
</script>
<script type="module">
  import 'htmx';
</script>

Import maps resolve bare specifiers (import 'htmx') to URLs, the same job that webpack, Rollup, and esbuild perform during a build step. With import maps, the browser does this resolution at runtime. No bundler needed.

The trade-offs are real. There is no tree-shaking: unused code in imported modules ships to the client. No TypeScript compilation: types are stripped only if a build step runs. No code splitting: the browser loads entire modules rather than optimised chunks. For applications with large client-side dependency graphs, these costs matter.

For HDA applications, they do not. The client-side dependency count is typically small: htmx (14 KB gzipped), perhaps a date formatting library, perhaps a small charting library for a dashboard page. The total client-side JavaScript in an HDA application is measured in tens of kilobytes, not megabytes. HTTP/2 and HTTP/3 multiplexing further reduce the cost of serving a handful of small modules individually.

Some practitioners retain a build step for minification, but this is an optional optimisation, not an architectural requirement. The htmx project itself argues explicitly against build steps, distributing as a single file that can be included with a <script> tag. The no-build approach is not a compromise for HDA. It is the natural fit.

The supply chain security argument

The architectural choice to avoid npm is not only a simplicity argument. It is a security argument, grounded in the structural properties of the npm dependency graph and the empirical record of supply chain attacks against it.

The dependency graph problem

Zimmermann, Staicu, Tenny, and Pradel (Small World with High Risks: A Study of Security Threats in the npm Ecosystem, USENIX Security 2019) analysed npm’s dependency graph as of April 2018 and found small-world network properties: just 20 maintainer accounts could reach more than half of the entire ecosystem through transitive dependencies. Installing an average npm package implicitly trusts approximately 80 other packages and 39 maintainers. 391 highly influential maintainers each affected more than 10,000 packages.

A comparative study by Decan, Mens, and Grosjean (An Empirical Comparison of Dependency Network Evolution in Seven Software Packaging Ecosystems, Empirical Software Engineering, 2019) found npm had the highest transitive dependency counts among seven ecosystems. A more recent study by Biernat et al. (How Deep Does Your Dependency Tree Go?, December 2025) across ten ecosystems found that Maven now shows the highest mean amplification ratio (24.70x transitive-to-direct), with npm at 4.32x. npm is not the worst offender across all ecosystems, but it remains structurally exposed: 12% of npm projects exceed a 10x amplification ratio, and the absolute number of affected projects is enormous given npm’s scale.

The empirical record

The structural risk is not theoretical. Supply chain attacks against npm are recurring and escalating in sophistication.

event-stream (November 2018). A new maintainer, given publish access through social engineering, added a dependency on flatmap-stream containing encrypted malicious code targeting the Copay Bitcoin wallet. The package had approximately 2 million weekly downloads. The malicious code was live for over two months before a computer science student noticed it.

polyfill.io (June 2024). The polyfill.io CDN domain was acquired by a new owner in February 2024. Four months later, the CDN began serving modified JavaScript that redirected mobile users to scam sites. Over 380,000 websites were embedding scripts from the compromised domain. Andrew Betts, the original creator, had warned users when the sale occurred. Most did not act.

chalk/debug (September 2025). A phishing attack compromised the npm credentials of a maintainer of chalk, debug, and 16 other packages. The malicious versions contained code to hijack cryptocurrency transactions in browsers. The 18 affected packages accounted for over 2.6 billion combined weekly downloads. The malicious versions were live for approximately two hours.

These incidents share a structural cause: the npm ecosystem’s deep transitive dependency graphs mean that compromising a single package or maintainer account can reach thousands or millions of downstream projects. The risk scales with the number of dependencies.

The HDA alternative

An HDA application with vendored htmx eliminates this entire attack surface. htmx is 14 KB minified and gzipped, has zero dependencies, and is distributed as a single JavaScript file. There is no npm install step, no node_modules directory, no transitive dependency graph, and no exposure to registry-level supply chain attacks.

This is not an incremental improvement. A typical React application created with Vite installs approximately 270 packages, and projects using Create React App (now deprecated) routinely exceeded 1,500. Each package is a node in the dependency graph that the Zimmermann findings describe. Reducing that graph from hundreds of nodes to zero is a categorical change in supply chain risk profile.

The comparison is worth stating plainly. One architecture requires you to trust hundreds of packages, maintained by strangers, with update cadences you do not control, delivered through a registry that is a recurring target of supply chain attacks. The other architecture requires you to trust one 14 KB file that you can vendor, audit, and pin.

What this means for HDA

The web platform’s capability expansion between 2022 and 2026 is the material condition that makes hypermedia-driven architecture practical for production applications. The HDA model depends on three platform properties:

  1. CSS is sufficient for production UI. Nesting, container queries, cascade layers, :has(), and @scope collectively provide the capabilities that previously required a preprocessor, a utility framework, or CSS-in-JS.

  2. HTML provides interactive primitives. <dialog>, the Popover API, and Invoker Commands cover modals, tooltips, dropdowns, and declarative element interaction without JavaScript component libraries.

  3. The browser is a capable module system. ES Modules and Import Maps enable dependency management without a build tool, and the small dependency footprint of HDA applications makes the trade-offs (no tree-shaking, no code splitting) irrelevant.

The Interop Project ensures these features work consistently across browsers. The backward-compatibility guarantee described in the previous section ensures they will continue to work. And the elimination of the npm dependency graph provides a supply chain security posture that no framework-dependent architecture can match.

The web platform was not always adequate for building rich applications without frameworks. It is now.