Native interactive elements
Prefer native HTML interactive elements — <button>, <a>, <details>/<summary>, <dialog> — over divs with click handlers. You get keyboard support, focus management, and assistive-tech semantics for free.
What it is
HTML ships a small set of interactive primitives — <button>, <a href>, <details>/<summary>, <dialog> — that come pre-wired with keyboard handling, focus management, ARIA roles, and browser behaviours. Reach for those first; reach for <div role="button" tabindex="0"> only when the platform genuinely has no element for what you are building. The broader principle of “use the right tag” lives on Semantic HTML; this page is about the interactive ones specifically.
Why it matters
Replacing <div onclick> with <button> is not cosmetic. The native element gives you, with zero code:
- Keyboard semantics.
<button>activates on ENTER and SPACE.<a>activates on ENTER.<dialog>closes on ESC.<details>toggles on ENTER or SPACE when<summary>has focus. Tab order is correct by default. - Focus management.
<dialog>traps focus inside the modal while open, returns focus to the opener on close, and renders in the top layer above other content. - Assistive-tech announcement. Screen readers expose the right role (“button”, “link”, “dialog”, “disclosure”) and state (“expanded”, “modal”) without an
aria-*attribute in sight. - Browser plumbing. Open
<details>state survives back/forward navigation.<a>populates history, supports middle-click and “open in new tab”, honourstargetandrel. - Free maintenance. Every browser ships fixes and refinements (animatable
<details>,::backdrop, ESC-dismiss policy) without you changing a line.
Re-implement any of this in JavaScript and you have signed up to maintain it forever, with bugs.
How to implement
Disclosure / FAQ / accordion — <details> and <summary>:
<details>
<summary>What is the return policy?</summary>
<p>Returns are accepted within 30 days of delivery.</p>
</details>
Animate with modern CSS — no JavaScript needed:
details::details-content {
block-size: 0;
overflow: hidden;
transition: block-size 200ms, content-visibility 200ms allow-discrete;
interpolate-size: allow-keywords;
}
details[open]::details-content { block-size: auto; }
Modal dialog — <dialog> with showModal():
<dialog id="confirm">
<form method="dialog">
<p>Delete this entry?</p>
<button value="cancel">Cancel</button>
<button value="confirm">Delete</button>
</form>
</dialog>
<button type="button" onclick="confirm.showModal()">Delete</button>
showModal() opens it modally — focus is trapped, ESC dismisses, ::backdrop styles the dim layer. Use show() for non-modal. For transient, non-blocking overlays (menus, tooltips, toasts) use the Popover API instead.
Buttons and links. A <button> does something on the current page; an <a href> goes somewhere. Inside a <form>, always set type="button" on buttons that are not the submit:
<button type="button" onclick="reset()">Reset</button>
Common mistakes
<div onclick>as a button. No keyboard activation, no focus, no accessible name, no role.<a href="#" onclick>as a button. Pollutes browser history, breaks middle-click and “open in new tab”, and announces as a link in screen readers.- Rebuilding
<details>in JavaScript “to control the animation”. Modern CSS —interpolate-size,transition-behavior: allow-discrete,::details-content— animates the native element. - Using
<dialog>for transient, non-blocking UI. Reserve<dialog>for modal flows that require a decision. For menus, popovers, and toasts, use the Popover API. - Forgetting
type="button"inside a<form>. A bare<button>defaults totype="submit"and submits the form on click.
Verification
- Tab through the page. Every interactive thing must be reachable, in a sensible order, with a visible focus ring.
- Open Chrome DevTools → Elements → Accessibility tree. Confirm
details,dialog, andbuttonare exposed with the correct roles and states. - Activate
<details>,<button>, and<dialog>triggers using only the keyboard (ENTER, SPACE, ESC). - In VoiceOver, open the Web Item Rotor — disclosure widgets and dialogs appear with the right labels.
- Search the codebase for
role="button"andonclickon non-button elements. Each hit is a candidate for replacement.
Related topics
Sources & further reading
- WHATWG HTML — The details element — WHATWG
- WHATWG HTML — The dialog element — WHATWG
- MDN — <dialog> — MDN
- WAI-ARIA Authoring Practices — W3C WAI