What We Lost
When you replace a browser primitive with a framework primitive, three things happen:
- You inherit a maintenance burden the platform was already carrying for you.
- You usually lose a feature in the process.
- You gain control over things you probably didn’t need to control.
Let’s walk through what got replaced.
Native <dialog>
Section titled “Native <dialog>”The platform has had a native <dialog> element with full modal support, focus management, backdrop, and Escape-to-close since Chrome 37 and now everywhere. It does what a dialog should do, the way a screen reader expects it to do it.
Most frontend codebases ship a <Modal> built on top of <div> and a portal, with custom focus traps, custom Escape handling, custom backdrop, and custom inert-management for the rest of the page. Each one has slightly different semantics. Each one has a slightly different bug.
You wrote that. You’re maintaining that. The platform offered to do it for you in five lines.
<dialog id="confirm"> <p>Are you sure?</p> <form method="dialog"> <button>Cancel</button> <button value="confirm">Yes</button> </form></dialog>
<script> document.querySelector('button.open').onclick = () => confirm.showModal()</script>Focus enters the dialog. Tab is trapped. Escape closes. The browser inerts the rest of the document. You did not write any of that.
Form-associated controls
Section titled “Form-associated controls”ElementInternals and formAssociated let custom elements participate in <form> natively — submission, validation, :invalid, :valid, setCustomValidity, the works. Your <my-input> can be a real form control with full constraint validation.
Most frontend codebases ship a <TextField> component that re-implements validation through React state, then re-implements the submission through onSubmit, then re-implements label association through htmlFor, then re-implements :invalid styling through className branching.
You wrote that. The platform offered to do it for you.
Focus management
Section titled “Focus management”:focus-visible exists. inert exists. tabindex="-1" exists. popover exists with native focus management. Element.focus({ preventScroll: true }) exists. delegatesFocus exists for shadow DOM.
Most frontend codebases ship custom focus traps, custom focus restoration, custom keyboard listeners that interfere with the browser’s built-in tab order. Each component reinvents what the platform already had.
CSS — the cascade, not the runtime
Section titled “CSS — the cascade, not the runtime”CSS got enormously better in the last five years and most teams haven’t noticed:
@layerfor ordering specificity without!important@containerfor responsive design that responds to parent size, not viewport:has()for parent selectorsoklch()for perceptually uniform colorcolor-mix()for runtime mixing- Native nesting
light-dark()for automatic dark mode support@scopefor component-scoped styles
Most frontend codebases ship CSS-in-JS runtimes. Each one re-implements scoping. Each one re-implements theming. Each one ships a runtime to the browser.
You wrote that. The platform offered to do it for you.
Routing context
Section titled “Routing context”The browser has <a href>, popstate, History API, Navigation API, View Transitions, prerender, prefetch, <link rel="modulepreload">, and now declarative shadow DOM for streaming HTML.
Most frontend codebases ship a router. Each router is incompatible with every other router. Each router has its own lifecycle. Each router has its own opinions about loaders, layouts, and nested routes.
The platform has all of this. It just doesn’t ship a convention layer around it. That’s a real gap. But the gap is “convention,” not “capability.”
Why this matters
Section titled “Why this matters”You can write a perfectly functional app on top of the platform that lost none of these things. Custom elements give you encapsulation. Modules give you cross-cutting capability. The cascade gives you styling. Native semantics give you accessibility for free.
The thing you give up is the framework’s convention layer — and Kitsune is, in part, a small convention layer designed to be used with the platform instead of over it.
You can have the ergonomics. You don’t have to lose the platform to get them.