Browser support
@arag has a hard floor of Chrome/Edge 123, Firefox 120, Safari
17.5. That is not a guess: it is the maximum, per engine, of every native CSS
feature the package actually uses — and the single binding constraint is
light-dark().
Chrome/Edge 123+ · Firefox 120+ · Safari 17.5+. Below this, colour tokens do not resolve the way the system expects. Everything else the package relies on shipped considerably earlier in all three engines.
Feature support table
One row per native CSS feature the package depends on, with the version each engine shipped support in, and where the package uses it.
| Feature | Chrome/Edge | Firefox | Safari | Used in |
|---|---|---|---|---|
light-dark() | 123 | 120 | 17.5 | tokens.css — every semantic colour role |
@layer | 99 | 97 | 15.4 | index.css — the whole layer order |
oklch() | 111 | 113 | 15.4 | tokens.css — every ramp step |
color-mix() | 111 | 113 | 16.2 | tokens.css — surface-sunken and derived state tokens |
Nesting with & | 112 | 117 | 17.2 | base.css, docs.css — pseudo-classes and ::backdrop |
text-wrap: balance | 114 | 121 | 17.5 | base.css — heading wrap. Progressive enhancement: degrades to normal wrapping, so it does not gate the floor. |
::backdrop inheriting custom properties | 122 | 120 | 17.4 | base.css — dialog::backdrop |
dvb / vi units | 108 | 101 | 15.4 | docs.css — sidebar sticky max-block-size |
:focus-within | 60 | 52 | 10.1 | card.css, field.css — a focus ring on the whole card or input group. Used instead of :has(:focus-visible), which would raise the floor. |
aspect-ratio | 88 | 89 | 15.0 | button.css, badge.css — square icon-only buttons and count badges. |
overscroll-behavior | 63 | 59 | 16.0 | dialog.css, menu.css, tabs.css, table.css — stops a scroll chaining to the page behind. |
appearance: none | 84 | 80 | 15.4 | field.css — the switch track is a real checkbox with its native rendering removed. |
Logical properties / inset-inline | 87 | 63 | 14.1 | Everywhere. The whole package is written in logical properties. |
accent-color | 93 | 92 | 15.4 | base.css — checkboxes, radios, range inputs |
What shapes the floor
light-dark() sets the floor
Every other feature in the table was available considerably earlier, in every engine.
light-dark() alone pushes the requirement to Chrome/Edge 123, Firefox 120 and
Safari 17.5, and those three numbers are the floor exactly. The only used feature that wants
anything newer is text-wrap: balance on Firefox 121, and because it degrades
silently to normal wrapping it enhances rather than gates.
If the floor ever needs to drop, light-dark() is the one feature to replace, and
the replacement is a prefers-color-scheme media query fork plus a duplicate
token block — exactly the duplication the current single-block design was built to avoid.
:has() and container queries are not used
Both are comfortably within the floor and both are worth reaching for later, but neither
appears anywhere in src/ today, so neither constrains anything. They are called
out because it is easy to assume a modern CSS package uses them and to raise the floor on
their account: :has() did not reach Firefox until 121. Verify before assuming —
grep -rn ':has(' src/ and grep -rn '@container' src/ both return
nothing.
::backdrop custom-property inheritance
base.css styles dialog::backdrop with
var(--color-overlay). That declaration only resolves correctly if
::backdrop inherits custom properties from the element that generates it —
behaviour that changed and only settled across all four engines in early 2024. MDN's
::backdrop page still carries a stale sentence claiming it does not inherit.
It does; this was verified in a browser, not taken on faith from the docs.
Deliberate exclusions
Two features were considered and rejected. Neither is a gap — both were tested and ruled out for specific reasons.
scrollbar-color / scrollbar-width — rejected for three
reasons. First, scrollbar-width is not an inherited property (only
scrollbar-color is), so setting it once at the root only styles the root
scroller and nothing nested inside it. Second, scrollbar-color is
inherited, but Chromium disables the legacy ::-webkit-scrollbar-*
pseudo-elements on any element whose computed scrollbar-color is
non-auto — declaring it at the root would kill the webkit fallback
document-wide. Third, scrollbar-color only reached Safari in 26.2, missing
this floor entirely. Scrollbars are therefore left unstyled, which also suits the kiosk
build, where touch targets want fatter scrollbars than a desktop portal.
text-wrap: pretty — used, but as pure progressive enhancement only. It has
never shipped in Firefox, and Safari's implementation uses a different, whole-paragraph
algorithm from Chrome's, so the engines that do support it disagree with each other on the
result. Unsupported, it degrades silently to normal wrapping — nothing breaks — so do not
rely on it for layout.
Graceful degradation
Below the floor, the two load-bearing features fail very differently. An unsupported
@layer block is dropped by the parser in its entirety, taking the whole
stylesheet with it — this is the one failure mode that is catastrophic. An unsupported
light-dark() value, by contrast, makes that single declaration invalid at
computed-value time; the property falls back to its inherited or initial value, so a page
loses colour on that property but nothing collapses.
If the project ever needs to serve a legacy fallback sheet, gate it with a plain
@supports check rather than a build step or a browser sniff:
@supports not (color: light-dark(#000, #fff)) {
/* legacy fallback rules */
}Testing
The two real device targets are a Pixel 8 Pro and an iPad Air (5th generation). The demo sheet and these docs are checked at 375px. Reduced motion is testable either from the OS setting or from the devtools emulation panel — no separate tooling required.