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
openattribute to expand by default, and read it from JavaScript to detect state. - Give several
<details>the samenamevalue and they behave as an exclusive accordion in current browsers. - Style the marker with
summary::markerorlist-style— never hide it without providing a visible replacement.
<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 attribute | What 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 |
popover | Top-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 withtemplate.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?
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.Related
Accessibility foundations in markup Inline SVG and icon markup
Last refreshed 2026-09-18.