⏱️ Reading time: 15 min

An HTML attribute and a CSS pseudo-class have retired much of the JavaScript libraries you used in recent years for modals, dropdown menus, and page transition animations. These are native browser APIs: <dialog>, the popover attribute, the :has() selector, and the View Transitions API already solve patterns that used to require adding an npm dependency.

📑 En este artículo
  1. TL;DR
  2. What Are Native Browser APIs?
  3. Why It Matters to Stop Reinventing the Wheel
  4. Why So Many Developers Prefer Building Their Own Version
  5. How Each API Works in Detail
    1. <dialog>: The Modal You No Longer Code by Hand
    2. popover: Menus, Tooltips, and Panels Without JavaScript
    3. :has(): Selecting a Parent Based on What It Contains
    4. View Transitions API: Animating DOM Changes and Navigation
  6. Practical Examples and How to Get Started
    1. How to Test the Native APIs on Your Machine
  7. Real-World Use Cases
  8. Common Mistakes and Best Practices
  9. Comparison: Native API or Library
  10. Going Deeper: How Native APIs Work Under the Hood
  11. Frequently Asked Questions
    1. Does the web platform completely replace React or Vue?
    2. Does the popover attribute work without writing JavaScript?
    3. Does :has() affect page performance?
    4. Does the View Transitions API work in all browsers?
    5. What are native browser APIs, in one sentence?
    6. When does it still make sense to use a JS library instead of the web platform?
  12. References

Using the platform doesn’t mean abandoning libraries, but choosing first what the browser already solves. This article covers what each API does, what code it replaces, and in which cases a library is still the better choice, with the official documentation as a source.

TL;DR

  • Four native APIs (dialog, popover, :has(), View Transitions) replace typical JS library patterns.
  • <dialog> handles focus, the Esc key, and a semi-transparent backdrop without any modal library.
  • The popover attribute coordinates opening, outside clicks, and stacking order without extra JavaScript.
  • :has() selects a parent element based on its content, something that used to require JS.
  • The View Transitions API animates DOM changes and full navigations with a single CSS block.

What Are Native Browser APIs?

Native browser APIs are the interfaces that Chromium, WebKit, and Gecko expose in HTML, CSS, and JavaScript without relying on an external library. They cover everything from accessible dialogs to transition animations, and they solve a common interface pattern with standard markup instead of thousands of lines of third-party code.

For years the web platform lagged behind the ecosystem growing on top of it. jQuery handled complex selectors before querySelectorAll supported them natively, and polyfills covered gaps while old browsers were still around. That gap has nearly disappeared, but the habit of looking at npm first remains intact.

Why It Matters to Stop Reinventing the Wheel

Every npm dependency has a cost: bytes that the browser downloads, parses, and runs before showing anything useful. Native browser functions don’t add that weight because they already live compiled into the rendering engine, optimized by the same team that maintains the rest of the browser.

Accessibility is the second argument. The <dialog> element traps focus and responds to the Esc key without the developer writing that logic by hand. An average modal library reimplements exactly that, with accessibility nuances that vary from project to project.

The third argument is long-term maintenance. A native API doesn’t break in the next major version of a library, nor does it disappear when its maintainer abandons the repository. It lives in a public specification, and several engines implement it independently.

None of these three arguments is absolute. If your team already depends on a mature, battle-tested library with no plans of abandonment, migrating just to use the platform may not justify the effort. Judgment matters more than the rule.

Why So Many Developers Prefer Building Their Own Version

The essay Why don’t more developers “use the platform”?, by Nolan Lawson, asks the question in reverse. If using the platform is so obvious, why do so many people need to be told repeatedly? The first reason is historical: during the Internet Explorer era, browsers lagged behind the ecosystem growing on top of them, and building your own solution was the reasonable choice.

