Skip to main content

Baseline 2026: 4 APIs That Changed How I Code

"Baseline 2026" in JetBrains Mono on a Catppuccin Mocha gradient background

Baseline 2026 brought a wave of new interoperable web platform APIs. Not the kind of big where everything changes overnight. The kind where APIs that have been experimental for years finally ship in every browser. Baseline means you can use them in production without a polyfill, and the 2026 cohort has a handful that actually changed what I reach for when I start a new project. Not “technically you could,” but “I stopped installing this.”

The Navigation API: one navigate event instead of three different APIs

The Navigation API became Baseline Newly available in early 2026, supported in Chrome, Edge, Firefox 147, and Safari 26.2. It replaces the fragmented mess that was History API routing: pushState for navigation, popstate for back/forward, and manual link click handlers to prevent full-page reloads. Three separate APIs you had to wire together yourself, or reach for a router library that did it for you.

Here is what client-side routing looked like before:

// Before: three separate concerns, manually wired
window.addEventListener("popstate", (e) => {
  renderRoute(window.location.pathname);
});

document.addEventListener("click", (e) => {
  const link = e.target.closest("a[data-route]");
  if (link) {
    e.preventDefault();
    history.pushState(null, "", link.href);
    renderRoute(new URL(link.href).pathname);
  }
});

// Initial render
renderRoute(window.location.pathname);

And here is the Navigation API equivalent:

// After: one centralized event
navigation.addEventListener("navigate", (e) => {
  const url = new URL(e.destination.url);
  e.intercept({
    handler() {
      renderRoute(url.pathname);
    },
  });
});

One event. One interception point. The intercept() call tells the browser “I’ll handle this navigation, wait for me,” which also means the browser can show a loading indicator natively. You get scroll restoration (e.scroll()), form data access (e.formData), and entry traversal (navigation.traverseTo(key)) without wiring any of it yourself.

This does not kill React Router or TanStack Router. Both have open discussions about adopting the Navigation API as a backend: React Router’s and TanStack Router’s. They would sit on top of it rather than reimplement the History API workaround they currently maintain. The frameworks add value (nested routes, data loading, error boundaries) that the raw Navigation API does not. What the API kills is the need to pull in a router just to avoid full-page reloads on a content site. If your routing needs are modest (a blog, a docs site, a dashboard with a few views), the platform now has you covered.

The caveat: Safari 26.2 is missing precommitHandler support, which limits some advanced interception patterns. And Ian Hickson, the spec author, famously called pushState() his “favourite mistake,” so the replacement had a low bar to clear.

Container style queries: theming without JS class toggling

Container style queries for custom properties became Baseline Newly available in May 2026, with Firefox 151 shipping the last piece and Chrome 148 adding name-only container queries the same month.

This one solves a problem I have written far too much JavaScript for: switching visual variants based on context. A card that renders differently in a sidebar vs. a hero section. A theme toggle that propagates across the component tree. All done with CSS classes, JS observers, or both.

Here is the old pattern:

// Before: JS class toggling for a card variant
function Card({ variant = "default" }) {
  return (
    <div className={`card card--${variant}`}>
      <h3>{title}</h3>
      <p>{excerpt}</p>
    </div>
  );
}
.card--featured { /* special styles */ }
.card--compact { /* compact styles */ }

And with container style queries:

.sidebar {
  container-name: sidebar;
  /* no container-type needed for style queries */
}

.card {
  /* default styles */
}

@container sidebar style(--variant: featured) {
  .card {
    /* featured layout */
  }
}

@container sidebar style(--compact: true) {
  .card {
    /* compact layout */
  }
}

The container declares its context via CSS custom properties, and descendant elements react. No JS. No class chains. The component does not need to know where it is rendered; the container owns that relationship.

The limitation worth mentioning: style queries currently only support custom properties. The spec allows querying any CSS property, but no browser ships that yet. So you query style(--theme: dark), not style(background-color: black). For the use case it solves (theming, layout variants, contextual styling): custom properties are exactly what you want. For arbitrary CSS property introspection, we are still waiting.

:open: state tracking you never write

The :open pseudo-class became Baseline Newly available in May 2026 when Safari 26.5 shipped it. It matches any element with an open semantic state: <dialog>, <details>, <select>, and <input type="color"> / <input type="date"> with their pickers open.

Before :open, styling an open disclosure meant either an attribute selector or JS:

