Select
Select control that lowers directly to a native <select>; author <option> children directly inside it.
Default
With no value or state properties supplied, Select follows its native selected option.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-select/ui-select.html"
>
<ui-select
aria-label="Option"
>
<option
value="one"
>One</option>
<option
value="two"
>Two</option>
</ui-select><script setup>
import { UiSelect } from "@threadlabs/looma";
</script>
<template>
<UiSelect
aria-label="Option"
>
<option
value="one"
>One</option>
<option
value="two"
>Two</option>
</UiSelect>
</template>import { UiSelect } from "@threadlabs/looma";
export default function Example() {
return <UiSelect
aria-label="Option"
>
<option
value="one"
>One</option>
<option
value="two"
>Two</option>
</UiSelect>;
}/** @jsxImportSource preact */
import { UiSelect } from "@threadlabs/looma";
export default function Example() {
return <UiSelect
aria-label="Option"
>
<option
value="one"
>One</option>
<option
value="two"
>Two</option>
</UiSelect>;
}<script>
import { UiSelect } from "@threadlabs/looma";
</script>
<UiSelect
aria-label="Option"
>
<option
value="one"
>One</option>
<option
value="two"
>Two</option>
</UiSelect>import { Component } from "@angular/core";
import { UiSelect } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiSelect],
template: `
<select
ui-select
aria-label="Option"
>
<option
value="one"
>One</option>
<option
value="two"
>Two</option>
</select>
`,
})
export default class Example {}value, required, and invalid
Value selects an option; required and invalid expose its validation state.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-select/ui-select.html"
>
<ui-select
value="editor"
required
invalid
aria-label="Workspace role"
>
<option
value
>Choose a role…</option>
<option
value="viewer"
>Viewer</option>
<option
value="editor"
>Editor</option>
</ui-select><script setup>
import { UiSelect } from "@threadlabs/looma";
</script>
<template>
<UiSelect
value="editor"
:required="true"
:invalid="true"
aria-label="Workspace role"
>
<option
value=""
>Choose a role…</option>
<option
value="viewer"
>Viewer</option>
<option
value="editor"
>Editor</option>
</UiSelect>
</template>import { UiSelect } from "@threadlabs/looma";
export default function Example() {
return <UiSelect
value="editor"
required
invalid
aria-label="Workspace role"
>
<option
value=""
>Choose a role…</option>
<option
value="viewer"
>Viewer</option>
<option
value="editor"
>Editor</option>
</UiSelect>;
}/** @jsxImportSource preact */
import { UiSelect } from "@threadlabs/looma";
export default function Example() {
return <UiSelect
value="editor"
required
invalid
aria-label="Workspace role"
>
<option
value=""
>Choose a role…</option>
<option
value="viewer"
>Viewer</option>
<option
value="editor"
>Editor</option>
</UiSelect>;
}<script>
import { UiSelect } from "@threadlabs/looma";
</script>
<UiSelect
value="editor"
required
invalid
aria-label="Workspace role"
>
<option
value=""
>Choose a role…</option>
<option
value="viewer"
>Viewer</option>
<option
value="editor"
>Editor</option>
</UiSelect>import { Component } from "@angular/core";
import { UiSelect } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiSelect],
template: `
<select
ui-select
[value]="'editor'"
[required]="true"
[invalid]="true"
aria-label="Workspace role"
>
<option
value=""
>Choose a role…</option>
<option
value="viewer"
>Viewer</option>
<option
value="editor"
>Editor</option>
</select>
`,
})
export default class Example {}disabled
Disabled makes a select unavailable to pointer, keyboard, and form submission. For choosing more than one option, use Combobox in multiple mode.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-select/ui-select.html"
>
<ui-select
aria-label="Disabled"
disabled
>
<option>Unavailable</option>
</ui-select><script setup>
import { UiSelect } from "@threadlabs/looma";
</script>
<template>
<UiSelect
aria-label="Disabled"
:disabled="true"
>
<option>Unavailable</option>
</UiSelect>
</template>import { UiSelect } from "@threadlabs/looma";
export default function Example() {
return <UiSelect
aria-label="Disabled"
disabled
>
<option>Unavailable</option>
</UiSelect>;
}/** @jsxImportSource preact */
import { UiSelect } from "@threadlabs/looma";
export default function Example() {
return <UiSelect
aria-label="Disabled"
disabled
>
<option>Unavailable</option>
</UiSelect>;
}<script>
import { UiSelect } from "@threadlabs/looma";
</script>
<UiSelect
aria-label="Disabled"
disabled
>
<option>Unavailable</option>
</UiSelect>import { Component } from "@angular/core";
import { UiSelect } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiSelect],
template: `
<select
ui-select
aria-label="Disabled"
[disabled]="true"
>
<option>Unavailable</option>
</select>
`,
})
export default class Example {}size
Each size matches Button's (sm 32px, md 40px, lg 48px); sm also takes smaller text and a smaller chevron.
Code
<link
rel="component"
href="@threadlabs/looma/components/ui-stack/ui-stack.html"
>
<link
rel="component"
href="@threadlabs/looma/components/ui-select/ui-select.html"
>
<ui-stack
gap="s"
style="max-inline-size: 14rem"
>
<ui-select
size="sm"
aria-label="Small"
>
<option>Small</option>
</ui-select>
<ui-select
aria-label="Medium"
>
<option>Medium</option>
</ui-select>
<ui-select
size="lg"
aria-label="Large"
>
<option>Large</option>
</ui-select>
</ui-stack><script setup>
import { UiSelect, UiStack } from "@threadlabs/looma";
</script>
<template>
<UiStack
gap="s"
style="max-inline-size: 14rem"
>
<UiSelect
size="sm"
aria-label="Small"
>
<option>Small</option>
</UiSelect>
<UiSelect
aria-label="Medium"
>
<option>Medium</option>
</UiSelect>
<UiSelect
size="lg"
aria-label="Large"
>
<option>Large</option>
</UiSelect>
</UiStack>
</template>import { UiSelect, UiStack } from "@threadlabs/looma";
export default function Example() {
return <UiStack
gap="s"
style={{"maxInlineSize":"14rem"}}
>
<UiSelect
size="sm"
aria-label="Small"
>
<option>Small</option>
</UiSelect>
<UiSelect
aria-label="Medium"
>
<option>Medium</option>
</UiSelect>
<UiSelect
size="lg"
aria-label="Large"
>
<option>Large</option>
</UiSelect>
</UiStack>;
}/** @jsxImportSource preact */
import { UiSelect, UiStack } from "@threadlabs/looma";
export default function Example() {
return <UiStack
gap="s"
style={{"maxInlineSize":"14rem"}}
>
<UiSelect
size="sm"
aria-label="Small"
>
<option>Small</option>
</UiSelect>
<UiSelect
aria-label="Medium"
>
<option>Medium</option>
</UiSelect>
<UiSelect
size="lg"
aria-label="Large"
>
<option>Large</option>
</UiSelect>
</UiStack>;
}<script>
import { UiSelect, UiStack } from "@threadlabs/looma";
</script>
<UiStack
gap="s"
style="max-inline-size: 14rem"
>
<UiSelect
size="sm"
aria-label="Small"
>
<option>Small</option>
</UiSelect>
<UiSelect
aria-label="Medium"
>
<option>Medium</option>
</UiSelect>
<UiSelect
size="lg"
aria-label="Large"
>
<option>Large</option>
</UiSelect>
</UiStack>import { Component } from "@angular/core";
import { UiSelect, UiStack } from "@threadlabs/looma";
@Component({
selector: "example-view",
standalone: true,
imports: [UiStack, UiSelect],
template: `
<div
ui-stack
[gap]="'s'"
style="max-inline-size: 14rem"
>
<select
ui-select
[size]="'sm'"
aria-label="Small"
>
<option>Small</option>
</select>
<select
ui-select
aria-label="Medium"
>
<option>Medium</option>
</select>
<select
ui-select
[size]="'lg'"
aria-label="Large"
>
<option>Large</option>
</select>
</div>
`,
})
export default class Example {}API
Package: @threadlabs/looma · Native root: select
Attributes
| Name | Description | Property | Type | Default | Allowed values |
|---|---|---|---|---|---|
disabled | Prevents selection and form submission. | disabled | boolean | false | — |
invalid | Exposes invalid state to assistive technology and styling. | invalid | boolean | false | — |
muted | Shows the selected value with supporting-text emphasis, for an inherited or default choice. The select remains interactive and its options retain normal emphasis. | muted | boolean | false | — |
required | Requires a non-empty option for form validation. | required | boolean | false | — |
size | Height, padding, chevron, and type size, matching Button's sizes so a field and a button of one size share a row. sm is the small control height (2rem by default) with smaller text and a smaller chevron, for compact rows such as a table row or a filter bar; md is the standard control height; lg is taller with roomier padding. Touch input keeps body-size field text without inflating the visible size ladder. It is not the native size attribute: a select is always a single-choice dropdown. Change it after render through this option; the element's size property is the native one. | size | keyword | md | sm, md, lg |
value | The value of the selected option, initially or when set externally. It is also the default, so a form reset returns to it rather than to the first option. | value | string | — | — |
Framework props
| Name | Type | Default | Allowed values |
|---|---|---|---|
disabled | boolean | false | — |
invalid | boolean | false | — |
muted | boolean | false | — |
required | boolean | false | — |
size | "sm" | "md" | "lg" | md | sm, md, lg |
value | string | — | — |
Events
No events.
Slots
| Name | Purpose |
|---|---|
default | Default child content. |
Design tokens
Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.
Component tokens
| Token | Declared values | Fallback |
|---|---|---|
--ui-select-border | — | var(--ui-control-border, var(--ui-border-strong)) |
--ui-select-border-hover | — | var(--ui-control-border-hover, var(--ui-text-secondary)) |
--ui-select-chevron | — | url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235c5c58' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") |
--ui-select-focus-border | — | var(--ui-control-focus, var(--ui-accent-solid)) |
--ui-select-focus-shadow | — | var(--ui-shadow-sm), var(--ui-control-focus-halo-shadow) |
--ui-select-inline-size | — | 100% |
--ui-select-invalid-border | — | var(--ui-field-danger, var(--ui-danger-solid)) |
--ui-select-invalid-focus-shadow | — | var(--ui-shadow-sm),
0 0 0 calc(var(--ui-focus-width) * 1.5) color-mix(
in srgb,
var(--ui-select-invalid-border, var(--ui-field-danger, var(--ui-danger-solid))) 22%,
transparent
) |
--ui-select-radius | — | var(--ui-field-radius, var(--ui-radius-md)) |
--ui-select-shadow | — | var(--ui-shadow-sm) |
--ui-select-surface | — | var(--ui-control-surface, var(--ui-surface-elevated)) |
Shared tokens
| Token | Fallback |
|---|---|
--ui-accent-solid | — |
--ui-border-strong | — |
--ui-border-width | — |
--ui-control-border | var(--ui-border-strong) |
--ui-control-border-hover | var(--ui-text-secondary) |
--ui-control-focus | var(--ui-accent-solid) |
--ui-control-focus-halo-shadow | — |
--ui-control-inset-block-lg | var(--ui-space-3) |
--ui-control-inset-block-md | var(--ui-space-2) |
--ui-control-inset-block-sm | var(--ui-space-1) |
--ui-control-size-lg | — |
--ui-control-size-md | — |
--ui-control-size-sm | — |
--ui-control-surface | var(--ui-surface-elevated) |
--ui-control-surface-disabled | var(--ui-surface-muted) |
--ui-danger-solid | — |
--ui-disabled-text | — |
--ui-field-danger | var(--ui-danger-solid) |
--ui-field-radius | var(--ui-radius-md) |
--ui-focus-width | — |
--ui-font-size-md | — |
--ui-font-size-sm | — |
--ui-icon-size-sm | — |
--ui-line-height-tight | — |
--ui-motion-ease | — |
--ui-motion-fast | — |
--ui-radius-md | — |
--ui-shadow-sm | — |
--ui-space-1 | — |
--ui-space-2 | — |
--ui-space-3 | — |
--ui-space-4 | — |
--ui-surface-elevated | — |
--ui-surface-highlight | linear-gradient(transparent, transparent) |
--ui-surface-muted | — |
--ui-text-primary | — |
--ui-text-secondary | — |