< button >
Not a link.
The button element has picked up a handful of new capabilities over the last few years. Before getting to those, here is what the element already does the moment you type <button>: keyboard activation, focus handling, the platform focus ring, disabled semantics, form submission participation, and the correct assistive technology role.
Built-in with native HTML
- Activation on Space and Enter, with the correct timing. Space fires on key-up, Enter fires on key-down. This matches platform conventions across operating systems.
- Focusability and a platform focus ring that respects
:focus-visibleand the user's operating system focus settings. - Form participation. A button inside a
<form>submits the form by default. If a form you are building reloads unexpectedly when a button is activated, the button's defaulttypeis one angle worth checking. Thetypeattribute is covered below. - Disabled semantics. The
disabledattribute removes the button from the tab sequence, blocks activation, and exposes the correct disabled state to assistive technology. There's more nuance here, covered below. - Implicit
type="submit"when the button is inside a form. This is a default worth knowing about.
The type attribute, precisely
Three values: submit (the default), reset, and button. A button inside a <form> with no explicit type behaves as type="submit".
<form>
<button>Save</button>
<button type="button">Cancel</button>
</form>
The first button submits the form. The second does not. Setting type explicitly on every button inside a form is one way to make the behavior unambiguous at the markup level. For an in-house button component, one option is to default the component's type to "button", leaving callers to opt into submit when submission is the intent. When a component passes type through without setting its own default, HTML's own default applies: submit inside a form, otherwise button.
Form-associated attributes: letting a button override the form
A button inside a form can override the form's submission behavior on a per-button basis. Five attributes do the work:
formactionsubmits to a different URL than the form's own action.formmethodsubmits with a different HTTP method.formenctypesubmits with a different encoding.formnovalidateskips constraint validation for this submission.formtargetopens the response in a different browsing context.
Consider a form with two submit paths: a "Save draft" button that posts to a draft endpoint and skips validation, and a "Publish" button that submits to the primary endpoint with validation intact. Both buttons live in one form. The markup:
<form action="/publish" method="post">
<input name="title" required>
<textarea name="body" required></textarea>
<button type="submit" formaction="/draft" formnovalidate>Save draft</button>
<button type="submit">Publish</button>
</form>
Invoker commands: declarative triggers for dialogs, popovers, and more
The button element is the declarative trigger for several platform features that used to require a script.
popovertarget="id"opens a[popover]element. Light-dismiss, top-layer rendering, and focus return are handled by the browser.popovertargetaction="show","hide", or"toggle"sets the intent explicitly.commandandcommandforare a newer addition. They let a button declaratively open a dialog, toggle a details element, or dispatch a custom command event to a named target. Patterns like "open this dialog," "toggle this disclosure," or "dispatch that event" can be expressed directly in markup with this API.
<button popovertarget="menu" popovertargetaction="toggle">Options</button>
<div id="menu" popover>...</div>
The invoker commands API (command and commandfor) is the newest of these. Worth checking current browser support before relying on it in production.
Composite patterns that call for <button>
Patterns where the button element is the right choice and other elements would break the pattern:
- Split button. A primary action button next to a menu-opener button, styled to look unified. Each is its own
<button>because each carries its own activation semantics: "do the thing" and "open the menu." The menu-opener takesaria-haspopup="menu"andaria-expanded. - Toolbar, with
role="toolbar"on the container. Children are buttons, navigated by roving tabindex. - Disclosure trigger, toggling the visibility of an adjacent region through
aria-expanded. The trigger is a button because the action is in-page, not navigational. - Menu button, with
aria-haspopup="menu"andaria-expandedtracking the menu state. - Tab, with
role="tab"inside arole="tablist". When authored from scratch, tabs are buttons under the hood. - Dialog trigger, the element that opens a
<dialog>throughshowModal()or through the invoker commands API.
disabled vs aria-disabled
Two attributes, two different behaviors. Which one fits depends on whether the button should still be reachable.
| Behavior | disabled |
aria-disabled="true" |
|---|---|---|
| Tab-focusable | No | Yes |
| Click fires | No | Yes (the handler must block) |
| Announced as disabled | Yes | Yes |
| Participates in form submission | No | Yes |
aria-disabled is useful when the button needs to stay focusable so the user can inspect it, read a tooltip, or see a validation message explaining why it's disabled. The handler then checks the attribute and blocks the action. disabled is for controls that are genuinely inert, with no need to be reached.
Already HTML-available
Patterns that HTML handles natively, which are often rebuilt in JavaScript:
- Custom clickable elements with
role="button"andtabindex="0". The native button covers keyboard activation, focus ring, disabled state, and assistive technology role in one element. - Anchors styled as buttons for actions. When the element performs an action rather than navigating, the button element is the fit.
- Click handlers that toggle on and off state. For UI like bold, italic, or mute, the button with
aria-pressedis the platform's pattern. For binary settings, a labeled<input type="checkbox">serializes into form submissions and integrates with constraint validation. - Popover and menu libraries. The
[popover]attribute and the invoker commands API cover the common cases of what popover helpers do, with focus return and top-layer rendering built in. - Custom "Save draft" flows. The
formactionandformnovalidateattributes let one form submit to two different endpoints with two different validation policies, declaratively.
Sizing
The 44 by 44 CSS pixel minimum for an interactive target traces back to WCAG 2.5.5 (Target Size, Level AAA) and the Apple Human Interface Guidelines. WCAG 2.2 added success criterion 2.5.8 at Level AA, which is 24 by 24 with spacing exceptions. For a site targeting AA, 24 is the floor. 44 remains a solid default for touch interfaces.
Reference:
- WHATWG
- the-button-element
- caniuse.com
- mdn-html_elements_button
- MDN Docs
- Web/HTML/Element/button
- WAI-ARIA APG
- Button Pattern
- Menu Button Pattern
- Open UI
- Invoker Commands explainer