The second reason is search habit. A developer used to looking for React components on npm keeps looking there even when the problem has a native solution; nobody searching for “sticky positioning” finds a package that says “use position: sticky and stop overcomplicating things.” The third is documentation. For years, help on native drag and drop was scattered across blogs and Stack Overflow, while libraries like Dragula offered a tidy site with ready-to-copy examples. MDN has closed much of that gap, but the reflex of preferring a well-written README over the spec is still alive.

The fourth reason has nothing wrong with it: building something yourself is more fun for a certain type of developer, and that exercise teaches how the platform works under the hood. Many of today’s native API authors started out writing polyfills for gaps the browser didn’t cover yet. The point isn’t to stop experimenting, but to know when that exercise is already solved by the browser and when it isn’t yet.

How Each API Works in Detail

The four APIs solve different problems, but they share the same philosophy: move the repetitive part of an interface pattern to the browser and leave the developer only the part specific to their product.

flowchart TD
    A["I need a UI pattern"] --> B{"Does a native API exist?"}
    B -->|"Yes"| C["Check support on MDN"]
    C --> D{"Does it cover the use case?"}
    D -->|"Yes"| E["Use the native API"]
    D -->|"No"| F["Evaluate a JS library"]
    B -->|"No"| F

<dialog>: The Modal You No Longer Code by Hand

The <dialog> element represents a native modal or non-modal browser window. Called with .showModal(), the browser renders it in the top layer, a layer above everything else, blocks interaction with the rest of the page, traps focus inside the dialog, and paints a semi-transparent backdrop accessible via the ::backdrop pseudo-element.

<dialog id="confirmar">
  <p>Confirm the action?</p>
  <form method="dialog">
    <button value="cancel">Cancel</button>
    <button value="confirm">Confirm</button>
  </form>
</dialog>
<script>
  const dialogo = document.getElementById('confirmar');
  document.getElementById('abrir').addEventListener('click', () => dialogo.showModal());
  dialogo.addEventListener('close', () => console.log('Result:', dialogo.returnValue));
</script>

Clicking “Confirm”, the form with method="dialog" closes the dialog and copies the pressed button’s value into returnValue. The console prints exactly Result: confirm, without a single line of logic to close the modal or read which button was pressed.

The semi-transparent backdrop is also customizable. A block like #confirmar::backdrop { background: rgba(0,0,0,.6); } changes the opacity without touching JavaScript, because ::backdrop is a standard pseudo-element, not a div the developer has to create and position.

sequenceDiagram
    participant U as User
    participant D as Dialog
    U->>D: click open button
    D->>D: showModal()
    Note over D: focus trapped, backdrop active
    U->>D: Esc or click button
    D-->>U: close event with returnValue

popover: Menus, Tooltips, and Panels Without JavaScript

The popover attribute turns any element into a panel that opens and closes with light-dismiss: an outside click, the Esc key, or opening another popover closes it automatically. It connects to a trigger button with the popovertarget attribute, without writing a single addEventListener.

<button popovertarget="menu-usuario">My account</button>
<div id="menu-usuario" popover>
  <a href="/perfil">Profile</a>
  <a href="/salir">Log out</a>
</div>

Opening and closing the menu already works without JavaScript. To confirm its state from the console, document.getElementById('menu-usuario').matches(':popover-open') is enough, which returns true while the panel is visible and false as soon as it closes.

The popover attribute is part of standard HTML, not a framework. Foto de Zulfugar Karimov en Unsplash

:has(): Selecting a Parent Based on What It Contains

The :has() selector lets CSS choose an element based on its descendants, something that for two decades only JavaScript could achieve with a querySelectorAll and a listener per field.

.campo:has(input:invalid) {
  border-color: #e11d48;
  background: #fef2f2;
}

That block highlights the entire card of a form in red as soon as the inner input becomes invalid, without listening for the invalid event or toggling classes by hand. The browser re-evaluates the selector every time the input’s state changes, in real time.

💡 Tip: :has() is also useful for styling a different container if it has an image inside (.card:has(img)) or a form based on how many fields are checked, without touching a single line of JavaScript.

View Transitions API: Animating DOM Changes and Navigation

