Skip to main content

Select

<zeno-select>FormsForm controlSource

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.

Continue

Nothing submitted yet.

<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​

PropertyAttributeTypeDefaultDescription
optionsoptionsSelectOption[][]
variantvariantreflects'outlined' | 'filled' | 'underlined''outlined'
sizesizereflects'sm' | 'md' | 'lg''md'
placeholderplaceholderstring'Select an option'
multiplemultiplereflectsbooleanfalseSeveral options can be chosen. value is then a comma-separated list.
searchablesearchablereflectsbooleanfalseShows a field in the dropdown that filters the options.
clearableclearablereflectsbooleanfalse
loadingloadingreflectsbooleanfalse
successsuccessreflectsbooleanfalse
emptyTextempty-textstring'No options available'
valuevalueFormValue'' From FormFieldMixin.
namenamereflectsstring From FormFieldMixin.
requiredrequiredreflectsbooleanfalse From FormFieldMixin.
disableddisabledreflectsbooleanfalse From FormFieldMixin.
readonlyreadonlyreflectsbooleanfalse From FormFieldMixin.
errorerrorstringAn error to show, for example one a server returned. From FormFieldMixin.
labellabelstringA visible label. An outside <label for> works as well. From FormFieldMixin.
hideRequiredIndicatorhide-required-indicatorbooleanfalse From FormFieldMixin.
touchedbooleanfalseTrue once the user has left the control or a submit was refused. From FormFieldMixin.
dirtybooleanfalseTrue once the user has changed the value. From FormFieldMixin.
defaultValueFormValue''The value the control goes back to when its form is reset. From FormFieldMixin.
formread onlyHTMLFormElement | null From FormFieldMixin.
labelsread onlyNodeList From FormFieldMixin.
validityread onlyValidityState From FormFieldMixin.
validationMessageread onlystring From FormFieldMixin.
willValidateread onlyboolean From FormFieldMixin.
isDisabledread onlybooleanTrue when the element or an ancestor fieldset disables it. From FormFieldMixin.
invalidread onlybooleanTrue when the control should look invalid. From FormFieldMixin.
testIdstringTest ID for automated testing From ZenoElement.

Methods​

MethodDescription
focus(options?: FocusOptions): voidFocus the component or its first focusable element From ZenoElement.
blur(): voidBlur the component or its focused element From ZenoElement.
open(): voidOpens the dropdown.
close(): voidCloses the dropdown.
toggle(): voidOpens the dropdown when it is closed and closes it when it is open.
checkValidity(): booleanReports 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(): booleanChecks validity and shows the result on the control. From FormFieldMixin.

Events​

EventDetailDescription
input{ value: FormValue; selected: SelectOption[] }The selection changed.
change{ value: FormValue; selected: SelectOption[] }The selection changed.
clearThe clear button was pressed.
openThe dropdown opened.
closeThe 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​

KeyWhat it does
TabMoves focus to the select. When the list is open, closes it and moves focus on.
ArrowDownArrowUpOpens the list. When the list is open, moves to the next or previous option. Disabled options are skipped and the movement wraps around.
HomeEndWhen 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.
EnterSpaceOpens 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.
EscapeCloses 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 label attribute or by a <label for> that points at its id.
  • It takes part in validation: checkValidity(), reportValidity() and setCustomValidity() 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> with role="combobox", aria-haspopup="listbox", aria-expanded and aria-controls. The list has role="listbox", and each option has role="option" with aria-selected and, when disabled, aria-disabled.
  • Focus stays on the trigger while the list is open, and the active option is given with aria-activedescendant. With searchable, focus moves to the search field, which is named “Filter options” and carries aria-activedescendant itself.
  • The label attribute renders a <label> for the trigger. Without it, the name comes from an outside <label for> or from aria-label on the element, and after that from the placeholder.
  • When the select is invalid, the trigger has aria-invalid="true" and aria-describedby points at the error text. The error text is not a live region. The trigger also has aria-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 variant type lists underlined, but the element has no styles for it. It has styles for ghost, 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 searchable gives a way to find an option by text.
  • The readonly attribute does nothing. A read-only select still opens and its value can still be changed.
  • The success state 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-controls then points at nothing, and the empty-text message is a plain <div> with no live region.
  • With multiple, the value is joined and split on commas. An option whose value contains a comma cannot be selected correctly.