English
Browser support
These components solve styling and layering with what browsers ship, not with JavaScript patches. The upside is small code, good accessibility, and no extra dependency. The cost is a floor: older browsers lose pieces.
The floor first.
| Browser | Minimum version |
|---|---|
| Chrome, Edge | 123 |
| Safari (including iOS) | 17.5 |
| Firefox | 120 |
light-dark() sets that floor, because nearly every color variable is written as light-dark(light value, dark value). An older browser does not understand the function, drops the whole declaration, and the page is left without its colors.
What we actually use
Every row below appears in packages/core/styles; none of it is copied from the upstream checklist.
| Feature | Where it is used | Chrome | Safari | Firefox |
|---|---|---|---|---|
light-dark() | Every theme color variable | 123 | 17.5 | 120 |
@layer | First line of every stylesheet, declaring the five astryx.* layers | 99 | 15.4 | 97 |
:has() | Form state, lists, chat, and tabs, where a parent style depends on a child | 105 | 15.4 | 121 |
color-mix() | Hover tints, sliders, code blocks, and other derived colors | 111 | 16.2 | 113 |
@property | Registers the custom property behind the press animation so it can animate as a number | 85 | 16.4 | 128 |
| Popover API | Menus, tooltips, the command palette, and the toast viewport use native popover and popovertarget | 114 | 17 | 125 |
<dialog> and showModal() | Dialogs, bottom sheets, and the lightbox | 37 | 15.4 | 98 |
@starting-style | Entry animation start values for overlays, sheets, and toasts | 117 | 17.5 | 129 |
Container queries @container | Pagination, steppers, and chat adapting to their own width | 105 | 16 | 110 |
text-wrap: balance / pretty | Balanced headings, and body text that avoids a lone last word | 114 | 17.5 | 121 |
:dir() | Direction checks in a few places, such as the slider | 120 | 16.4 | 49 |
| Logical properties | Padding, margin, radius, and offsets written as padding-inline-start and friends | 87 | 14.1 | 66 |
@property and @starting-style are "missing is fine": without them the press animation and the entry animation simply do not play, and the interface still works. light-dark() is what actually sets the floor.
The versions follow MDN and caniuse as of when this page was written. When in doubt, search caniuse for the feature name.
Below the floor
- Colors drop out.
light-dark()is not understood,--astryx-color-*resolves to nothing, and text may fall back to default black. - Overlays open, but may not sit next to the trigger. Without the native popover API, menus and tooltips still open; CSS fallback positioning is less exact.
- Dialogs may not open at all. There is no fallback path when
showModal()is unavailable. - Animations stop. Entry and press motion disappear; content still shows.
Nothing goes to a blank page. The visuals just stop matching the design, which is why older browsers are not supported.
Supporting older browsers anyway
We do not ship a fallback build for old browsers, and there is no bundled polyfill. Three options.
1. If your audience is old, hold the floor. Internal tools can usually push a browser update. That is the cheapest path.
2. Detect with @supports and supply your own colors. Check for light-dark() at the app entry and write fixed light values when it is missing:
css
@supports not (color: light-dark(#fff, #000)) {
:root {
--astryx-color-text-primary: #000000;
--astryx-color-background-surface: #ffffff;
}
}That restores colors only. Overlay positioning and animation do not improve.
3. Needing full function on an old browser means changing code. That covers a popover fallback, a @starting-style fallback, or rewriting the affected overlays in your project. That work is outside this library.
Compatibility details already handled
- iOS zooms the page on focus. On iOS, focusing an input whose font size is below 16px zooms the page. Every input component lifts its font size to at least
1remon touch devices, using@supports (-webkit-touch-callout: none)so the rule only applies on iOS. - Reduced motion. One global
prefers-reduced-motion: reducerule flattens the distance, scale, and opacity deltas of entry and exit motion, and components add their own opt-outs. With the system setting on, animation does not play. - Scrollbars. Scrollbar styling lives in one file,
scrollbars.css, implemented with theoverlayscrollbarsruntime dependency. Colors are derived from the text color withcolor-mix(), so dark mode follows.
Check your own browser
Paste this in the console to see which pieces your browser lacks.
js
const checks = {
'light-dark()': CSS.supports('color', 'light-dark(#fff, #000)'),
'@layer': CSS.supports('at-rule', '@layer a, b;'),
':has()': CSS.supports('selector(:has(*))'),
'color-mix()': CSS.supports('color', 'color-mix(in oklab, red, blue)'),
'Popover API': typeof HTMLElement.prototype.showPopover === 'function',
'@container': CSS.supports('container-type', 'inline-size'),
}
console.table(checks)Any false maps to a row in the table above. When light-dark() is false, page colors are effectively unusable.