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.
Prerequisites
To follow along with this tutorial, you should have:
Basic HTML knowledge: Understand how HTML elements and attributes work.
Basic JavaScript knowledge: Familiarity with basic JavaScript concepts like functions, event handling, and DOM manipulation is helpful.
Understanding of ARIA: While the tutorial explains ARIA roles and attributes, having a basic understanding of accessibility concepts can be beneficial.
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 combobox can be either a listbox, tree, grid, or dialog element.
In this example, we’ll be using a listbox element.
The combobox element needs to have an aria-contols and aria-haspopup attributes, the value of these attributes will be the ID of the listbox element.
How to Set Up the HTML
From the information gathered we will need a combobox, listbox, and 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.
The CSS
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:
The Javascript
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 Space or 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 handleKeyPress and 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
Pressing the UP or 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 Space or 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:
The elements object now includes a reference to the option elements with the role “option.”
The dropdown can also be opened with keys like ArrowDown, ArrowUp, Space or Enter
When the dropdown is open, Escape closes the dropdown, ArrowDown moves focus down the options, ArrowUp moves it up, and Enter or Space selects 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.
active class 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:
Then use JavaScript to update the value in the alert:
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:
Wrapping Up
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.
If you’re looking for a package to use you should consider using React-Select or Vue3-select.
Thank you so much for reading this article, if you found it helpful consider sharing. Happy coding!