Skip to main content

Why native buttons matter

When building interactive elements, we should always prefer native implementations over custom ones whenever possible. Nowhere is this more prevalent than with buttons. Native HTML buttons provide essential accessible features out of the box without any additional work:
Native buttons automatically have the proper ARIA role of button, which tells screen readers and other assistive technologies that the element is interactive and can be activated.
Both the Enter and Space keys automatically activate the click event on native buttons. Custom elements require manual event handling for both keys.
Buttons are correctly placed in the focus order of the page by default with tabindex="0". They receive focus when users navigate with the Tab key.
Native buttons work seamlessly with forms, support the disabled attribute, and provide attributes like type, form, and formaction for advanced use cases.

Native button example

Here’s a simple native button from the course website:
<button class="my-2">Classic button</button>
This single line of code provides all the accessibility features mentioned above.

Common pitfalls: div buttons

One of the most common accessibility mistakes is creating “buttons” using <div> or <span> elements:
Avoid this pattern:
<div onclick="handleClick()">Click me</div>
This div has several critical accessibility issues:
  • No semantic meaning (not announced as a button)
  • Not keyboard accessible (can’t be activated with Enter or Space)
  • Not in the focus order (can’t be reached with Tab key)
  • No disabled state support

What’s wrong with div buttons?

  • Screen readers won’t announce them as interactive elements
  • Keyboard users can’t activate them without a mouse
  • Not in tab order - users navigating with keyboard will skip right over them
  • No form integration - can’t submit forms or use standard button attributes
  • More code - requires additional JavaScript and ARIA attributes to match native functionality

Building accessible custom buttons

If you absolutely must use a non-button element (which should be rare), you need to manually add all the accessibility features:
<div
  role="button"
  tabindex="0"
  onclick="handleClick()"
  onkeydown="handleKeyDown(event)"
>
  Click me
</div>
function handleKeyDown(event) {
  // Activate on Enter or Space
  if (event.key === 'Enter' || event.key === ' ') {
    event.preventDefault(); // Prevent page scroll on Space
    handleClick();
  }
}

Required attributes and handlers

1

Add role='button'

Tells assistive technologies this element is a button:
<div role="button">Click me</div>
2

Add tabindex='0'

Makes the element keyboard focusable and adds it to the natural tab order:
<div role="button" tabindex="0">Click me</div>
3

Handle Enter and Space keys

Both keys should activate the button (native buttons do this automatically):
element.addEventListener('keydown', (event) => {
  if (event.key === 'Enter' || event.key === ' ') {
    event.preventDefault();
    // Trigger your click action
  }
});
Remember to call preventDefault() for the Space key to prevent page scrolling.
4

Implement disabled state

If needed, handle the disabled state with ARIA:
<div role="button" tabindex="-1" aria-disabled="true">Click me</div>
And prevent activation in your event handlers when aria-disabled="true".

Best practices

Use native buttons

Always prefer <button> over custom implementations. It’s simpler, more reliable, and more accessible.

Use button for actions

Use <button> for actions that don’t navigate (like opening modals, toggling content, or submitting forms).

Use links for navigation

Use <a> tags for navigation to different pages or locations. Don’t use buttons styled as links.

Provide visible focus indicators

Ensure your buttons have clear, visible focus indicators for keyboard users. Never remove outlines without providing an alternative.

Exercise

Ready to practice? Try the Custom Button Exercise where you’ll convert a <div> element into a fully functioning accessible button.

Resources

Build docs developers (and LLMs) love