Select fields are very common form fields. They allow the user to select one or more options from a list of options.
The native select element does a lot in terms of interactivity and accessibility. However, it leaves a lot to be desired in terms of styling and customization. Formwerk tries to address that by providing the same accessability and interactive behaviors to your custom select component so you don’t have to compromise for the sake of styling.
Note that customizable selects are coming soon to the browser and Formwerk will leverage that when it becomes available.
Features
Labeling, descriptions, error message displays are automatically linked to input and label elements with aria-* attributes.
Support for single/multiple selections.
Support for option groups.
Support for option searching with starting characters.
First-class Support for [popover] popups for dropdowns/menus.
When focusing the trigger element, and menu is closed:
Key
Description
⎵Space
Opens the options menu.
↩Enter
Opens the options menu.
↓ArrowDown
Opens the options menu.
↑ArrowUp
Opens the options menu.
When focusing an option in the menu, and menu is open:
Key
Description
⎵Space
Selects the option, if multiple toggles the option selection state.
↩Enter
Selects the option, if multiple toggles the option selection state.
↓Arrow Down
Focuses the next option if available.
↑Arrow Up
Focuses the previous option if available.
⇧Shift + ↓Arrow Down
Focuses the next option and selects it.
⇧Shift + ↑Arrow Up
Focuses the previous option and selects it.
Home
Focuses the first option.
⇧Shift + Home
Focuses the first option. If multiple is enabled, selects all options between the last focused option and the first option.
⇞Page Up
Focuses the first option.
⇧Shift + ⇞Page Up
Focuses the first option. If multiple is enabled, selects all options between the last focused option and the first option.
End
Focuses the last option.
⇧Shift + End
Focuses the last option. If multiple is enabled, selects all options between the last focused option and the last option.
⇟Page Down
Focuses the last option.
⇧Shift + ⇟Page Down
Focuses the last option. If multiple is enabled, selects all options between the last focused option and the last option.
⌘Command + A
If multiple is enabled, selects all options. If all are already selected, deselects all options.
Most people aren’t aware of these keyboard shortcuts, but they do make a big difference in the user experience for keyboard users.
Anatomy
Label
Label
Value
Selected value
Trigger
Help text
Description or Error Message
Label
Value
Option 1
Option 2
Option item
Option 3
Popup
Building a Select Field
The select field is one of the most complex fields with multiple moving parts. It is what Formwerk considers a “compound component” because it is made up of an ecosystem of components that work together to create a single field.
From the anatomy diagram above, you can see that the core select field is made up of the following parts:
Trigger: A trigger element that is used to open the options menu, doubles as the selected value display.
Popup: An options menu that contains the list of options.
Option: An option component that represents each option in the list.
Option Group: Not illustrated above, an option group that groups options together based on some categorization.
Formwerk handles all of these parts and abstracts them into the following component ecosystem:
Select: Contains the trigger and popup parts. You will use useSelect to build it.
Option: Represents an option in the list. You will use useOption to build it.
OptionGroup: Represents a group of options in the list. You will use useOptionGroup to build it.
Notice that the popup is not a separate component. This is because popups today can be done in different ways, so Formwerk while offers the open state along with some accessability attributes, it does not have a specific popup component implementation. But we offer out of the box support for Popover API if you happen to use one for the popup element.
Building an Option Component
We can start by using useOption to create our option component. This component will be responsible for rendering each option in the list.
Notice that we are using the OptionProps type to define the props for our component. This type is generic and allows you to specify the type of the option value. In this case, we are using any to represent any type of value.
The label is available inside OptionProps and we will display it to the user.
Building a Select Component
Next, you will use useSelect to create our select component. This component will be responsible for rendering the trigger and popup parts.
The useSelect composable returns binding objects for some of the elements shown in the anatomy, you will use v-bind to bind them to the corresponding DOM elements.
Building an Option Group Component
Building an option group component is similar to building an option component. You will use useOptionGroup to create the component.
Validation
Because selects in Formwerk are a fully custom component, it doesn’t support any HTML validation attributes. You can however, use Standard Schemas to validate the value of the select.
In the future when customizable selects are available in the browser, Formwerk will leverage that to provide better validation support for the native HTML constraints.
Standard Schema
useSelect supports Standard Schema validation through the schema prop. This includes multiple providers like Zod, Valibot, Arktype, and more.
Usage
Multiple Select
The useSelect composable accepts a multiple prop, it adjusts behaviors to what users expect out of a multi-select field.
Disabled
Use disabled to mark fields as non-interactive, disabled fields are not validated and are not submitted.
If you need to prevent the user from interacting with the field while still allowing it to submit, consider using readonly instead.
You can also mark individual options as disabled by passing a disabled prop to the OptionItem component. Note that disabled options are skipped from the focus order when using shortcuts or the search functionality.
Readonly
Readonly fields are validated and submitted, but they do not accept user input. The field is still focusable and the popup is still openable. For more info, check the MDN.
Styling
When styling the select field, you need to be aware of the following…
Option attributes
When an option is selected, it will receive the aria-selected="true" attribute if the select field is a single choice select.
If the select field is a multi-choice select, the selected options will receive the aria-checked="true" attribute instead.
Trigger attributes
The trigger element will receive the aria-expanded attribute. So you can style the trigger according to the popover state if needed.
Popover Attributes
The popover (listbox) element will receive the aria-activedescendant attribute when an option is focused that corresponds to the id of the focused option.
Furthermore, the popover element will receive the aria-multiselectable attribute if the select field is a multi-choice select.
API
Option
Props
These are the properties that can be passed to the useOption composable.
Name
Type
Description
disabled
Whether the option is disabled.
label
The label text for the option.
value
The value associated with this option.
Returns
These are the properties in the object returned by the useOption composable.
Name
Type
Description
isDisabled
Whether the option is disabled.
isSelected
Whether the option is selected.
optionEl
Reference to the option element.
optionProps
Props for the option element.
Option Group
Props
These are the properties that can be passed to the useOptionGroup composable.
Name
Type
Description
disabled
Whether the option group is disabled.
label
The label text for the option group.
Returns
These are the properties in the object returned by the useOptionGroup composable.