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.
<!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.
<!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.
<!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
<!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
<!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).
<!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.
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).
<!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>.