Select

ui-select renders a native <select> and copies its <option> children into it, so you get the platform's own picker, keyboard handling and mobile UI with the theme's styling. It is form-associated. The chosen option's value is submitted under the element's name.

Without a placeholder, the select behaves like a native one. The option marked selected, or else the first option, is chosen from the start. It fills the width of its container. width gives it a fixed width instead.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Select demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/select.html"></import-element>

  <ui-select aria-label="Fruit" width="48">
    <option value="apple">Apple</option>
    <option value="banana">Banana</option>
    <option value="blueberry">Blueberry</option>
    <option value="grapes">Grapes</option>
  </ui-select>
</body>
</html>

Examples

Placeholder

placeholder adds an empty first entry that cannot be picked again once an option is chosen. Combine it with required to make the user choose.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Select demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/select.html"></import-element>

  <ui-select aria-label="Time zone" placeholder="Select a time zone" width="60">
    <option value="utc">UTC</option>
    <option value="cet">Central European Time</option>
    <option value="jst">Japan Standard Time</option>
  </ui-select>
</body>
</html>

Selected option

Set value on the element, or selected on an option.

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Select demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-row {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: calc(var(--spacing) * 4);
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/select.html"></import-element>

  <div class="demo-row">
    <ui-select aria-label="Role" value="editor" width="40">
      <option value="viewer">Viewer</option>
      <option value="editor">Editor</option>
      <option value="admin">Admin</option>
    </ui-select>
    <ui-select aria-label="Status" width="40">
      <option value="todo">To do</option>
      <option value="doing" selected>In progress</option>
      <option value="done">Done</option>
    </ui-select>
  </div>
</body>
</html>

Small

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Select demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/select.html"></import-element>

  <ui-select aria-label="Rows per page" size="sm" value="25" width="20">
    <option value="10">10</option>
    <option value="25">25</option>
    <option value="50">50</option>
  </ui-select>
</body>
</html>

Disabled

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Select demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-row {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: calc(var(--spacing) * 4);
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/select.html"></import-element>

  <div class="demo-row">
    <ui-select aria-label="Region" disabled width="40">
      <option value="eu">Europe</option>
    </ui-select>
    <ui-select aria-label="Plan" width="40">
      <option value="free">Free</option>
      <option value="pro">Pro</option>
      <option value="enterprise" disabled>Enterprise</option>
    </ui-select>
  </div>
</body>
</html>

Width

By default the select fills its container, like the inputs it sits next to in a form. width sets a fixed width in spacing units, so width="40" is calc(var(--spacing) * 40).

Previewindex.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Select demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
  <style>
    .demo-stack {
      display: grid;
      gap: calc(var(--spacing) * 4);
    }
  </style>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/select.html"></import-element>

  <div class="demo-stack">
    <ui-select aria-label="Fills the container">
      <option value="full">Fills the container</option>
    </ui-select>
    <ui-select aria-label="Fixed width" width="40">
      <option value="fixed">width="40"</option>
    </ui-select>
  </div>
</body>
</html>

Options from state

The select watches its <option> children, so options rendered with repeat stay in sync.

Previewindex.htmlselect-state-demo.html

value:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Select demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/select.html"></import-element>

  <import-element from="./select-state-demo.html"></import-element>
  <select-state-demo></select-state-demo>
</body>
</html>
<custom-element name="select-state-demo">
  <script>
    const picker = useState({
      languages: [
        { code: "en", name: "English" },
        { code: "de", name: "Deutsch" },
        { code: "id", name: "Bahasa Indonesia" },
      ],
      chosen: "en",
    });

    function changed(select) {
      picker.chosen = select.value;
    }
  </script>

  <style>
    :host {
      display: flex;
      align-items: center;
      gap: calc(var(--spacing) * 3);
    }
    p {
      margin: 0;
      font-size: var(--text-sm);
      color: var(--muted-foreground);
    }
  </style>

  <ui-select aria-label="Language" value="en" onchange="changed(this)">
    <option repeat="#picker.languages" as="language" key="code" _value="#language.code"><slot name="#language.name"></slot></option>
  </ui-select>
  <p>value: <slot name="#picker.chosen"></slot></p>
</custom-element>

In a form

The chosen value is submitted under name. required with a placeholder blocks submission until an option is chosen, and reset returns to the value attribute (or the placeholder).

Previewindex.htmlselect-form-demo.html
Save Reset
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Select demo</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
  <script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
</head>
<body>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
  <import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/select.html"></import-element>

  <import-element from="./select-form-demo.html"></import-element>
  <select-form-demo></select-form-demo>
</body>
</html>
<custom-element name="select-form-demo">
  <script>
    const result = useState({ text: "Submit the form to see its data." });

    function submit(event) {
      event.preventDefault();
      result.text = [...new FormData(event.target)].map(([name, value]) => `${name}=${value}`).join("\n");
    }

    function reset() {
      result.text = "The form was reset.";
    }
  </script>

  <style>
    :host {
      display: grid;
      gap: calc(var(--spacing) * 4);
      width: 100%;
      max-width: calc(var(--spacing) * 80);
    }
    form {
      display: grid;
      gap: calc(var(--spacing) * 3);
    }
    .actions {
      display: flex;
      gap: calc(var(--spacing) * 2);
    }
    output {
      display: block;
      padding: calc(var(--spacing) * 3);
      border-radius: var(--radius-md);
      background: var(--muted);
      font-family: var(--font-mono);
      font-size: var(--text-xs);
      white-space: pre-wrap;
    }
  </style>

  <form onsubmit="submit(event)" onreset="reset()">
    <ui-select name="country" aria-label="Country" placeholder="Choose a country" required>
      <option value="BR">Brazil</option>
      <option value="DE">Germany</option>
      <option value="JP">Japan</option>
    </ui-select>
    <ui-select name="currency" aria-label="Currency" value="EUR">
      <option value="USD">US dollar</option>
      <option value="EUR">Euro</option>
      <option value="JPY">Yen</option>
    </ui-select>
    <div class="actions">
      <ui-button type="submit">Save</ui-button>
      <ui-button type="reset" variant="outline">Reset</ui-button>
    </div>
  </form>
  <output><slot name="#result.text"></slot></output>
</custom-element>

API

Parts

Part Slot Renders
ui-select A native <select> styled like an input, with a chevron. Fills its container's width unless width is set.
<option> (data) One choice, copied into the native select.

The select keeps the native <option> children rather than separate item parts, so the platform picker, keyboard handling and mobile UI keep working.

Attributes

Attribute Type Default Description
value string "" The value of the chosen option.
placeholder string Text of an empty, unselectable first entry. Without it, the first or selected option is chosen automatically.
size "default" | "sm" "default" Height of the select.
width number Fixed width in spacing units, where n means calc(var(--spacing) * n). Without it the select fills its container.
disabled boolean false Disables the select. Also true inside a disabled <fieldset>.
required boolean false The form is invalid while the placeholder is shown.
aria-label string Accessible name, passed to the inner select. Property name: label.

Properties

Property Type Description
value string The chosen value. Setting it selects the matching option.

Events

Event Detail Description
change none Dispatched from the element (bubbling) after the user picks an option.
input none The inner select's native input event (composed).

Child elements

Element Attributes read Description
<option> value, label (or text), disabled, selected One choice. Options are copied into the inner select and kept in sync. selected only picks the initial option when there is no placeholder and no value. <optgroup> is not supported.

CSS parts

Part Description
control The inner native <select>.

CSS states

State Description
:state(attr-size-is-sm) Set for the small size.

Forms

ui-select is form-associated. It submits value under its name (an empty string while the placeholder is shown). Validity is mirrored from the inner select, so required with a placeholder reports valueMissing and the validation bubble points at the select. form.reset() restores the value attribute. Without one, the select goes back to the placeholder, or to the first or selected option.

Accessibility

The host delegates focus to the inner native <select>, which keeps the platform's keyboard support and picker. Name it with aria-label, ui-label for or a wrapping <label>.

Edit this page on GitHub · View as Markdown