Skip to content
ClearedGet the next issue

A monthly letter about HTML and CSSNext issue · Monday, October 5

Cleared to use. Cleared to delete.

The web platform grows. Your codebase doesn’t have to.

Every month I check what changed in HTML and CSS, browser by browser: which new arrivals are cleared to use, and which fallbacks you can finally clear out. I keep the few that actually matter, and give each one a verdict: Cleared, Hold, or Skip. I publish the result in one email you can read in five minutes.

Or just read this month’s issue below

The difference

Baseline tells you a feature shipped. It doesn’t tell you whether to bother.

webstatus.dev (opens in a new tab) will tell you that the lh unit passed 30 months of browser support in May. It won’t tell you that Safari still resolves 1lh against the wrong value whenever the line-height is inherited, which is how line-height usually arrives. Nothing errors: the box just comes out the wrong height! So lh is a Hold, 30 months or not, and the issue says the magic number stays. Google’s team publishes the date. It doesn’t publish the verdict. But I can…

  • Cleared

    Use it now. Once it is safe everywhere, the fallback goes.

  • Hold

    Real, but not yet, and here is the specific reason.

  • Skip

    Works fine. Not worth your afternoon.

How a verdict gets made

Issue 08

Cleared 08 · August 2026 · Cleared to use. Cleared to delete. Five minutes.

Delete your declarative shadow DOM polyfill

Published September 14, 2026 · covers August 2026 · 1 Cleared·2 Hold

Two features became newly available in August, both CSS, and both are Holds. One because Safari and Firefox are behind Chrome on it. The other because the label says every browser has shipped it, and Chrome has not. One feature finished its 30 months of support, Declarative shadow DOM, and it retires a script you may still be loading on every page.

01Cleared

Declarative shadow DOM

Safe everywhere as of August 20, 2026

A web component keeps its internal markup and styles in a shadow root, and until recently the only way to create one was from JavaScript, by calling attachShadow(). Declarative shadow DOM lets you write the shadow root in the HTML instead. Put a <template shadowrootmode="open"> inside an element, and the browser turns it into that element’s shadow root while it parses the page. A server can send a component fully rendered, and it looks right before any script has loaded.

Delete: the script that finds every <template shadowrootmode> and attaches its shadow root by hand. Keep it only for components that are HTML only, with no JavaScript to render them.

JS · before after
/* the polyfill, on every page */
document.querySelectorAll('template[shadowrootmode]').forEach((t) => {
  const mode = t.getAttribute('shadowrootmode');
  t.parentNode.attachShadow({ mode }).append(t.content);
  t.remove();
});

/* the parser already did this */

One more thing to check before deleting. The browser only creates these shadow roots when it parses HTML as the page loads. If your JavaScript builds the same markup later and inserts it with innerHTML, nothing happens: the <template> stays a plain template and no shadow root is created. This is deliberate. The spec disables declarative shadow roots for innerHTML, and the test suite confirms it for every HTML element. So check your code for any innerHTML that inserts markup containing shadowrootmode. For those, either switch to setHTMLUnsafe(), a newer method that does create the shadow roots, or keep the polyfill for that one call.

Every test that matters for this deletion passes in all five current stable runs. The few misses are a newer attribute Safari has not shipped and two document.open() edge cases.

HTML-only components are the one case to keep the polyfill for because they break completely in browsers without this feature. The <template> is ignored and the component’s internal markup never appears.

02Hold

sibling-index() and sibling-count()

Not yet

Two new CSS functions. sibling-index() gives an element’s position among its siblings, starting at 1, and sibling-count() gives how many siblings there are. Both return plain numbers you can use in calc(). If all you want is a staggered animation on a list that does not change, this works today. Anything beyond that, wait.

CSS
/* Works in every current run */
li { animation-delay: calc(sibling-index() * 80ms); }

/* Fails in Safari 26.6: the index inside a gradient */
li { background: linear-gradient(blue calc(10px * sibling-index()), yellow); }

/* Fails in Firefox 155 and Safari 26.6: the index inside @keyframes
   stops updating when a sibling is removed */
@keyframes rise { from { z-index: sibling-index(); } }

Firefox 154 shipped the last piece on August 18, and the simple uses work everywhere: the index or the count inside calc(), on the element itself, on ::before, on the root. The trouble starts when the index is read somewhere other than a plain declaration. Safari 26.6 passes only three of the suite’s 22 tests whole. It fails when the index is used in a gradient, in a container query, or inside a shadow tree. Both Safari 26.6 and Firefox 155 fail when the index is used inside @keyframes and the list then changes: remove an element before the animated one, and the animation keeps the old position number instead of updating. Outside keyframes, the same removal updates correctly in every browser.

03Hold

Media element pseudo-classes

Not yet

Seven CSS pseudo-classes that match a <video> or <audio> element by what it is doing: :playing, :paused, :seeking, :buffering, :stalled, :muted and :volume-locked. If you have built a custom player, you probably listen for the play and pause events and toggle a class like is-playing so the CSS can swap the button. These pseudo-classes replace the need for that listener, but keep it for now. Chrome has not shipped this feature, whatever the label says.

CSS
/* The deletion waiting here */
.player.is-playing .play-button { display: none; }

/* Blocked by Chrome, for now */
.player:has(video:playing) .play-button { display: none; }

Baseline says Chrome 152 shipped these on August 25, which is what made the feature newly available on August 28. That date comes from a single entry in the browser compatibility data, and it is wrong. Chrome’s own status tracker lists the feature as proposed, with a shipping target of Chrome 155. Every stable Chrome run since 152 fails all six checks in the test suite: a video whose outline should turn green when it plays never changes, and :playing, :seeking and :muted never match through :has(). Edge fails the same way. Firefox 155 and Safari 26.6 pass everything, and so does Chrome’s pre-release channel, so the implementation exists. It is just not in the browser most of your visitors are running.

That was August: one polyfill to delete, one new feature to wait on, and one feature that Baseline says is ready but Chrome has not shipped.

Deleted something thanks to this? Got a correction? .

What’s next

I already know what November brings

A feature becomes widely available exactly 30 months after the last browser ships it. No committee, no vote, just a date. So I can already tell you that five features cross that line in November 2026, and what each of the twelve months ahead holds. What I can’t tell you yet is which of them are worth acting on, and that’s what the newsletter is for.

See the twelve-month calendar

Who writes this

One person, one opinion

I’m Yann. I run minimal design (opens in a new tab), and I’ve been building for the web for over twenty years. I also maintain mCSS (opens in a new tab), an open-source CSS framework. I started Cleared to break a loop I kept repeating: spot something clever on CodePen, look it up on caniuse, shelve it for another year. I like new toys as much as anyone, but what I need to know is which ones are ready for client work.

More about Cleared and why it exists

Subscribe