Sometimes you might want to use a different select element to match your style based on the theme of your design. Or perhaps the default design is different on separate browsers and you want uniformity.
But when designing this new element, you might forget to consider the accessibility of the component.
Typically, default elements are accessible – and if you plan to replace them with custom designs, you should ensure it works as well as the default.
In this tutorial, I'll show you how to build a custom dropdown with a step by step example.
To follow along with this tutorial, you should have:
- Basic HTML knowledge: Understand how HTML elements and attributes work.
- Understanding of ARIA: While the tutorial explains ARIA roles and attributes, having a basic understanding of accessibility concepts can be beneficial.
Here's What We'll Cover:
- Default Select Features
- How to Figure Out Which ARIA Attributes Are Required
- How to Set Up the HTML
- The CSS
– Toggle dropdown visibility
– How to close the dropdown
– Dropdown keyboard interaction
– How to fix the option visibility issue
– How to highlight options on alphanumeric keypress
– How to enhance screenreader functionality
- Wrapping Up
Default Select Features
Since the default select attribute is accessible, let's look at some of the features that makes it accessible:
- The select element visibly indicates when it's active or selected, typically through a change in appearance.
- The element opens on click or key press (SPACE, UP, and DOWN Arrow)
- While the dropdown is open, users can move through the available options by pressing the UP or DOWN arrow keys.
- Entering alphanumeric keys when the dropdown is open highlights the option that matches the entered letters. If there's no match, nothing changes.
- Clicking on an option or pressing SPACE or ENTER when an option is highlighted selects that option, updates the select value, and closes the dropdown.
- If the dropdown is open, pressing the ESC key closes it, providing a quick way to cancel the selection or close the dropdown.
- When the select element is focused when using a screen reader, the screen reader announces that it's a select element and provides information about the currently selected value for accessibility.
Using this information, let's build a custom dropdown.
How to Figure Out Which ARIA Attributes Are Required
While some elements' roles and accessible names are obvious, there are some that aren't. Whenever I need to find the appropriate role or ARIA-attibute for a component, I check out the W3 accessible names guide.
In this case, I know the custom dropdown should have a
role="options" but I don't know what role to assign the parent elements.
So to start, I locate the option Accessible Name Guidance by Role list. You can see that the Option is pointing to a combobox pattern.
The next thing I need to do is to read more on the combobox. According to this MDN page I understand that a combobox is a component that combines an input type element with a dropdown list and allows users to select from a list of options presented in the dropdown.
That sounds exactly like what the select element does. The combobox also needs to have an
aria-expanded attribute and a connecting popup element which will contain the list of options. According to the MDN page:
The popup element associated with a
comboboxcan be either a
In this example, we'll be using a
The combobox element needs to have an
aria-haspopup attributes, the value of these attributes will be the ID of the
How to Set Up the HTML
From the information gathered we will need a
option to setup our HTML.
In this example, the HTML will look like this:
In the code above, the button has a
role="combobox" and according to the MDN combobox article the
aria-expanded attribute is required when working with a combobox. The
aria-controls points to the id of the listbox which is
listbox. This associates the listbox with the combobox.
You can style the dropdown anyhow you want based on your requirements. Here's a sample style for my component:
In the code above, I have hidden the listbox element and added an active class that shows it. I have also added a current class that styles the highlighted option and an active class that styles the selected option.
This is how it looks:
It's easier to break down the features and work on them one at a time, and that's what we'll do here.
Toggle Dropdown Visibility
Clicking the combobox or pressing the
Enter keys on the keyboard (when the button is focused) should toggle the dropdown visibility:
In the code above we've created a toggleable dropdown button that responds to both keyboard and mouse interactions. The
toggleDropdown function adds an
active class to the dropdown.
Closing the Dropdown: Using the Esc Key
Pressing the Esc key or clicking outside the dropdown element should close the dropdown:
While it might seem like the
handleDocumentInteraction functions could be combined for simplicity, we'll keep them separate as these functions will handle more tasks later in the article.
In the code above we have updated the
handleKeyPress function to check for
Escape and also introduced a
handleDocumentInteraction function to close the dropdown if there's a click outside the dropdown element.
Dropdown Keyboard Interaction
DOWN arrow keys should open the dropdown. When the dropdown is open, these keys should allow navigation through the options, moving the selection up or down. Also, clicking on an option or pressing the
Enter keys while an option is focused should update the button's displayed value. This behavior aims to replicate the interaction of a standard select element.
In the updated code, we've added keyboard navigation and option selection. Here's the breakdown:
elementsobject now includes a reference to the option elements with the role "option."
- The dropdown can also be opened with keys like
- When the dropdown is open,
Escapecloses the dropdown,
ArrowDownmoves focus down the options,
ArrowUpmoves it up, and
Spaceselects the current option.
- Clicking an option or using keyboard input selects the option.
- The selected option is displayed in the button, and the aria-selected state of the option is updated.
activeclass is now added to the selected option
Fixing Option Visibility Issue
This looks good so far – but if you're following along and you test this code, you'll notice an issue: if an option is out of view and the down arrow is pressed the option isn't shown.
To fix this, you can use the
scrollIntoView method to ensure that the current option is scrolled into view when it is focused. Add it to the
focusCurrentOption like this:
Also when the user selects an option, the dropdown should close, the same way the select element works. Call the
toggleDropdown function in the
selectOptionByElement function like this:
Highlighting Options on Alphanumeric Key Press
Pressing an alphanumeric keys should highlight the option that starts with said character. And if the same character is pressed again then the next option should be highlighted and so on.
Running the code and testing it with both mouse and keyboard inputs should result in the expected behavior.
Enhancing Screen Reader Functionality
The last functionality I'm addressing in this article is the screen reader functionality.
For screen reader users, selecting an option should announce the selected option, Just like the default HTML select element. Update the HTML to have a div that will contain the content to be announced, like this:
In the code above, I've added an
announceOption function. The function is called whenever a user selects an option. The use of the assertive value in the
aria-live attribute signals to the screen reader to interrupt its current announcement and promptly announce the updated value.
Now when you test this with a screenreader, the screen reader announces the selected option as expected.
Here's a working example of the custom select on Codepen:
There's room for improvement in these features, such as adding multiple select options, autocomplete, and enhancing the overall look. Yet, my intention is for this article to be a helpful guide, encouraging you to keep accessibility in mind when building a component.
Thank you so much for reading this article, if you found it helpful consider sharing. Happy coding!