Broader Ideas
The design system decides this once, for everyone
The highest-leverage consequence of this module is not in application code. If a design system
ships <Button> rendering a styled div, every consuming team inherits the liability, and
changing it later is a breaking change across dozens of applications.
Questions worth asking of any design system, including one you are buying:
- What element does each primitive actually render? (
asprops make this non-obvious) - Are native behaviours preserved, or reimplemented?
- Is there a behavioural test suite — focus, keyboard, Escape — or only visual regression?
- When a native element gains a capability (
popover, anchor positioning), what is the adoption path?
A design system that wraps native elements gets platform improvements for free. One that replaces them must reimplement each improvement forever. That is the whole argument, and it is decided in week one by one person. Developed in fe-38.
The platform is closing the styling gap
Most "we can't use the native element" objections are styling objections with a shelf life:
| Objection | Platform answer |
|---|---|
| can't style the modal backdrop | dialog::backdrop |
| need light-dismiss without JS state | Popover API |
can't style <select> | appearance: base-select (emerging) |
| can't position anchored UI | CSS Anchor Positioning |
| can't style checkboxes | accent-color, appearance: none on the real input |
| can't reach shadow internals | ::part() |
The strategic consequence for a Principal: custom control inventories are a depreciating asset. Every custom modal, tooltip and dropdown built in 2020 is now more code than the native equivalent, with fewer capabilities. Worth a standing item on a technology radar (fe-50) rather than a one-time migration.
Progressive enhancement is a reliability strategy
Reframed as failure modes rather than ideology, this connects directly to fe-44 (reliability):
| Failure | Native-first | JS-dependent |
|---|---|---|
| bundle fails / CDN blocked | works | inert |
| hydration throws | works | broken, often silently |
| slow network | usable immediately | visible but non-functional |
| CSP or extension blocks a script | works | broken |
"Visible but non-functional" is the most common production complaint about SPAs, and it is a graceful degradation question — the same discipline as circuit breakers and kill switches, applied at the markup layer. A form built on native constraints has a working fallback for free.
Custom elements as the integration boundary
Web Components are the platform's answer to framework-agnostic shared UI, and the trade is real:
Good fit: design-system primitives consumed by multiple frameworks; long-lived widgets that must outlive framework churn; embedded third-party UI needing style isolation.
Poor fit: anything needing SSR without extra machinery (declarative shadow DOM helps, but the ecosystem is uneven); tight framework integration where the framework's own model is better; frequent complex data passing, where attribute/property marshalling becomes friction.
The accessibility caveat this module makes concrete: shadow roots change focus traversal and label
association. delegatesFocus, explicit ::part exposure, and cross-root ARIA (aria-labelledby
across a shadow boundary is not straightforward) are all real costs. Picked up in fe-38 and
fe-41.
<details>, <dialog>, popover: state the browser owns
The deepest idea here is not "use semantic tags" — it is which component owns state.
<details><summary>More</summary>…</details>
There is no isOpen state, no toggle handler, no aria-expanded to keep in sync, no possibility of
the ARIA attribute drifting from the visual state. The browser owns the invariant, so it cannot
decay.
Every ARIA attribute you manage manually is an invariant that must be maintained in every code path
— including error paths, cancelled transitions and interrupted animations. aria-expanded on a div
will eventually disagree with what is on screen. This is the same argument as fe-02's
AbortController finding and the specification's "make invalid states difficult to represent"
heuristic: prefer designs where the invalid state cannot be expressed.
Reaching for <details> when you need disclosure is not a style preference. It is choosing a design
in which the bug class does not exist.