Interactive elements

Use the built-in interactive elements instead of rebuilding them, and know what each one gives you free.

15of 22
≈ 11Min read
8Parts

Behaviour you do not have to write

Several widgets that people reimplement every week are already in HTML, with keyboard support, focus handling and the right announcements included. Using them is less code and better behaviour.

details and summary

  • <summary> must be the first child. It is the button; everything after it is the panel.
  • open is a boolean attribute — present means expanded.
  • It is focusable, opens on Enter and Space, and is announced as expanded or collapsed.
  • A toggle event fires when it changes: el.addEventListener("toggle", …).
  • Give several in a group the same name attribute and they behave like an accordion — only one open at a time.
a disclosure with no JavaScript
<details>
  <summary>What is included?</summary>
  <p>Frame, leaf, fittings and fitting.</p>
</details>

<details open>
  <summary>Lead time</summary>
  <p>Five days for stock sizes.</p>
</details>
Searching inside a closed panel

Modern browsers open a closed details when the reader uses find-in-page, which is something a hand-built accordion never does.

dialog

<dialog> with showModal() gives you a real modal: the rest of the page becomes inert, focus is trapped inside, Escape closes it, and it renders in the top layer so no z-index can cover it. The backdrop is styleable.

  • showModal() — modal, inert background, Escape closes. show() — non-modal, no trapping.
  • <form method="dialog"> closes the dialog on submit and puts the button's value in returnValue.
  • Put autofocus on the control the reader should land on.
  • Style the backdrop with dialog::backdrop.
a confirmation
<button id="open">Delete draft</button>

<dialog id="confirm">
  <form method="dialog">
    <h2>Delete this draft?</h2>
    <p>This cannot be undone.</p>
    <button value="cancel">Cancel</button>
    <button value="delete" autofocus>Delete</button>
  </form>
</dialog>
script.js
const dialog = document.getElementById("confirm");

document.getElementById("open").addEventListener("click", () => dialog.showModal());

dialog.addEventListener("close", () => {
  console.log(dialog.returnValue);   // "cancel" or "delete"
});
style.css
dialog {
  border: 0;
  border-radius: 14px;
  padding: 24px;
}

dialog::backdrop {
  background: rgba(0, 0, 0, 0.55);
}

popover

A popover is a light overlay that closes when you click outside it or press Escape, without making the page inert. Two attributes and no script.

a menu
<button popovertarget="menu">Options</button>

<div id="menu" popover>
  <button>Duplicate</button>
  <button>Rename</button>
</div>

popover="manual" keeps it open until you close it explicitly. Use popover for menus and hints; use <dialog> when the reader must answer before continuing.

progress and meter

<progress> is for something finishing. <meter> is for a reading on a scale, and it colours itself from low, high and optimum. The text inside is the fallback.

two bars that mean different things
<!-- a task moving towards completion -->
<label for="upload">Uploading</label>
<progress id="upload" value="32" max="100">32%</progress>

<!-- a measurement inside a known range -->
<label for="disk">Disk used</label>
<meter id="disk" value="0.72" low="0.5" high="0.85" optimum="0.2">72%</meter>

<!-- unknown duration: leave out value -->
<progress></progress>

output

<output> is an ARIA live region by default, so a screen reader announces the new value as it changes — which is exactly what a slider needs, since the slider itself shows nothing.

a live result
<form>
  <label for="doors">Doors</label>
  <input type="range" id="doors" name="doors" min="1" max="20" value="6">
  <output for="doors" name="total">6</output>
</form>

datalist

Unlike <select>, the reader may type something that is not on the list. Use it for suggestions; use <select> when the answer must be one of the options.

suggestions, not a fixed list
<label for="finish">Finish</label>
<input list="finishes" id="finish" name="finish">

<datalist id="finishes">
  <option value="MDF painted">
  <option value="Veneer">
  <option value="Ceramic">
</datalist>

When to build it yourself

Native elements are limited in how they can be styled — <select> in particular. That is a genuine reason to build a custom control, but it is a large amount of work done properly: roles, aria-expanded, aria-activedescendant, arrow keys, Home and End, typeahead, Escape, focus return.

Before you start, check whether the design can bend. A styled native control that works for everyone usually beats a beautiful one that a keyboard cannot reach.

Practice