Select
A select lets people choose one option from a list, in a text field that opens a menu: a country, a role, a sort order. Use it when the options don't need to be visible at once; for a few that do, use radio buttons. See the M3 menus guidelines, where M3 describes the exposed dropdown menu.
Usage
Each option has an id, which is the select's value, and a text.
import { createSelect } from 'mtrl';
const select = createSelect({
label: 'Fruit',
name: 'fruit',
options: [
{ id: 'apple', text: 'Apple' },
{ id: 'banana', text: 'Banana' },
{ id: 'cherry', text: 'Cherry' },
],
});
select.on('change', ({ value }) => choose(value));
document.body.append(select.element);
<m-select name="fruit" label="Fruit">
<m-select-option value="apple">Apple</m-select-option>
<m-select-option value="banana">Banana</m-select-option>
<m-select-option value="cherry">Cherry</m-select-option>
</m-select>
<script type="module">
const select = document.querySelector('m-select');
select.addEventListener('change', (event) => choose(event.detail.value));
</script>
import { Select, SelectOption } from 'mtrl/react';
import { choose } from './app';
export function Example() {
return (
<Select onChange={(event) => choose(event.detail.value)} name="fruit" label="Fruit">
<SelectOption value="apple">Apple</SelectOption>
<SelectOption value="banana">Banana</SelectOption>
<SelectOption value="cherry">Cherry</SelectOption>
</Select>
);
}
<script setup lang="ts">
import { MSelect, MSelectOption } from 'mtrl/vue';
import { choose } from './app';
</script>
<template>
<MSelect @change="choose($event.detail.value)" name="fruit" label="Fruit">
<MSelectOption value="apple">Apple</MSelectOption>
<MSelectOption value="banana">Banana</MSelectOption>
<MSelectOption value="cherry">Cherry</MSelectOption>
</MSelect>
</template>
<script lang="ts">
import { Select, SelectOption } from 'mtrl/svelte';
import { choose } from './app';
</script>
<Select onchange={(event) => choose(event.detail.value)} name="fruit" label="Fruit">
<SelectOption value="apple">Apple</SelectOption>
<SelectOption value="banana">Banana</SelectOption>
<SelectOption value="cherry">Cherry</SelectOption>
</Select>
import { Select, SelectOption } from 'mtrl/solid';
import { choose } from './app';
export function Example() {
return (
<Select onChange={(event) => choose(event.detail.value)} name="fruit" label="Fruit">
<SelectOption value="apple">Apple</SelectOption>
<SelectOption value="banana">Banana</SelectOption>
<SelectOption value="cherry">Cherry</SelectOption>
</Select>
);
}
variant is filled (the default) or outlined, and density: 'compact' lowers the field,
as on a text field. A select fills its container's width.
Examples
A chosen option, a disabled one
value selects an option by its id. A disabled option stays in the list and can't be
chosen.
import { createSelect } from 'mtrl';
const select = createSelect({
label: 'Plan',
variant: 'outlined',
value: 'pro',
supportingText: 'Billed monthly',
options: [
{ id: 'free', text: 'Free' },
{ id: 'pro', text: 'Professional' },
{ id: 'enterprise', text: 'Enterprise', disabled: true },
],
});
document.body.append(select.element);
<m-select value="pro" variant="outlined" label="Plan" supporting-text="Billed monthly">
<m-select-option value="free">Free</m-select-option>
<m-select-option value="pro">Professional</m-select-option>
<m-select-option value="enterprise" disabled>Enterprise</m-select-option>
</m-select>
import { useState } from 'react';
import { Select, SelectOption } from 'mtrl/react';
export function Example() {
const [value, setValue] = useState("pro");
return (
<Select value={value} onChange={(event) => setValue(event.detail.value)} variant="outlined" label="Plan" supportingText="Billed monthly">
<SelectOption value="free">Free</SelectOption>
<SelectOption value="pro">Professional</SelectOption>
<SelectOption value="enterprise" disabled>Enterprise</SelectOption>
</Select>
);
}
<script setup lang="ts">
import { ref } from 'vue';
import { MSelect, MSelectOption } from 'mtrl/vue';
const value = ref("pro");
</script>
<template>
<MSelect v-model="value" variant="outlined" label="Plan" supporting-text="Billed monthly">
<MSelectOption value="free">Free</MSelectOption>
<MSelectOption value="pro">Professional</MSelectOption>
<MSelectOption value="enterprise" disabled>Enterprise</MSelectOption>
</MSelect>
</template>
<script lang="ts">
import { Select, SelectOption } from 'mtrl/svelte';
let value = $state("pro");
</script>
<Select bind:value variant="outlined" label="Plan" supportingText="Billed monthly">
<SelectOption value="free">Free</SelectOption>
<SelectOption value="pro">Professional</SelectOption>
<SelectOption value="enterprise" disabled>Enterprise</SelectOption>
</Select>
import { createSignal } from 'solid-js';
import { Select, SelectOption } from 'mtrl/solid';
export function Example() {
const [value, setValue] = createSignal("pro");
return (
<Select value={value()} onChange={(event) => setValue(event.detail.value)} variant="outlined" label="Plan" supportingText="Billed monthly">
<SelectOption value="free">Free</SelectOption>
<SelectOption value="pro">Professional</SelectOption>
<SelectOption value="enterprise" disabled>Enterprise</SelectOption>
</Select>
);
}
Required, in error
setError(true, message) shows a message in place of the supporting text, and clearError()
restores it. An action here chooses an option and clears the error.
import { createSelect } from 'mtrl';
const select = createSelect({
label: 'Role',
value: '',
required: true,
error: true,
supportingText: 'Choose a role',
options: [
{ id: 'admin', text: 'Administrator' },
{ id: 'editor', text: 'Editor' },
{ id: 'viewer', text: 'Viewer' },
],
});
document.body.append(select.element);
function chooseEditor() {
select.setValue('editor');
select.setError(false);
}
<m-select label="Role" supporting-text="Choose a role" required error>
<m-select-option value="admin">Administrator</m-select-option>
<m-select-option value="editor">Editor</m-select-option>
<m-select-option value="viewer">Viewer</m-select-option>
</m-select>
<script type="module">
const select = document.querySelector('m-select');
function chooseEditor() {
select.value = "editor";
select.toggleAttribute('error', false);
}
</script>
import { useState } from 'react';
import { Select, SelectOption } from 'mtrl/react';
export function Example() {
const [value, setValue] = useState("");
const [error, setError] = useState(true);
const chooseEditor = () => {
setValue("editor");
setError(false);
};
return (
<Select value={value} onChange={(event) => setValue(event.detail.value)} label="Role" supportingText="Choose a role" required error={error}>
<SelectOption value="admin">Administrator</SelectOption>
<SelectOption value="editor">Editor</SelectOption>
<SelectOption value="viewer">Viewer</SelectOption>
</Select>
);
}
<script setup lang="ts">
import { ref } from 'vue';
import { MSelect, MSelectOption } from 'mtrl/vue';
const value = ref("");
const error = ref(true);
function chooseEditor() {
value.value = "editor";
error.value = false;
}
</script>
<template>
<MSelect v-model="value" label="Role" supporting-text="Choose a role" required :error="error">
<MSelectOption value="admin">Administrator</MSelectOption>
<MSelectOption value="editor">Editor</MSelectOption>
<MSelectOption value="viewer">Viewer</MSelectOption>
</MSelect>
</template>
<script lang="ts">
import { Select, SelectOption } from 'mtrl/svelte';
let value = $state("");
let error = $state(true);
function chooseEditor() {
value = "editor";
error = false;
}
</script>
<Select bind:value label="Role" supportingText="Choose a role" required error={error}>
<SelectOption value="admin">Administrator</SelectOption>
<SelectOption value="editor">Editor</SelectOption>
<SelectOption value="viewer">Viewer</SelectOption>
</Select>
import { createSignal } from 'solid-js';
import { Select, SelectOption } from 'mtrl/solid';
export function Example() {
const [value, setValue] = createSignal("");
const [error, setError] = createSignal(true);
const chooseEditor = () => {
setValue("editor");
setError(false);
};
return (
<Select value={value()} onChange={(event) => setValue(event.detail.value)} label="Role" supportingText="Choose a role" required error={error()}>
<SelectOption value="admin">Administrator</SelectOption>
<SelectOption value="editor">Editor</SelectOption>
<SelectOption value="viewer">Viewer</SelectOption>
</Select>
);
}
The menu is mounted in the select's own element. In a scrolling or clipping container, such as
a drawer or a sheet, set menu: { container: document.body, maxHeight: '320px' }, or
layer: 'top' to show it in the browser's top layer. setOptions() replaces the options, for
a list loaded later. Validating a select in a form is planned for Examples.
API
Options
| Option | Type | Default | Description |
|---|---|---|---|
options |
SelectOption[] |
[] |
The options |
value |
string |
undefined |
The id of the option selected at first |
variant |
'filled' | 'outlined' |
'filled' |
The text field's style |
density |
'default' | 'compact' |
'default' |
The field height |
label |
string |
undefined |
The floating label |
name |
string |
undefined |
The input's name, for forms |
required |
boolean |
false |
Whether a selection is required |
disabled |
boolean |
false |
Whether the select is disabled |
supportingText |
string |
undefined |
Helper text under the field |
error |
boolean |
false |
The error state |
placement |
string |
'bottom-start' |
The menu's placement against the field |
menu |
{ container?, maxHeight?, autoFlip?, variant?, color? } |
undefined |
Where the menu is mounted, its maximum height, whether it flips above the field, and its variant and colors |
layer |
'top' |
undefined |
Renders the menu beside the field and shows it in the top layer; menu.container is then not used |
on |
{ change?, open?, close? } |
undefined |
Event handlers registered at creation |
class |
string |
undefined |
Additional CSS classes |
prefix |
string |
'mtrl' |
Prefix for CSS class names |
An option
| Option | Type | Default | Description |
|---|---|---|---|
id |
string |
required | The option's value |
text |
string |
required | Its text |
disabled |
boolean |
false |
Whether it can be chosen |
icon |
string |
undefined |
HTML, usually an SVG, before the text |
hasSubmenu / submenu |
boolean / SelectOption[] |
undefined |
Nested options; a select with them is a menu button rather than a combobox |
data |
unknown |
undefined |
Data of your own, carried with the option |
Methods
| Method | Parameters | Returns | Description |
|---|---|---|---|
getValue() / setValue(value) |
value: string | null | undefined |
string | null / SelectComponent |
The selected option's id |
clear() |
none | SelectComponent |
Clears the selection |
getText() |
none | string |
The selected option's text |
getSelectedOption() |
none | SelectOption | null |
The selected option |
getOptions() / setOptions(options) |
options: SelectOption[] |
SelectOption[] / SelectComponent |
The options |
open(interactionType?) / close() |
interactionType?: 'mouse' | 'keyboard' |
SelectComponent |
Opens or closes the menu; a disabled select does not open |
isOpen() |
none | boolean |
Whether the menu is open |
setDensity(density) / getDensity() |
density: 'default' | 'compact' |
SelectComponent / string |
The field height |
enable() / disable() |
none | SelectComponent |
The disabled state |
setError(error, message?) / clearError() |
error: boolean, message?: string |
SelectComponent |
The error state |
on(event, handler) / off(event, handler) |
event: string, handler: Function |
SelectComponent |
Adds or removes a listener |
destroy() |
none | void |
Destroys the select and releases its menu |
| Property | Type | Description |
|---|---|---|
element |
HTMLElement |
The root, which is the text field's |
textfield |
TextfieldComponent |
The text field |
menu |
MenuComponent |
The menu |
Events
| Event | Description | Data |
|---|---|---|
change |
The selection changed | { select, value, text, option, originalEvent?, preventDefault, defaultPrevented } |
open / close |
The menu opened or closed, however it was | { select, originalEvent?, preventDefault, defaultPrevented } |
The web component's change carries { value }.
Accessibility
- With flat options, the input is a select-only combobox (
role="combobox",aria-haspopup,aria-expanded,aria-controls) over alistboxof options. Focus stays on the input, which names the active option witharia-activedescendant. - The label names the input, as on a text field.
| Keys | Action |
|---|---|
Enter / Space |
Opens the menu, or chooses the active option |
Down / Up |
Opens the menu, or moves the active option |
Alt + Down / Alt + Up |
Opens the menu / chooses the active option |
Home / End |
The first / last option |
Page Down / Page Up |
Moves several options |
| A letter | The next option starting with what was typed |
Escape |
Closes the menu without choosing |
Tab |
Chooses the active option, and moves on |
Styling
The select is a text field: its root carries both sets of classes, and the menu is its child.
.mtrl-select, .mtrl-select--open { }
.mtrl-select.mtrl-textfield--filled, .mtrl-select.mtrl-textfield--outlined { }
.mtrl-select .mtrl-textfield__input, .mtrl-select .mtrl-textfield__label { }
.mtrl-select .mtrl-textfield__trailing-icon { }
.mtrl-select > .mtrl-menu { }