---
title: Browser support
description: The platform features Timeless requires, and what happens where
  they are missing.
editUrl: true
head: []
template: doc
sidebar:
  order: 2
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

Timeless targets Baseline 2025 browsers. The floor is the newest requirement in each engine, and two
features set it between them — `:state()` in Chrome and Firefox, `light-dark()` in Safari: **Chrome
and Edge 125, Safari 17.5, and Firefox 126.** There are no polyfills, so support tracks the platform
features below.

Versions are taken from [MDN browser-compat-data](https://github.com/mdn/browser-compat-data) and
record the first release of each feature, not a support promise. Check
[Baseline](https://web.dev/baseline) for current data.

## Required features

All Baseline, and all older than the floor above except the last two rows, which set it. A browser
missing any of these cannot render Timeless correctly.

| Feature                  | Chrome / Edge | Safari | Firefox | Used by                                                   |
| ------------------------ | ------------- | ------ | ------- | --------------------------------------------------------- |
| Custom elements          | 54            | 10.1   | 63      | Every enhanced component                                  |
| `<dialog>`               | 37            | 15.4   | 98      | Dialog, Sheet                                             |
| Cascade layers           | 99            | 15.4   | 97      | Every stylesheet                                          |
| `:has()`                 | 105           | 15.4   | 121     | Field, Choice, and Range layout                           |
| `color-mix()`            | 111           | 16.2   | 113     | Derived accent and danger fills                           |
| Popover API              | 114           | 17     | 125     | Popover, Hover Card, Menu, Context Menu, Select, Combobox |
| `light-dark()`           | 123           | 17.5   | 120     | Tokens and themed surfaces                                |
| `:state()` custom states | 125           | 17.4   | 126     | Sheet drag, Toast exit, Color Picker, and Copy Button     |

## Progressive features

All three are Baseline as well, but they landed much later than the floor, so a browser that is
current-but-not-latest may still be missing them. Each degrades to something usable.

| Feature                                | Chrome / Edge | Safari | Firefox | Without it                                                                                                                                                                     |
| -------------------------------------- | ------------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `@starting-style` and `allow-discrete` | 117           | 17.5   | 129     | Overlays appear and disappear without the enter and exit transition.                                                                                                           |
| CSS anchor positioning                 | 125           | 26     | 147     | Overlay surfaces stay in normal flow next to their trigger instead of being anchored to it. They still open, close, dismiss, and trap focus correctly.                         |
| Invoker Commands                       | 135           | 26.2   | 144     | A dialog or modal sheet trigger needs JavaScript to open its panel, because the component falls back to its click listener. Popovers are unaffected: they use `popovertarget`. |

One requirement is not a version at all. [Copy Button](/docs/components/copy-button/) needs
`navigator.clipboard`, which every target engine has shipped for years but which is undefined
outside a secure context and refuses the write without transient user activation. So the same build
works on `https://` and on `localhost` and does nothing over plain `http://` — a staging box, or a
LAN address during development. The component reports that as `unsupported` on `ui-copy` rather than
failing silently, and a trigger authored `hidden` is revealed only once the API is there.

Anchor positioning is the widest gap in practice: Firefox only shipped it in 147, well above the
Firefox 126 floor. It is a layout refinement rather than a functional requirement, so a popover in
an older Firefox is fully usable, just not tethered to its trigger.

Invoker Commands are the one place where the markup you author decides which path runs. Put
`command="show-modal"` and `commandfor` on a Dialog or modal Sheet trigger and the browser opens the
panel itself, before the bundle loads; add `command="close"` to a close button and the platform
closes it and copies the button's `value` into `returnValue`. Timeless reads those attributes and
stands down — it never writes them, since an attribute added during enhancement would be back to
needing JavaScript. Where the API is missing, the component's click listener does the same work, so
one set of markup is correct everywhere.

Two limits come from the platform rather than from Timeless. An invoker can only name an `id` that
exists in the markup, so an invoked `<dialog>` needs an explicit one rather than a generated one.
And there is no built-in command for `dialog.show()`, so a **non-modal** Sheet cannot be opened
declaratively; leave `command` off that trigger and the click listener opens it. Its close buttons
still work declaratively.

### Wide-gamut color

`oklch()`, `oklab()`, `lch()`, `lab()`, `hwb()`, and `color(display-p3 …)` are Baseline everywhere
Timeless runs — Chrome 111, Safari 15, Firefox 113 — so this is not a browser-support question. What
varies is the **display**: an out-of-gamut color is rendered clamped on an sRGB screen. Color Picker
parses and edits the value faithfully regardless, flags when a color falls outside the current
gamut, and offers an explicit clamp control so you can decide rather than guess.

## Unsupported capability

When a component needs a platform capability the browser lacks, enhancement stops and the authored
markup is left exactly as written. Nothing is hidden, no public diagnostic attribute is added, and
no polyfill is loaded. A Popover in a browser without the Popover API keeps its native trigger and
content; the surface simply does not become a top-layer popover.

This is why every component page documents markup that already works before JavaScript runs.

Two components are the exception, and both are platform gaps rather than support ones.
[Context Menu](/docs/components/context-menu/) has no declarative way to open a surface at pointer
coordinates, so with scripting off the browser shows its own context menu and the authored `ui-menu`
stays hidden. Nothing reachable only from a context menu is reachable at all, in any browser, until
the bundle loads. [Copy Button](/docs/components/copy-button/) has no declarative way to reach the
clipboard: `navigator.clipboard` is undefined outside a secure context and refuses the write without
a user gesture, and `document.execCommand('copy')` is deprecated. Author its trigger `hidden` and
registration reveals it once the API is there, so no dead control renders.
[Sheet](/docs/components/sheet/)'s swipe-to-dismiss is a gentler version of the same shape: a
gesture is an addition, so Escape and the close control stay, and the sheet is fully operable
without a pointer.

## What is tested

Automated coverage runs on every change:

- Chromium for the website and the component catalog.
- Firefox and WebKit for platform contracts: form association, native validation, dialog and popover
  behavior, and no-JavaScript rendering.
- axe WCAG 2.2 A and AA rules, plus reflow and text-spacing checks, on every documented route.

These are regression evidence, not a conformance claim. Keyboard, zoom, forced-colors, and
screen-reader review with real assistive technology still belong to your release process.