Select
A field that picks one or several options from a list that opens beside it.
import '@usezeno/tokens/css';
import '@usezeno/core/web-components';
Examples
Basic
Options are set with the options property. Each option has a value, a label and, when needed, disabled.
<zeno-select label="Country" placeholder="Choose a country"></zeno-select>
document.querySelector('zeno-select').options = [
{ value: 'ca', label: 'Canada' },
{ value: 'mx', label: 'Mexico' },
{ value: 'us', label: 'United States' },
{ value: 'aq', label: 'Antarctica', disabled: true },
];
Search, clear and multiple
With searchable the list has a field that filters the options. With clearable a button removes the selection. With multiple the list stays open and the value property is one string with the chosen values separated by commas.
<zeno-select label="Time zone" value="utc" searchable clearable></zeno-select>
<zeno-select label="Working days" value="mon,wed" multiple clearable></zeno-select>
const selects = document.querySelectorAll('zeno-select');
selects[0].options = [
{ value: 'utc', label: 'UTC' },
{ value: 'lisbon', label: 'Lisbon' },
{ value: 'nairobi', label: 'Nairobi' },
{ value: 'tokyo', label: 'Tokyo' },
{ value: 'lima', label: 'Lima' },
];
selects[1].options = [
{ value: 'mon', label: 'Monday' },
{ value: 'tue', label: 'Tuesday' },
{ value: 'wed', label: 'Wednesday' },
{ value: 'thu', label: 'Thursday' },
{ value: 'fri', label: 'Friday' },
];
Sizes and variants
<zeno-select label="Small" size="sm" value="pdf"></zeno-select>
<zeno-select label="Medium" size="md" value="pdf"></zeno-select>
<zeno-select label="Large" size="lg" value="pdf"></zeno-select>
<zeno-select label="Filled" variant="filled" value="pdf"></zeno-select>
document.querySelectorAll('zeno-select').forEach(select => {
select.options = [
{ value: 'pdf', label: 'PDF' },
{ value: 'csv', label: 'CSV' },
{ value: 'txt', label: 'Plain text' },
];
});
States
A disabled or loading select cannot be opened. The error text shows below the field and marks it as invalid.
<zeno-select label="Disabled" value="weekly" disabled></zeno-select>
<zeno-select label="Loading" placeholder="Loading options" loading></zeno-select>
<zeno-select label="With an error" error="Choose how often to send the report."></zeno-select>
<zeno-select label="Success" value="weekly" success></zeno-select>
document.querySelectorAll('zeno-select').forEach(select => {
select.options = [
{ value: 'daily', label: 'Every day' },
{ value: 'weekly', label: 'Every week' },
{ value: 'monthly', label: 'Every month' },
];
});
In a form
A required select with no value stops the submit. After that the select shows a message below the field.
<form>
<zeno-select name="plan" label="Plan" placeholder="Choose a plan" required></zeno-select>
<zeno-button type="submit">Continue</zeno-button>
<p data-result>Nothing submitted yet.</p>
</form>
document.querySelector('zeno-select').options = [
{ value: 'free', label: 'Free' },
{ value: 'team', label: 'Team' },
{ value: 'company', label: 'Company' },
];
const form = document.querySelector('form');
form.addEventListener('submit', event => {
event.preventDefault();
const sent = [...new FormData(form)].map(([name, value]) => name + '=' + value);
document.querySelector('[data-result]').textContent = 'Submitted: ' + sent.join('&');
});
Attributes and properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
options | options | SelectOption[] | [] | |
variant | variantreflects | 'outlined' | 'filled' | 'underlined' | 'outlined' | |
size | sizereflects | 'sm' | 'md' | 'lg' | 'md' | |
placeholder | placeholder | string | 'Select an option' | |
multiple | multiplereflects | boolean | false | Several options can be chosen. value is then a comma-separated list. |
searchable | searchablereflects | boolean | false | Shows a field in the dropdown that filters the options. |
clearable | clearablereflects | boolean | false | |
loading | loadingreflects | boolean | false | |
success | successreflects | boolean | false | |
emptyText | empty-text | string | 'No options available' | |
value | value | FormValue | '' | From FormFieldMixin. |
name | namereflects | string | From FormFieldMixin. | |
required | requiredreflects | boolean | false | From FormFieldMixin. |
disabled | disabledreflects | boolean | false | From FormFieldMixin. |
readonly | readonlyreflects | boolean | false | From FormFieldMixin. |
error | error | string | An error to show, for example one a server returned. From FormFieldMixin. | |
label | label | string | A visible label. An outside <label for> works as well. From FormFieldMixin. | |
hideRequiredIndicator | hide-required-indicator | boolean | false | From FormFieldMixin. |
touched | boolean | false | True once the user has left the control or a submit was refused. From FormFieldMixin. | |
dirty | boolean | false | True once the user has changed the value. From FormFieldMixin. | |
defaultValue | FormValue | '' | The value the control goes back to when its form is reset. From FormFieldMixin. | |
formread only | HTMLFormElement | null | From FormFieldMixin. | ||
labelsread only | NodeList | From FormFieldMixin. | ||
validityread only | ValidityState | From FormFieldMixin. | ||
validationMessageread only | string | From FormFieldMixin. | ||
willValidateread only | boolean | From FormFieldMixin. | ||
isDisabledread only | boolean | True when the element or an ancestor fieldset disables it. From FormFieldMixin. | ||
invalidread only | boolean | True when the control should look invalid. From FormFieldMixin. | ||
testId | string | Test ID for automated testing From ZenoElement. |
Methods
| Method | Description |
|---|---|
focus(options?: FocusOptions): void | Focus the component or its first focusable element From ZenoElement. |
blur(): void | Blur the component or its focused element From ZenoElement. |
open(): void | Opens the dropdown. |
close(): void | Closes the dropdown. |
toggle(): void | Opens the dropdown when it is closed and closes it when it is open. |
checkValidity(): boolean | Reports whether the value is valid without showing anything. Use reportValidity() or validate() to show the result as well. From FormFieldMixin. |
reportValidity(): boolean | From FormFieldMixin. |
setCustomValidity(message: string): void | From FormFieldMixin. |
validate(): boolean | Checks validity and shows the result on the control. From FormFieldMixin. |
Events
| Event | Detail | Description |
|---|---|---|
input | { value: FormValue; selected: SelectOption[] } | The selection changed. |
change | { value: FormValue; selected: SelectOption[] } | The selection changed. |
clear | The clear button was pressed. | |
open | The dropdown opened. | |
close | The dropdown closed. |
Slots
This element has no slots.
CSS parts
| Part |
|---|
base |
clear |
dropdown |
empty |
error |
icon |
label |
loading |
option |
options |
search |
trigger |
value |
CSS custom properties
This element documents no custom properties of its own. Style it through its parts and through the tokens it reads.
The element's own source reads no tokens.
Types
interface SelectOption {
value: string;
label: string;
disabled?: boolean;
}
type FormValue = string | number | boolean | Date | null | undefined;
Accessibility
Keyboard
| Key | What it does |
|---|---|
| Tab | Moves focus to the select. When the list is open, closes it and moves focus on. |
| ArrowDownArrowUp | Opens the list. When the list is open, moves to the next or previous option. Disabled options are skipped and the movement wraps around. |
| HomeEnd | When the list is open, moves to the first or last option that is not disabled. In the search field these keys move the text cursor. |
| EnterSpace | Opens the list. When the list is open, chooses the active option. With multiple the list stays open. In the search field, Space types a space. |
| Escape | Closes the list and returns focus to the trigger. |
Forms
- With a
name, the element adds its value to the data of the form it is in, and is left out while it is disabled. - It is labelled by its
labelattribute or by a<label for>that points at itsid. - It takes part in validation:
checkValidity(),reportValidity()andsetCustomValidity()work, and an invalid element blocks the submit and matches:invalid. - It follows a
<fieldset disabled>around it and goes back to its first value when the form is reset. - The messages it writes itself, such as “Please fill out this field.”, are in English only.
Notes
- The trigger is a native
<button>withrole="combobox",aria-haspopup="listbox",aria-expandedandaria-controls. The list hasrole="listbox", and each option hasrole="option"witharia-selectedand, when disabled,aria-disabled. - Focus stays on the trigger while the list is open, and the active option is given with
aria-activedescendant. Withsearchable, focus moves to the search field, which is named “Filter options” and carriesaria-activedescendantitself. - The
labelattribute renders a<label>for the trigger. Without it, the name comes from an outside<label for>or fromaria-labelon the element, and after that from theplaceholder. - When the select is invalid, the trigger has
aria-invalid="true"andaria-describedbypoints at the error text. The error text is not a live region. The trigger also hasaria-required. - The list opens in the top layer with the popover API. It closes on a press outside, when focus leaves the element, and on Escape. Escape is cancelled, so a dialog around the select stays open.
Tests and known gaps
- Tested in a real browser by
select.test.ts. - Checked with axe-core in the light and the dark theme.
- Has no Storybook story.
34 of the 56 properties, methods, events, slots and parts above have no description in the source
- the property
options - the property
variant - the property
size - the property
placeholder - the property
clearable - the property
loading - the property
success - the property
emptyText - the property
value - the property
name - the property
required - the property
disabled - the property
readonly - the property
hideRequiredIndicator - the property
form - the property
labels - the property
validity - the property
validationMessage - the property
willValidate - the method
reportValidity() - the method
setCustomValidity() - the part
base - the part
clear - the part
dropdown - the part
empty - the part
error - the part
icon - the part
label - the part
loading - the part
option - the part
options - the part
search - the part
trigger - the part
value
Known gaps:
- The
varianttype listsunderlined, but the element has no styles for it. It has styles forghost, which the type does not list. - The clear button is 20 by 20 pixels. WCAG 2.2 asks for a target of at least 24 by 24 pixels.
- Typing a letter does not move to the option that starts with it. Only
searchablegives a way to find an option by text. - The
readonlyattribute does nothing. A read-only select still opens and its value can still be changed. - The
successstate is shown by border colour only. It adds no text and no ARIA state. - The clear button label, the search field label, the fallback list label and the required message are fixed English strings. They cannot be translated.
- When no option matches, the listbox is not rendered.
aria-controlsthen points at nothing, and theempty-textmessage is a plain<div>with no live region. - With
multiple, the value is joined and split on commas. An option whosevaluecontains a comma cannot be selected correctly.