Accessibility: Why we disallow role=”button”

At smallcase, we are constantly working on making our web applications more accessible. As part of these efforts, we have come across patterns where we consistently see developers getting confused or making mistakes. One of such repeated issues has led us to enforce the following rule: We do not allow passing role = button in non-semantic HTML elements(like div, section, span) instead we want the devs to use a button element that is styled as per requirements.

This blog talks about why we enforced this rule, and the solution using the element.

Why?

We do not allow passing because after passing role as a button it becomes mandatory to pass and properties to make the element accessible. If you are using eslint with the accessibility plugin, it will remind you to pass both of these, but passing these properties manually is prone to accessibility problems if not passed carefully.

Problems:

onKeyDown: The function passed to element is called when a button on the keyboard is pressed while the element is focused. The problem happens when the developer forgets to add a check for which key to trigger the causing the function to be called on the press of any key(eg. key) when the user is accessing the website using the keyboard only.

tabIndex: The makes a DOM element focusable and allows/prevent them from being sequentially focusable. Passing a wrong value to tabIndex can mess up the order of tab accessibility. Determining what value to pass to tabIndex is usually complicated and should be done with caution.

As an example, below is a video of one of the prod issues we faced when one developer incorrectly specified onKeyDown without filtering for the space/enter key.

Prod bug we faced:

In the above example, we are accessing the page using the keyboard(tab button) and the methodology link is a with and manually passed for accessibility but we missed checking for which key to trigger . Therefore, when the methodology link is focused and then the is pressed, the modal opens, along with tabbing to the next item.

Solution:

A better solution is to use the element instead div because the takes care of and prop automatically so that developers don’t have to worry about them. We usually reset the styles of buttons and design them according to the requirement using CSS.

How to reset button styles?

Stylesheet:

.reset-button-styles {
padding: 0;
margin: 0;
background: none;
border: none;
outline: none;
}
.primary-cta {
font-size: 16px;
font-weight: bold;
color: #2f363f;
cursor: pointer
}
.primary-cta:hover {
color: #1f7ae0;
}

Clickable Div(not allowed):

<div class="primary-cta" onPress={() => {}} onKeyDown={() => {}} tabIndex={0}>Call to Action</div>

Button(allowed):

<button class="reset-button-styles primary-cta" onPress={() => {}}>Call to Action</button>

In the above code snippets, to start using instead of we started passing (to reset default button styles) and (to add custom styles).

Please note that in your codebase, resetting the button styles and overriding them to make them look alright might require more CSS than mentioned in the above snippet.

Next steps:

  • Write a custom eslint rule that enforces this convention
  • May be create a unstyled button component which has css defaults like a div, and make sure devs use that instead of using a div with onClick

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Bharat Gupta

Software Engineer. React | React Native | Next.js | PHP | Laravel