HTML provides a variety of input elements.
<select> allows users to select one of many options. Accessibility tools like screen readers readily recognize these “native” input elements. However the downside of these elements is they are not fully customizable across browsers and platforms. Not being able to customize will be a problem if one is working a site that follows a specific design language like Material Design. One way around this limitation would be implement your own widget. This blog post is a walk-through of implementing such a widget as a Vue.js component which is accessible as well. Inspiration for this component MDN’s guide to implementing a custom widget.
Before we begin let us define our Minimum Viable Product for our component.
- Behaves similar to
- No dependencies apart from Vue.js
- Keyboard accessible
- Screen reader accessible
The basic skeleton structure for our widget is very simple.
<div> acts as the container of the component which houses a
<span> and a
<ul>. We will use the
<span> to show the selected option and use the
<ul> to show the options.
Styling the component
First thing to note is the
scoped attribute of the style tag. Vue.js takes care of keeping the styles defined by this component are local to this and does not leak out and conflict with others. One of the first things that we do is define some variables and reset the browser defaults for
margin, padding, box-sizing, and
user-select. Now we start defining CSS classes that we will for our component.
First class that we define is
.select__dropdown. This will be assigned to our container
<div>. We remove the
outline set the
position attribute to
relative, this will allows us to position the list of options. Next we define the chevron for our drop down using the pseudo-selector
:after using the classes
.select__dropdown--close. CSS-Tricks has a nice article on how to create triangles. Next we will define the styling for the
<span> using the class
.select__value. When our component is in focus we want to provide visual feedback, this is accomplished via the pseudo-selector
.select__optionlist defines the styling for the list container
<ul>. We set the
z-index to a high value so that it overlaps the down arrow. Moving further we define the closed state of the drop down list using
.select__optionlist--close where we set the
hidden. We are using the attribute visibility instead of
display is to support accessibility. To style the individual options we will define the class
.select__option. We use the
:hover pseudo-selector to provide visual feedback for mouse hover. When a certain option is selected we hightlight that using
Now our HTML should look like this.
Making it all work
Mouse and keyboard events that we have to handle to make our component to be functional are:
onClickof the container
onClickof the options
Space, Enter, Up arrow, and
Down arrowkeyboard events on the container
We also have to add Accessibility attributes to our markup. These attributes enables screen readers to interpret VueSelect component correctly. Component after adding the event listeners and accessibility attributes should look something like this.
VueSelect supports two properties,
value of type
items and array of
Strings. As the name suggests,
value is the property that the parent component will pass to us with the preselected value or an empty string and
items are the options that the user can select from. Whenever user selects an option we will emit two events
update:value. Custom event
input is straight forward to understand. Second event
update:value is emitted to support the new Vue 2.3.0+ feature the .sync modifier.
To orchestrate our component we need these data properties:
isOpen— denotes if the drop down is open or closed.
mutableValue— the current selected value.
selectedIdx— index within the
hoverIndex— maintains the state of option on which the user is “hovering” when navigating via the keyboard.
The completed code for this component can be found on Github at https://github.com/cx0der/vueselect. Note that this component is not production ready, this is something I wrote to understand how to implement accessible elements in Vue.