Intro
It is hard to imagine a website app without a single button. It is a key element of user interaction, providing a means for users to perform actions and navigate through the interface. However, not all users may be able to easily interact with buttons, particularly those with disabilities or impairments that affect their motor skills or vision. Together with the Storefront UI team we decided to provide the best accessibility possible for our components, starting with something as simple as a button.
In this article, we will explore the importance of button accessibility, the challenges that users with disabilities may face, and best practices for designing buttons that are accessible to all users.
Semantics as a starting point
We can’t have a conversation about accessibility without providing proper semantics. It all starts with HTML and if you would ask developers around the globe how to create a button, you may get completely different answers. So let’s review our options.
Using the <button>
tag
The most recommended one is using <button>
tag. It provides all behaviors and functionalities by default. Funny thing is, it is not the only accessible way to achieve our goals. For many years developers lacked awareness on why the quality of code is so important. As long as the solution works and the client is happy, why should we care about semantics? Well, in recent years accessibility has become a legal requirement in the US. It can also affect SEO, as Google is ranking the websites based on their HTML structure. So yes, it does matter.
Using a <div>
or <input>
Legacy code can be a real pain, because it is not always so easy to change the code without causing breaking change. Unfortunately, using things like <div role=”button” />
or <input role=”button />
is still common among many web pages. However, it is a perfectly accessible element as long as you actually provide all the interactions to make it work exactly like a native <button />
. You must treat the role as a promise, not a solution. This requirement and the human factor is the real reason why so many webpages are still not accessible enough. That is why I highly recommend riding on the coattails of others and using <button>
wherever the element should trigger a click action. And that’s it. Accessibility is all about making your code smarter.
Design vs. Development
I cannot stress this enough - cooperation between developers and designers is essential for creating a product with good accessibility. I would say that two key focus areas for designers should be proper sizing and color contrast. This can really affect your project's accessibility. Buttons must have a sufficient (minimum of 37 px according to MIT Touch Lab) size to be easily clickable, especially on touch devices. Larger buttons are easier to target accurately, reducing the risk of accidental clicks.
Color contrast between the button's foreground (text or icon) and background colors is particularly important for users with low vision or color blindness. Ratio of at least 4.5:1 should be used for normal-sized text and 3:1 for large text. There are many online tools that allow developers and designers to find a perfect match.
However, it is essential to prioritize accessibility over design preferences and invest the necessary effort to ensure an inclusive user experience for all users, including those who rely on keyboard input or assistive technologies.
Accessibility properties
Sometimes a little help from HTML attributes is needed to make the button truly accessible.
Role vs. type
In the previous section I’ve briefly presented the usage of role
attribute. Let’s dive deeper into this. From my personal experience, I can tell that role
is commonly confused with another HTML attribute - type
. The button
role identifies an element as a button to assistive technologies. Simply speaking, it does not matter to screen readers whether you use <button>
or <div>
with role=”button”
.
Whereas type
is specifying the context of usage. By using the proper type, you can create a button that behaves in a specific way, depending on the needs of your web page.
Here are the most commonly used button types in HTML:
type="button"
- a simple button that does not perform any specific action.type="submit"
- commonly used in forms. It submits the form data for processing.type="reset"
- this type of button is also used in forms. It resets all form fields to their default values.
For most browsers, the default button type is submit
, as in the early days of modern websites buttons were used mostly for building forms. Today they are used in many different contexts and it isn’t always clear at first glance at the markup. Therefore, it is a good practice to always declare the type of a button explicitly.
Aria Labels
ARIA attributes, such as aria-label
, aria-labelledby
, aria-describedby
and aria-hidden
, among others, allow developers to specify additional information about the purpose, state, and behavior of elements. Button is just one of them. No ARIA is better than bad ARIA - this is a quote taken from WCAG best practices and I couldn't agree more. While ARIA labels can be a powerful tool for improving web accessibility, it's important to use them judiciously and appropriately. Overuse of ARIA labels or incorrect usage can actually make web content more confusing or difficult to navigate for users with disabilities. And the truth is, for most cases, you don’t need it. ARIA can even act as a cloak and override original semantics of element i.e. <label>
.
But in some cases aria-label is a must have to provide good accessibility. ARIA labels should be used for buttons when the text on the button itself doesn't fully describe the action that the button performs, or when additional information is needed to clarify the purpose of the button for users with disabilities. A great example of such an element is a button with an icon, but without any label inside. These can be found in menus, product cards, wishlists etc., as seen on the picture below:
<SfButton type="button" :square="true" aria-label="Add to cart">
<SfIconAdd />
</SfButton>
Icon itself is not representative enough, at least not for someone with vision disability. When screen readers come across such a button without aria, it will not be read to the user. So how can anyone understand what the purpose of these buttons are? Therefore we need to provide a description using the aria label property.
Here are a few examples of BAD aria usage:
html
<SfButton aria-label="button"><MyCoolIcon /></SfButton>
html
<SfButton aria-label="Add to cart">Add to cart</SfButton>
In StorefrontUI we took the full flexibility approach when it comes to arias. There is no specific prop for aria labels or disabled attributes. Instead, the template allows users to assign those properties as simply as binding them to the component. All of our copy-pasteable examples has aria-labels
.
Preventing button click
Web Content Accessibility Guidelines (WCAG) requires that websites provide clear and consistent feedback to users, and that they do not present information or functionality in a way that is confusing or misleading. Using the disabled
attribute helps meet these standards by clearly indicating the availability of functionality. When a button is disabled, it can also provide visual feedback that helps users understand the status of the associated functionality. For example, if a "Submit" button is disabled until all required form fields have been completed, this can help users understand that their form is not yet complete.
Button as a link or link as a button?
While both buttons and links serve interactive purposes, there are specific scenarios where choosing the appropriate element can enhance the user experience for individuals with disabilities who rely on assistive technologies.
When the interactive element primarily triggers an action or performs a function, it is generally more appropriate to use a button instead of a link. Buttons are typically associated with actions such as submitting a form, confirming a selection, or initiating a process.
So what can we do when we have a link that should look like a button? A real life example can be a “Continue to payment” button in checkout. Well, here is how we handled it at Storefront UI. Vue offers a tremendous feature that is dynamic components.
The SfButton component is built using the component is=”tag”
and it is up to the user to decide what should be passed via tag prop. Therefore, you can render your component as a link by passing a
, or as a button by passing button
. It all depends on the context.
Keyboard a11y
Keyboard accessibility in button components is essential for achieving universal accessibility. While many users rely on a mouse or touch input for interaction, some individuals with disabilities may not have access to these input methods or may find it challenging to use them effectively. By ensuring that buttons can be fully operated using the keyboard, we can accommodate users who rely on alternative input devices, such as screen readers or assistive technologies, to navigate and interact with digital interfaces.
Such an important feature also happens to be the most “painful” one when it comes to implementation practices. So what is so tricky in the matter?
1. Focus outlines are ugly.
One common challenge in implementing keyboard accessibility is the perception that focus outlines are aesthetically unappealing or disrupt the visual design of a website or application. Some designers and developers may opt to remove or modify the default focus outline
styles to achieve a specific look or to match the overall design aesthetics. However, it's important to remember that the focus outline serves a crucial accessibility purpose, providing a visual indicator for keyboard users. While it may not align perfectly with certain design preferences, it is essential to prioritize accessibility over purely aesthetic considerations.
2. Keyboard accessibility is more than just tabbing around the elements.
Keyboard accessibility involves more than just enabling users to navigate through elements using the Tab
key. It’s much more, depending on the context. For menus and dropdowns you should also use arrows and optionally - Home/End
keys.
Also, there is a difference in how we need to navigate through elements. Imagine using just a keyboard to navigate through the page. The problem is that tabbing relies on the DOM tree. Therefore opening fe. a modal, won’t even trigger the focus on its elements. We’ll keep on tabbing under the opened window which may cause confusion. Therefore it should not be surprising that there are so many open source solutions just for handling the keyboard navigation. In the case of the Button
component, the solution is as simple as using focus-visible
Tailwind class.
For more advanced components, like custom Input, we decided we decided to implement a custom solution to have full flexibility of how the outlines behave:
-
useFocusVisible
- sometimes the CSS :focus-visible is not enough. In some of our components we wanted to style the wrapper element based on the nested input. That’s why we implemented a custom solution, as:has()
pseudo class is not fully supported yet.
The following composable/hook is showing the focus outline only for the keyboard events. The user does not have to worry about the ugly outlines ever again. This simple yet powerful solution allows us to have both - great accessibility and nice design.
You can check the source code in our repo here.
Button a11y Checklist
Add descriptive text or aria label: Ensure the button has clear and descriptive text that conveys its purpose or action. Avoid using vague labels like "Click here". If button contains only icon add an aria-label with proper description.
Use proper HTML structure: Ensure the element is used instead of (anchor) or other elements for actions and interactions. This ensures semantic correctness.
<button type="button">Hello</button>
Ensure keyboard accessibility: Test the button's functionality using only the keyboard. Ensure it can be focused on using the "Tab" key. Make sure the focus indicator is visible and adequately styled.
Implement proper focus management: Ensure that the focus is moved to the button when it becomes visible or activated, and that it is not trapped or lost inappropriately.
Provide visual cues on interaction: Make sure the button visibly changes its appearance when hovered over or clicked, using CSS styles such as background color, border, or shadow. This helps users understand the button's interactive nature.
Consider color contrast: Ensure that the button's text color and background color have sufficient contrast to be easily readable, especially for users with visual impairments.
Use ARIA attributes if necessary: If the button performs a complex action or has dynamic behavior, consider using ARIA attributes like aria-expanded, aria-controls, or aria-pressed to provide additional context to assistive technologies.
Test with assistive technologies: Use screen readers or other assistive technologies to test the button's accessibility. Ensure the button's purpose, state changes, and any associated information are communicated effectively.
Summary
Prioritizing accessibility in button design is an essential step towards building inclusive websites and applications. It may seem like a lot at first but in the end it leads toward better code and better experience for all users.
Have some suggestions regarding accessibility? Feel free to contact me, follow on Twitter or contribute to StorefrontUI and help make the web a more friendly place for everyone.