The View Transitions API takes a snapshot of the DOM’s previous state, applies the changes, and animates the difference with CSS pseudo-elements, instead of the developer calculating positions and writing keyframes by hand.

document.getElementById('cambiar-tema').addEventListener('click', () => {
  if (!document.startViewTransition) {
    aplicarTema();
    return;
  }
  document.startViewTransition(() => aplicarTema());
});
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.4s;
}

Without the document.startViewTransition check, the theme still changes in browsers without support, just without the cross-fade animation. With support, the theme change goes from an instant flicker to a 400-millisecond fade defined entirely in CSS.

⚠️ Watch out: transitions between full documents still don’t have the same level of support as transitions within a single page. Always wrap the call in the support check.
flowchart LR
    A["startViewTransition(callback)"] --> B["Capture old snapshot"]
    B --> C["Run the callback and change the DOM"]
    C --> D["Capture new snapshot"]
    D --> E["Animate with CSS pseudo-elements"]

Practical Examples and How to Get Started

The four APIs combine in the same project without conflict. A form can open a confirmation <dialog>, show a popover with contextual help, highlight invalid fields with :has(), and animate the transition between steps with the View Transitions API, all without installing anything.

Consider a simple checkout flow. An “Edit shipping” button opens a popover with the address form, without a single listener to close the panel on an outside click. If any field becomes invalid, :has() highlights the entire section in red. On confirmation, a <dialog> asks for final confirmation before charging, and if the site moves from one step to another by reloading the full page, the View Transitions API animates that navigation instead of showing a blank flicker. None of the four pieces depends on an external library.

How to Test the Native APIs on Your Machine

No package is needed: an updated browser is enough, plus, to avoid the restrictions of opening a file directly with file://, a minimal local server.

  1. Save the <dialog> and popover code from above into an index.html file.
  2. From the terminal, inside that folder, run npx serve . (the same command works on Windows, macOS, and Linux because it runs on Node.js; on Windows you can also use python -m http.server if you already have Python installed).
  3. Open http://localhost:3000 in the browser.
  4. Open the DevTools console and paste the following script to confirm what your browser supports.
const soporte = {
  popover: Object.prototype.hasOwnProperty.call(HTMLElement.prototype, 'popover'),
  has: CSS.supports('selector(:has(a))'),
  viewTransitions: 'startViewTransition' in document,
};
console.log(soporte);

In an updated Chromium-based browser, the console prints {popover: true, has: true, viewTransitions: true}. If any property comes out false, that’s exactly the point where you need a fallback plan for that particular browser.

Real-World Use Cases

  • Destructive confirmations: deleting an account or a repository with <dialog>, taking advantage of trapped focus so Tab doesn’t escape to the page behind it.
  • Navigation menus and command palettes: popover solves the visual stacking and closing on outside click, the same problem that outside-click detection libraries used to solve.
  • Forms with live validation: :has() lets you highlight the entire section of a long form as soon as a field becomes invalid, without a forms framework.
  • Theme or layout changes: View Transitions animates the switch from light to dark mode, or from a list view to a card view, with a single CSS block.
  • Multi-page sites: the View Transitions API for full documents lets you animate navigation between pages without turning the site into a single-page application.

Common Mistakes and Best Practices

  • Using .show() instead of .showModal(): .show() opens the dialog without a backdrop or trapped focus, losing exactly what makes the element useful.
  • Forgetting that popovertarget needs to match the id: if the id changes dynamically, the button stops opening the panel without any visible console error.
  • Overusing :has() with overly general selectors: a selector like body:has(*) forces the CSS engine to check entire trees on every repaint. Always scope it to a specific container.
  • Assuming universal View Transitions support: without the document.startViewTransition check, the site breaks in browsers that don’t implement it yet.
  • Using popover=”manual” without handling closing: manual mode disables automatic light-dismiss, so if you don’t program an explicit close button, the panel stays open forever.
  • Ignoring the default accessibility role: a popover isn’t automatically an accessible menu for screen readers; if the content is a navigation menu, add role="menu" and the corresponding item roles.
