Native CSS First: Patterns That Replaced My JavaScript

Native CSS First: Patterns That Replaced My JavaScript

6 November 2026

I have spent years writing JavaScript to do jobs the browser can now do on its own. Tooltips, modals, dropdowns, disclosure widgets, focus management. Every one of them used to need a script, a library, or a framework component. These days I start with native CSS first, and I only reach for JavaScript when the browser genuinely cannot do the job.

This is not a purist stance. It is a maintenance stance. Every line of JavaScript I do not ship is a line I never have to debug, update, or explain to the next developer. The browser already handles the edge cases I used to hand-roll, and it does it faster than my code ever did.

What we cover

  • The rule I start from before I write any JavaScript
  • Anchor positioning, and where it replaces tooltip scripts
  • The popover API, and what it does to dropdowns
  • Native dialog modals, and what they get right for free
  • The focus fix that made me take WCAG seriously
  • What I still reach for JavaScript for

The rule I start from

Native CSS first means exactly what it says. Before I write a script, I ask whether CSS can do it. If the answer is yes, the script does not get written. If the answer is almost, I check whether the gap is a real user need or just a habit.

Three things fall out of that rule. Page load gets lighter, because the browser is not downloading and parsing code for work it already knows how to do. Accessibility gets better, because native elements come with keyboard behavior and screen reader support built in. And the codebase gets smaller, because a CSS rule is shorter than the component it replaces.

At Hounder we made this the default approach: native CSS first, avoid JavaScript when it is not needed, optimize page load, and handle AIO, SEO, and WCAG at scaffold time rather than bolting them on later.

Anchor positioning

Anchor positioning lets you tether one element to another in pure CSS. A tooltip can sit next to its trigger without a single line of positioning math. A dropdown can follow its button when the page scrolls. The browser does the work that used to live in a positioning library.

The pattern is simple. Mark the trigger as an anchor, then position the floating element relative to it with anchor functions. The browser keeps them together, and it flips the floating element to the other side when there is no room.

The honest limit: this is newer CSS, so check support before you rely on it for a client site. On a personal project or a modern-browser audience, it is ready. On a site that still serves older browsers, keep the JavaScript fallback for now and use anchor positioning as the progressive enhancement.

The popover API

The popover API turns any element into a layer that floats above the page, with built-in show and hide behavior, light dismiss, and focus handling. You open it with an attribute on a button. You close it by pressing Escape or clicking outside. No script required.

What it replaces for me is the dropdown and disclosure pattern. Menus, info panels, and small overlays all become a button plus a popover element. The behavior users expect is already there, because the browser owns it.

Pair it with anchor positioning and you get a tooltip or menu that is positioned, dismissible, and keyboard friendly with almost no code at all. That combination is the one I reach for most.

Native dialog modals

The dialog element is the modal I have wanted for a decade. One element gives you a modal with a backdrop, Escape to close, and focus trapping handled by the browser. The showModal method opens it. The close method closes it. That is the whole API.

Compare that with the modal code I used to write: a portal, a focus trap, an Escape key listener, a scroll lock, a backdrop click handler, and a return-focus step for when it closed. Every one of those was a place for a bug to live. The native dialog has all of it built in, and it has been tested by far more people than my hand-rolled version ever was.

Style the backdrop with the ::backdrop pseudo-element, style the dialog like any other box, and you are done. For a confirm dialog, a form in a modal, or an image viewer, this is now my first stop.

The focus fix

The pattern that made me take this seriously was a focus indicator fix. A site I worked on had focus outlines that were invisible or inconsistent, which meant keyboard users could not tell where they were on the page. The fix was a few lines of CSS using :focus-visible, so the outline shows for keyboard navigation and stays out of the way for mouse users.

That is a WCAG win that costs almost nothing, and it is the kind of thing JavaScript never needed to touch. It also set the pattern for how I think about accessibility now. If the browser gives me the behavior for free, taking it is not a shortcut. It is the correct choice.

What I still reach for JavaScript for

Native CSS first is not native CSS only. Some jobs still need a script. Anything that fetches data, anything with complex state, and anything that needs to react to something other than user interaction still belongs in JavaScript.

The difference is the starting point. I used to default to JavaScript and look for a CSS escape hatch. Now I default to CSS and let JavaScript earn its place. Most UI patterns do not earn it.

Final thoughts

The browser caught up to a lot of the work we used to do by hand. Anchor positioning, popover, and dialog are not experiments anymore. They are the boring, reliable way to build common patterns, and boring is exactly what I want from UI code.

The short version: before you write the script, check whether the browser already does the job. Most of the time now, it does.

Tags

/blog/2026-11/native-css-first-patterns-replaced-my-javascript austin.amento.dev