/* Before: attribute selector, limited */
details[open] > summary {
  border-radius: 4px 4px 0 0;
}
// Before: manual state tracking for a dialog
dialog.addEventListener("toggle", () => {
  document.body.classList.toggle("dialog-open", dialog.open);
});

With :open:

details:open > summary {
  border-radius: 4px 4px 0 0;
}

/* Style the page when any dialog is open */
html:has(dialog:open) {
  overflow: hidden;
}

/* Style a label when its associated select is open */
label:has(select:open) {
  color: var(--accent);
}

The :has() combo is where this gets useful. html:has(dialog:open) is a body scroll lock in one line of CSS. label:has(select:open) is a parent style change driven by a child’s state: something that used to require a JS mutation observer or a framework binding. The :open selector tracks semantic state, not visibility: a <details> that is semantically open but visually hidden still matches :open. That distinction matters for things like display: none inside an open disclosure.

This replaces the details[open] attribute selector (which only works for <details>) and the pattern of manually toggling CSS classes on <body> or parent elements when modals or pickers open. One pseudo-class, all openable elements, no JS.

Math.sumPrecise(): the “wait, reduce is wrong?” moment

Math.sumPrecise() became Baseline Newly available in April 2026, part of ES2026 after reaching TC39 Stage 4 in July 2025.

The problem it solves is floating-point summation error:

const numbers = [1e20, 0.1, -1e20];
numbers.reduce((a, b) => a + b, 0);  // 0
Math.sumPrecise(numbers);              // 0.1

The naive reduce approach loses precision because floating-point addition is not associative. When you add a very small number to a very large one, the small number disappears. Math.sumPrecise() uses the Shewchuk algorithm (or an equivalent) to produce the maximally correct answer: the result you would get with arbitrary-precision arithmetic rounded back to a float.

What it does NOT fix: 0.1 + 0.2. That is a floating-point representation problem, not a summation problem. Math.sumPrecise([0.1, 0.2]) still returns 0.30000000000000004 because the literals 0.1 and 0.2 are inexact before sumPrecise ever sees them.

The API is deliberately not variadic (Math.sumPrecise(1, 2, 3) throws). It takes an iterable to avoid stack overflows on large datasets. Empty iterables return -0 (the floating-point additive identity). Non-number elements throw a TypeError, unlike Math.max which silently coerces. TypeScript does not ship type definitions for it yet (issue #63427), so you may need a manual declaration depending on your TypeScript 7 version.

This is not the API that will headline anyone’s year-end retrospective. But it fixes a real footgun: the kind of bug that passes code review because nobody spots the summation error in a 50-item array spread across a few chained .map() and .filter() calls. I wrote about JavaScript gotchas before, and floating-point behavior is the category that surprises experienced developers the most. Having a built-in that handles the common case correctly is a quiet win.

What “Baseline Newly available” actually means for Baseline 2026

All four of these APIs are Baseline Newly available, not Widely available. That distinction matters:

  • Newly available: All core browsers (Chrome, Edge, Firefox, and Safari across desktop and mobile) support the feature. You can use it in production.
  • Widely available: The feature has been interoperable for 30 months. You can use it without thinking about browser support.

These four are firmly in the “use it today” category. All core browsers ship them. If you still support older Safari or Firefox versions, feature detection is straightforward: "navigation" in window, CSS.supports("selector(:open)"), @supports (container-name: x) in CSS. None of these require a polyfill to degrade gracefully; they just need a fallback path.

The 30-month clock to Widely availability means these become “don’t even think about it” territory between mid-2028 and late 2028.

The web platform is eating libraries from the edges in

None of these four APIs kills a major framework. You will still reach for React Router if your app has nested layouts, data loaders, and route-level error boundaries. You will still use a CSS-in-JS library if your design system demands it. What they kill is the dependency you pulled in for one thing: the router for a content site, the classnames utility for variant switching, the state tracking for modal-open classes.

The gap between “what the platform gives you” and “what you need a library for” keeps narrowing. The HTTP QUERY method landed earlier this year for safe reads with a body. Now we have routing, variant-driven styling, state selectors, and precise math, all Baseline, all in every browser.

We are not at zero dependencies. But each Baseline release shrinks the surface area that justifies pulling in someone else’s code. And 2026 has been a good year for that.

Found this useful?

I write about web development, JavaScript, and open source. Follow along for new posts, plus the smaller stuff that never makes it onto the blog.