/Documentation

Select

PublishedUpdated

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.

Vanilla
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);
Web Components
<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>
React
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>
  );
}
Vue
<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>
Svelte
<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>
SolidJS
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.

Vanilla
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);
Web Components
<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>
React
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>
  );
}
Vue
<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>
Svelte
<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>
SolidJS
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.

Vanilla
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);
}
Web Components
<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>
React
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>
  );
}
Vue
<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>
Svelte
<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>
SolidJS
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 a listbox of options. Focus stays on the input, which names the active option with aria-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 { }