Buttons are the workhorse of the modern web app.
Today, I wanted to cover a series of trips, tricks, gotchas, and useful approaches to make working with buttons easier and more accessible for the people who use the things we build.
The screen reader and keyboard experience
Before diving in, we need to take a short detour to talk about a different way some people experience the web.
Not everyone navigates the web with a visible screen or mouse.
People with visual impairments often navigate the web with just a keyboard instead of using a mouse. People with neuromuscular conditions sometimes use only a keyboard as well, as those certain conditions can make using a mouse difficult.
Folks with visual impairments also often use software called a screen reader that reads the content of a page aloud.
In addition to reading text, it will announce important information about elements on the page. For example, it will tell you that an element is a link, what the link text is, and where the URL points to.
The way this software and keyboard navigation work influences how we build for the web.
Buttons aren’t links
While you can style buttons and links to look identical, HTML elements convey different intent to screen readers.
- A link conveys “this will navigate you somewhere else.”
- A button conveys “this will trigger an on-page behavior.”
That doesn’t mean you can’t style one to look like the other to match your intended UI, but choosing the correct element will provide critical information to visually impaired users about your intent.
Buttons aren’t <div>’s
Similarly, you can style a <div> element to look like a button, detect clicks on it, and run code in response.
But a <div> element cannot natively receive focus from a keyboard. While you can hack that functionality in, a button also treats Enter and Spacebar key presses as clicks, while a <div> does not.
A <div> also does not announce itself as a button, so it does not convey its intent to screen reader users.
While you can replicate all of this functionality with JavaScript and HTML attributes, it’s simpler and easier to just use a button.
Prevent buttons from submitting forms
A button is the primary way you submit a form.
But sometimes, you’ll have a button inside your form that triggers a different behavior (like showing some hidden content or opening a modal) and should not cause the form to submit.
Adding the [type="button"] attribute to your button tells the browser not to use that button to submit the form, without you having to write JavaScript to handle that.
<button id="show-tos" type="button">View Terms of Service</button>
Icons in buttons
Your user interface may have some buttons that use icons instead of text.
For example, imagine a “favorite” button that uses a heart icon.
<button id="favorite">❤️</button>
While it might be obvious to a sighted user what that button does, someone using a screen reader will hear something like: “red heart, button.”
If you use an SVG icon, they’ll only hear: “button.”
There are two ways to address this.
The [aria-label] attribute overrides whatever text label would be announced on an element, and announces its value instead.
<button id="favorite" aria-label="Favorite this item">❤️</button>
This button would be announced as “Favorite this item, button” instead of “red heart, button.”
There are two important consider when using [aria-label]:
- It will override all text inside of its containing element for screen reader announcements.
- Its content is generally not translated by automatic translation software (like Google Translate).
If translation/internationalization is a concern, or you’d like to include supplemental information for text that’s already there, you might use a .visually-hidden class instead.
A .visually-hidden class
A .visually-hidden class, as its name implies, hides content visually in the UI while keeping it accessible to screen readers.
/**
* Visually hide an element, but leave it available for screen readers
* @link https://github.com/h5bp/html5-boilerplate/blob/master/dist/css/main.css
* @link http://snook.ca/archives/html_and_css/hiding-content-for-accessibility
*/
.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
white-space: nowrap;
width: 1px;
}
Because it’s only visually hidden, translation software will still translate its text.
And unlike [aria-label], you’re not overriding all of the text in an element, which makes it useful for adding additional context for screen readers without having to write the same base text twice.
<button id=”save”>
Save
<span class=”visually-hidden”>this article</span>
</button>
When paired with an icon that would be announced but doesn’t provide any meaningful information (like our heart emoji), you should wrap the meaningless content in an element with the [aria-hidden="true"] attribute.
<button id="favorite">
<span aria-hidden="true">❤️</span>
<span class="visually-hidden">Favorite this article</span>
</button>
Button State
Buttons in web apps often have an on/off state.
Consider our “favorite” button again. The item it’s associated with might be favorited (the button state is “on”) or not favorited (the button state is “off”).
Developers will often reach for a class to style the button differently when it’s in the on state.
<button
id="favorite"
aria-label="Favorite this item"
class="is-active"
>
❤️
</button>
While that provides a visual indication of state for sighted users, someone using a screen reader will have no idea what the state of the button is.
For that, we can use the [aria-pressed] attribute.
It has two potential values: “true” when the item is “on,” and “false” when the item is “off.”
<button
id="favorite"
aria-label="Favorite this item"
aria-pressed="true"
>
❤️
</button>
Here, a screen reader will announce something like: “Favorite this item, selected, toggle button.”
This tells the user the button is used to favorite an item, it can be toggled on or off, and it’s currently toggled on.
And you can even style active or inactive state using the attribute, eliminating the need for a separate CSS class to handle visual changes.
[aria-pressed="true"] {
background-color: rebeccapurple;
color: white;
}
Disabling buttons
The HTML [disabled] attribute prevents a button from receiving focus or reacting to click and keyboard events.
<button disabled>Submit</button>
It’s useful when you want to prevent a user from taking action on a button until some other action happens or while some background process completes (such as waiting for a submitted form to resolve before submitting again).
But because a [disabled] button cannot receive focus, a visually impaired user using a screen reader might not be aware that there’s a button to press once some other action or process resolves itself.
And if the button had focus when the [disabled] attribute is added (such as when submitting a form), it will lose focus, which can create a confusing experience for both screen reader and keyboard users.
In situations where a button may already have focus, or where the user needs to be aware of its existence even if they can’t currently interact with it, the [aria-disabled="true"] attribute may be a better choice.
<button aria-disabled="true">Submit</button>
Unlike [disabled], it will not prevent clicks or presses, so you’ll need to check for [aria-pressed] state in your JavaScript and ignore interactions when set to “true.”
It purely conveys state to screen readers.
Picking the right tool for the job
As you can see, despite being a simple element, there are a lot of considerations when using a button in your web app.
It’s really important to consider the intent behind the button, as that helps guide all of the decisions that come after it.

Loved the article? Hated it? Didn’t even read it?
We’d love to hear from you.
Great! It’s Helpful. I often consider acceptability while building anything and this helped me to understand about a specific topic.