Interactive elements without JavaScript

details, dialog, popover, datalist, progress, meter, template and slot — native widgets that ship with behaviour included.

Disclosure with details and summary

A disclosure hides content until it is asked for. Browsers have shipped one natively for years, and because it is an element rather than a script, the keyboard behaviour and the announced state come with it.

<details>
  <summary>What counts as a defect?</summary>
  <p>Anything that breaks the stated specification, including gaps in the documentation.</p>
</details>

<details open>
  <summary>Shipping and returns</summary>
  <p>Orders leave the warehouse within one working day.</p>
</details>
  • <summary> must be the first child of <details>; that pairing is what makes it the toggle.
  • Add the open attribute to expand by default, and read it from JavaScript to detect state.
  • Give several <details> the same name value and they behave as an exclusive accordion in current browsers.
  • Style the marker with summary::marker or list-style — never hide it without providing a visible replacement.
💡
Prefer <details> over a hand-built accordion: focus handling, Enter and Space activation, and the expanded or collapsed state are all provided. Every line of JavaScript you do not write is a class of bug you cannot ship.

Dialog, popover and form helpers

Modal dialogs and transient menus used to require a focus-trap library. A modal <dialog> traps focus, closes on Escape, and paints a backdrop; the popover attribute gives a light-dismiss layer with no positioning code.

<button type="button" command="show-modal" commandfor="confirm">Delete account</button>

<dialog id="confirm">
  <form method="dialog">
    <h2>Delete your account?</h2>
    <p>This action cannot be undone.</p>
    <button value="cancel">Cancel</button>
    <button value="confirm">Delete</button>
  </form>
</dialog>

<button type="button" popovertarget="menu">Options</button>
<div id="menu" popover>Sort by date · Sort by name · Export</div>
Element or attributeWhat you get
<dialog>Modal or non-modal box; showModal() adds the focus trap and backdrop
form method="dialog"The submit button's value becomes dialog.returnValue, without a page reload
popoverTop-layer element dismissed by Escape or an outside click
<datalist>Suggested values for an input, filtered as the user types
<progress>Task completion; omit value for an indeterminate bar
<meter>A scalar within a known range, such as disk usage

All of these degrade honestly: an unsupported browser treats a popover element as an ordinary block, and a non-modal <dialog> renders as a plain box. When that matters, move the fallback into a @supports branch in CSS rather than a script.

Template and slot

The <template> element holds inert markup: the parser reads it, but nothing inside renders, no images load and no styles apply. It is a stamp for repeated markup, not a visible part of the page.

<template id="row">
  <tr><td class="name"></td><td class="email"></td></tr>
</template>

<user-badge>
  <span slot="label">Beta</span>
</user-badge>
  • Read the markup with template.content, then clone it with template.content.cloneNode(true) for each instance.
  • Slots project light DOM children into a shadow tree by matching slot="name" to <slot name="…">.
  • A slot's fallback content renders only when nothing is assigned to it.
  • Both features need JavaScript to be useful: a custom element must be defined before it upgrades, so plan for the pre-upgrade flash.

FAQ

Is details and summary accessible by default?
Yes, provided you do not destroy it. Removing the marker without a visible alternative, or putting the summary text inside a nested button, breaks the announcement. Style the marker; do not delete it.
Why is my dialog showing but not modal?
dialog.show() opens it in place; only showModal() (or the show-modal command) moves it to the top layer and traps focus inside it.

Accessibility foundations in markup Inline SVG and icon markup

Last refreshed 2026-09-18.