Video examples

iOS Voiceover

Android Talkback

Windows Jaws Chrome

Windows NVDA Chrome

  • When the user clicks a link, they are taken to a different location in the site.
    • Either another page or even another area of the same page
  • A link can look like a big shiny button but it must be coded as <a> link

If it does something, it’s a <button>

  • Buttons cause an action to occur on the same page
    • Submit a form (even when submission takes you to a new page)
    • Open a menu
    • Launch a modal
    • Expand details
  • A button can look like a link, but it must be coded as a <button>

Code examples

Use semantic HTML

  • This semantic HTML contains all accessibility features by default.
  • It uses CSS pseudo attributes to create the arrow indicator, no Javascript.

Focusable disabled button

The preferred method is to use aria-disabled="true" so screen reader users can find the button, click submit and be notified of errors in the form.

<button aria-disabled="true">

Fully disabled button

A button that uses the disabled attribute will not be focusable, but it is still discoverable by the screen reader while browsing.

<button disabled>

When you can’t use semantic HTML

This custom button requires extra attributes and JS event listeners. Adding tabindex="0" makes it focusable.

<div role="button" tabindex="0">

When there’s no inner text that text doesn’t make sense

  • As a last resort, aria-label can be used.
  • aria-label will (typically) replace the inner text of the button for the screen reader output.
  • DO NOT repeat the inner text in the aria-label as some screenreaders will read both.
<div role="button" tabindex="0" aria-label="Continue">
  <!-- icon but no text -->

<div role="button" tabindex="0" aria-label="Buy now, iPhone 17">
  Buy now <!-- Ambiguous text doesn't describe the intent -->

When there are repeating buttons

Sometimes the design will call for multiple buttons with the same text label. In a case like this, aria-label can be used to name each control’s purpose.

<button aria-label="Edit payment date">
<button aria-label="Edit payment amount">

Developer notes


  • Inner text should describe the purpose of the button.
  • aria-label="Button purpose" can also be used (as a last resort)


  • Native button identifies as button by default
  • Use role="button" for custom elements


  • Use aria-haspopup="true" for menu, listbox or modal
  • aria-controls="popupId" is not well supported


  • Toggle buttons aria-pressed="true/false"
  • Menus or expanders use aria-expanded="true/false"
  • Use the disabled state for completely inactive buttons that shouldn’t be focusable
  • Use aria-disabled="true/false" state for inactive custom elements


  • Focus must be visible
  • Custom elements (like <div>) need tabindex="0" to be focusable