I recently asked ChatGPT to: "write an accessible dropdown menu".
The response was:
<div class="dropdown">
<button
class="dropdown-button"
aria-expanded="false"
aria-haspopup="true"
>
Menu
</button>
<ul class="dropdown-list" aria-hidden="true">
<li><a href="#">Option 1</a></li>
<li><a href="#">Option 2</a></li>
<li><a href="#">Option 3</a></li>
</ul>
</div>
At first glance, this is a surprisingly good component as it uses some core ARIA attributes correctly.
But is this the right approach?
Ideally, we should always try to use native elements to build components rather than resorting to ARIA. As per the W3C's "Using ARIA":
If you can use a native HTML element or attribute with the semantics and behavior you require already built in, instead of re-purposing an element and adding an ARIA role, state or property to make it accessible, then do so.
A non-native solution means that the component has been built using elements differently than the intended purpose - in this case, a button is used to create a dropdown menu.
Let's look at a native vs non-native example and compare their accessibility.
1. A native example
A native dropdown solution would involve using the <label>
, <select>
and <option>
elements.
<label for="fruit">Favourite fruit</label>
<select id="fruit">
<option>Choose an option</option>
<option>Apple</option>
<option>Apricot</option>
<option>Avocado</option>
</select>
1.1: Role
The <label>
, <select>
and <option>
elements each have a specific semantic meaning that is understood by accessibility APIs.
1.2: Name
If the <label>
and <select>
elements are given matching for
and id
values, the <select>
will then have an accessible name.
<label for="fruit">Favourite fruit</label>
<select id="fruit">
<option>Choose an option</option>
<option>Apple</option>
<option>Apricot</option>
</select>
1.3: Properties
The <select>
element is defined with a native hasPopup
property, with a value of menu
.
The haspopup
property indicates the element can trigger a popup. The menu
value specifies that the popup will be a menu.
1.4: Current state
The <select>
element will have a state of expanded: false
until the user expands it.
1.5: Current value
If an <option>
is selected, it will be defined in the accessibility tree as the value - i.e. “Apple”.
1.6: Keyboard accessible
A range of pre-defined keystrokes can be used to interact with the <select>
and <option>
elements.
- SPACE - View all options in a dropdown
- ↓ and ↑ - Navigate through dropdown options
- ENTER or SPACE - Select a dropdown option
2. A non-native example
The ChatGPT example is a non-native component as it uses the <button>
, <ul>
and <li>
elements to create a dropdown - like the Bootstrap button.
<button>Choose your favourite fruit</button>
<ul>
<li>Apple</li>
<li>Apricot</li>
<li>Avocado</li>
</ul>
Let's look at this component before any accessibility is manually added.
2.1: Role
The <button>
element will be defined in the accessibility tree as button
, which could confuse some assistive technology users.
The <ul>
and <li>
elements have roles of list
and listitem
, but these are not intended for dynamically displayed dropdown information.
2.2: Name
This component will have an accessible name of “Choose your favourite fruit” in the accessibility tree, which is acceptable.
2.3: Properties
There are no additional properties assigned to the elements to provide additional context.
2.4: Current state
There is no native way to inform users about the dropdown’s current state.
2.5: Current value
There is no native way to inform users of the currently selected value. If a screen reader user returns to the component later, there is no indication that an option has been selected.
2.6: Keyboard accessible
The component will have no native keystrokes defined. So, it is not keyboard accessible.
Scorecard?
Native solution | Non-native solution | |
---|---|---|
Name | Available | Available |
Role | Combobox |
Button (Incorrect) |
Properties | hasPopup: menu |
Not available |
Current state | Expanded: false/true |
Not available |
Current value | Available | Not available |
Keyboard accessible | Available | Not available |
All of these problems can be fixed using a combination of ARIA and JavaScript. But it takes a lot of additional work.
Here are some resources that can help:
Bottom line: the ChatGPT solution could be made to work, but using a native solution where possible would be far more efficient and effective.