and connect it to the select using aria-labelledby.
Finally, we need to tell Assistive Technologies to ignore the custom select, using aria-hidden=true. That way, only the native select is announced by them, no matter what.
Main job role
This takes us to styling, where we not only make things look pretty, but where we handle the switch from one select to the other. We need just a few new declarations to make all the magic happen.
First, both native and custom selects must have the same width and height. This ensures people don’t see major differences in the layout when a switch happens.
.selectNative,
.selectCustom {
position: relative;
width: 22rem;
height: 4rem;
}
There are two selects, but only one can dictate the space that holds them. The other needs to be absolutely positioned to take it out of the document flow. Let’s do that to the custom select because it’s the “replacement” that’s used only if it can be. We’ll hide it by default so it can’t be reached by anyone just yet.
.selectCustom {
position: absolute;
top: 0;
left: 0;
display: none;
}
Here comes the “funny” part. We need to detect if someone is using a device where hover is part of the primary input, like a computer with a mouse. While we typically think of media queries for responsive breakpoints or checking feature support, we can use it to detect hover support too using @media query (hover :hover), which is supported by all major browsers. So, let’s use it to show the custom select only on devices that have hover:
@media (hover: hover) {
.selectCustom {
display: block;
}
}
Great, but what about people who use a keyboard to navigate even in devices that have hover? What we’ll do is hide the custom select when the native select is in focus. We can reach for an adjacent Sibling combinatioron (+). When the native select is in focus, hide the custom select next to it in the DOM order. (This is why the native select should be placed before the custom one.)
@media (hover: hover) {
.selectNative:focus + .selectCustom {
display: none;
}
}
That’s it! The trick to switch between both selects is done! There are other CSS ways to do it, of course, but this works nicely.
Last, we need a sprinkle of JavaScript. Let’s add some event listeners:
One for click events that trigger the custom select to open and reveal the optionsOne to sync both selects values. When one select value is changed, the other select value updates as wellOne for basic keyboard navigation controls, like navigation with Up and Down keys, selecting options with the Enter or Space keys, and closing the select with Esc
CodePen Embed Fallback
Usability testing
I conducted a very small usability test where I asked a few people with disabilities to try the hybrid select component. The following devices and tools were tested using the latest versions of Chrome (81), Firefox (76) and Safari (13):
Desktop device using mouse onlyDesktop device using keyboard onlyVoiceOver on MacOS using keyboardNVDA on Windows using keyboardVoiceOver on iPhone and iPad using Safari
All these tests worked as expected, but I believe this could have even more usability tests with more diverse people and tools. If you have access to other devices or tools — such as JAWS, Dragon, etc. — please tell me how the test goes.
An issue was found during testing. Specifically, the issue was with the VoiceOver setting “Mouse pointers: Moves Voice Over cursor.” If the user opens the select with a mouse, the custom select will be opened (instead of the native) and the user won’t experience the native select.
What I most like about this approach is how it uses the best of both worlds without compromising the core functionality:
Users on mobile and tablets get the native select, which generally offers a better user experience than a custom select, including performance benefits.Keyboard users get to interact with the native select the way they would expect.Assistive Technologies can interact with the native select like normal.Mouse users get to interact with the enhanced custom select.
This approach provides essential native functionality for everyone without the extra huge code effort to implement all the native features.
Don’t get me wrong. This technique is not a one-size-fits-all solution. It may work for simple selects but probably won’t work for cases that involve complex interactions. In those cases, we’d need to use ARIA and JavaScript to complement the gaps and create a truly accessible custom select.
A note about selects that look like menus
Let’s take a look back at the third Dropdown-land scenario. If you recall, it’s a dropdown that always has a checked option (e.g. sorting some content). I classified it in the gray area, as either a menu or a select.
Here’s my line of thought: Years ago, this type of dropdown was implemented mostly using a native . Nowadays, it is common to see it implemented from scratch with custom styles (accessible or not). What we end up with is a select element that looks like a menu.
A is a type of menu. Both have similar semantics and behavior, especially in a scenario that involves a list of options where one is always checked. Now, let me mention the WCAG 3.2.2 On Input (Level A) criterion:
Changing the setting of any user interface component should not automatically cause a change of context unless the user has been advised of the behavior before using the component.
Let’s put this in practice. Imagine a sortable list of students. Visually, it may be obvious that sorting is immediate, but that’s not necessarily true for everyone. So, when using , we risk failing the WCAG guideline because the page content changed, and ignificantly re-arranging the content of a page is considered a change of context.
To ensure the criterion success, we must warn the user about the action before they interact with the element, or include a immediately after the select to confirm the change.
Sort students
(Immediate effect upon selection)
…
That said, using a or building a custom menu are both good approaches when it comes to simple menus that change the page content. Just remember that your decision will dictate the amount of work required to make the component fully accessible. This is a scenario where the hybrid select approach could be used.
Final words
This whole idea started as an innocent CSS trick but, after all of this research, I was reminded once more that creating unique experiences without compromising accessibility is not an easy task.
Building truly accessible select components (or any kind of dropdown) is harder than it looks. WCAG provides excellent guidance and best practices, but without specific examples and diverse practical uses cases, the guidelines are mostly aspirational. That’s not to mention the fact that ARIA support is tepid and that native elements look and behave differently across browsers.
The “hybrid” select is just another attempt to create a good looking select while getting as many native features as possible. Don’t look at this technique experiment as an excuse to downplay accessibility, but rather as an attempt to serve both worlds. If you have the resources, time and the needed skills, please do it right and make sure to test it with different users before shipping your component to the world.
P.S. Remember to use a proper name when making a “dropdown” component. ?