The View Transitions specification is developed openly within the W3C. Foto de Denny Müller en Unsplash

Comparison: Native API or Library

The following table summarizes when each native API makes sense and when adding a library still makes sense, section by section, based on what you’ve already seen above.

UI NeedNative APILibrary AlternativeWhen a Library Still Makes Sense
Modal or dialog<dialog>Custom modal libraryChoreographed enter/exit animations or complex nested modals
Menu or dropdownpopoverPositioning library like Floating UIThe panel needs to reposition against the viewport edges (advanced collision detection)
Content-based selector:has()querySelectorAll + manual listenersThe condition depends on in-memory state, not just DOM structure
Transition animationView Transitions APIAnimation library like GSAPChoreography with per-element easing control and guaranteed support in browsers without the API

Going Deeper: How Native APIs Work Under the Hood

<dialog> and popover share the same internal mechanism: the top layer, a rendering layer above the entire normal document tree. That’s why neither one needs z-index tricks or suffers from a container with overflow: hidden clipping its content, a classic problem with hand-built modals using position: fixed.

The :has() selector is a relational pseudo-class. For each candidate element, the CSS engine evaluates whether any of its descendants match the inner selector. It’s the first time CSS can look inside an element to decide its own style, something the language avoided for years because of the cost of recalculating cascading styles.

The View Transitions API builds a tree of pseudo-elements for each transition name: ::view-transition-group() groups, ::view-transition-image-pair() pairs the old snapshot with the new one, and ::view-transition-old() / ::view-transition-new() expose each half separately. Assigning a different view-transition-name to each element lets you animate them independently within the same transition.

The fact that these four pieces exist today wasn’t automatic. The popover attribute went through the Open UI Community Group, a working group where engineers from various browsers and frameworks propose interface primitives before taking them to a formal specification. :has() took years to be adopted because reversing the usual direction of selector evaluation has a real computational cost that engines needed to optimize before exposing it.

Your next step: open your current browser’s DevTools, paste the support-detection script from the “How to Test the Native APIs on Your Machine” section, and replace a modal or dropdown menu in your project with <dialog> or popover this very week.

📬 Get new articles by email

We only email about big articles (1-2 a month).

Frequently Asked Questions

Does the web platform completely replace React or Vue?

No. They solve specific interface patterns (modals, menus, transitions), not state management or the declarative rendering of a full application. They coexist without issue inside components of any framework.

Does the popover attribute work without writing JavaScript?

Yes, for the basic case of opening and closing a panel. JavaScript only comes into play if you need additional logic, such as loading content before showing the popover.

Does :has() affect page performance?

It can, if the selector is too general and forces the browser to check large DOM trees on every change. Scoping the selector to a specific container avoids that cost.

Does the View Transitions API work in all browsers?

Not evenly. You should always wrap the call in a support check and treat it as a progressive enhancement, never as a requirement for the page to work.

What are native browser APIs, in one sentence?

They are the HTML, CSS, and JavaScript functions that the browser already handles on its own, without the developer adding an external library to achieve the same result.

When does it still make sense to use a JS library instead of the web platform?

When you need exact visual consistency across browsers right now, very specific animation choreography, or guaranteed support in a browser where the API isn’t available yet.

References

📱 Enjoy this content? Follow @programacion on Telegram for daily tech content in Spanish: quick summaries, fresh content every day.

Featured image: Foto de Florian Olivo en Unsplash

Did it work for you? Got a different error? Say so below: questions get answered and help the next reader.

Leave a comment
Categories: Tech NewsTutorials

Andrés Morales

Developer and AI researcher. Writes about language models, frameworks, developer tooling, and open source releases. Covers ML papers, the tech startup ecosystem, and programming trends.

0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *

You can include code inside <code>…</code> or, for several lines, <pre><code>…</code></pre>.

This site uses Akismet to reduce spam. Learn how your comment data is processed.