Skip to content
ClearedGet the next issue
Issue 09

Cleared 09 · September 2026 · Cleared to use. Cleared to delete. Five minutes.

Delete your text-stroke hack

Published October 5, 2026 · covers September 2026 · 2 Cleared·3 Hold

Safari 27 shipped on the 14th and made more features newly available in one day than the rest of the month did. Most of them are JavaScript features though. On the CSS front, it’s a mixed bag: light-dark() image support is ready, but scroll anchoring (which Chrome and Firefox have done for years) is not, and neither is the new alpha() function. Firefox completed progress() but Chrome holds it back. The deletion this month is paint-order, which retires the duplicate text hack to draw a stroke behind a headline.

01Cleared

paint-order

Safe everywhere as of September 22, 2026

Text fill is painted first, then the stroke, on top. A stroke is centered on the outline of the letter, so half of it covers the fill: a 6px stroke is 3px into the text and 3px outside. Using paint-order: stroke paints the stroke first and the fill over it. The fill covers the inner 3px, so the letter keeps its full shape and what you see is a 3px outline. The stroke itself comes from -webkit-text-stroke. (The prefix is part of the property’s name in every browser. No unprefixed form exists.) SVG has had the property for years, and Chrome 123 was the last browser to extend it to HTML text, in March 2024, 30 months before this September.

Delete: the second copy of the text that draws the stroke behind the real one, usually a pseudo-element.

CSS · before after
/* the text twice, with the stroke on the copy behind */
.title { position: relative; z-index: 0; }
.title::before {
  content: attr(data-text);
  position: absolute; inset: 0; z-index: -1;
  -webkit-text-stroke: 6px white;
}

.title { -webkit-text-stroke: 6px white; paint-order: stroke; }

Failure is graceful: a browser without the property paints the stroke on top, and the outline looks thinner. Firefox’s summary score on this feature is low, but none of what it fails is relevant to stroked HTML text. The stroke sits behind the fill in Firefox too.

02Hold

Scroll anchoring

Not yet

When something above the part of the page you are reading changes size, an image loading for instance, the content under your eyes jumps. Scroll anchoring is how browsers manage this issue: they pick an element in view as the anchor, and when content above it grows or shrinks, they move the scroll position by the same amount. Chrome has done this since 2017 and Firefox since 2019. Safari 27 is the last to join, and overflow-anchor: none switches it off.

Baseline’s score for Safari is 1 but it measures the wrong thing: the tests that check the property’s syntax and the opt-out pass, but the tests of the anchoring itself are where Safari 27 falls short. When text above rewraps, Safari does not adjust, and inside a scrolling element rather than the page, it misplaces the anchor when content above it shrinks. So keep the code that restores the scroll position after you insert content above the fold. And if you have a custom scroller in your code, put overflow-anchor: none on it so the browser leaves it alone. Chrome and Firefox already needed that. Safari 27 onward does too.

CSS
/* Works in Safari 27: a block above the viewport grows.
   Fails in Safari 27: the text above rewraps, or a scrolling
   div loses content above the anchor. */

/* Where you manage a scroller's position yourself */
.log { overflow-anchor: none; }
03Hold

progress()

Not yet

progress(value, start, end) returns how far a value sits between two others, as a plain number from 0 to 1. It replaces the ratio you compute by hand in calc(), with one difference: the result is clamped between 0 and 1 unless you add the no-clamp keyword.

Firefox 155 completed support on September 1. Chrome is the blocker. Version 154 accepts the plain form and little else: it rejects no-clamp, which Firefox 155 and Safari 27 both accept; it rejects a percentage mixed with lengths, progress(50%, 0px, 100px); and when start equals end and the value sits above them, it returns 1 where the spec says 0.

CSS
/* The ratio by hand. It can go below 0 or above 1 */
opacity: calc((var(--value) - var(--min)) / (var(--max) - var(--min)));

/* The same ratio, clamped to 0 to 1 unless you say otherwise */
opacity: progress(var(--value), var(--min), var(--max));

/* Chrome 154 ignores both of these */
opacity: progress(no-clamp 150, 0, 100);
margin-left: calc(progress(50%, 0px, 100px) * 10px);
04Cleared

light-dark() image values

New this month, September 14, 2026

light-dark() takes two values and uses the first in a light color scheme and the second in a dark one. Until now it took only colors. It now works with images too. Firefox has had this since April and Chrome since June. Safari 27 completed the set.

Delete: the per-theme override rule that swaps an image.

CSS · before after
/* one rule per theme */
.logo { background-image: url(light.svg); }
.dark .logo { background-image: url(dark.svg); }

/* one declaration. (The first line is the fallback) */
.logo {
  background-image: url(light.svg);
  background-image: light-dark(url(light.svg), url(dark.svg));
}

Every current browser passes the tests. Make sure you keep the fallback for earlier browsers.

05Hold

alpha()

Not yet

alpha(from <color> / <alpha>) takes a color and returns it with a different opacity and nothing else changed. It is the short form of relative color syntax: alpha(from var(--accent) / 50%) instead of rgb(from var(--accent) r g b / 50%). Chrome shipped it in August, Firefox on September 1, and Safari 27 completed the set on September 14.

All three implementations are a few weeks old, and past the plain case they differ. For alpha(from red / 50%) every browser produces the same color. Beyond that, Safari and Chrome fail a good share of the tests, mostly on how the result reads back from JavaScript, and Safari turns an alpha of none into zero. And the draft is still moving. In the current editor’s draft the alpha part is optional, so alpha(from red) is valid, which Chrome and Safari accept and Firefox and the tests reject. Wait a release or two. The long form does the same job today.

CSS
/* The long form you may already write */
color: rgb(from var(--accent) r g b / 50%);

/* The short form. The same color in every current run */
color: alpha(from var(--accent) / 50%);

/* Safari 27 makes this fully transparent */
color: alpha(from var(--accent) / none);

What didn’t make the cut

  • white-space-collapse (opens in a new tab)WidelyNothing to delete: the white-space shorthand already sets it, and the one value it adds on its own (preserve-spaces) is Firefox-only.
  • autocorrect (opens in a new tab)NewlyNothing to do: the attribute already worked in Chrome. Version 153 only made it readable from JavaScript.
  • ariaNotify(), asynchronously iterable streams, top-level await, WebAssembly promise integrationNewlyJavaScript, outside the scope.
  • AbortSignal.any(), Array grouping, checkVisibility(), Promise.withResolvers(), transferable ArrayBuffer, WebAssembly extended constant expressionsWidelyJavaScript, outside the scope.

That was September: one deletion, one declaration to start using, and three features that the label calls ready and the tests do not.

Deleted something thanks to this? Got a correction? .

Subscribe