Skip to content
ClearedGet the next issue
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? .

Subscribe