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

NameDescriptionPropertyTypeDefaultAllowed values
disabledPrevents selection and form submission.disabledbooleanfalse—
invalidExposes invalid state to assistive technology and styling.invalidbooleanfalse—
mutedShows the selected value with supporting-text emphasis, for an inherited or default choice. The select remains interactive and its options retain normal emphasis.mutedbooleanfalse—
requiredRequires a non-empty option for form validation.requiredbooleanfalse—
sizeHeight, 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.sizekeywordmdsm, md, lg
valueThe 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.valuestring——

Framework props

NameTypeDefaultAllowed values
disabledbooleanfalse—
invalidbooleanfalse—
mutedbooleanfalse—
requiredbooleanfalse—
size"sm" | "md" | "lg"mdsm, md, lg
valuestring——

Events

No events.

Slots

NamePurpose
defaultDefault child content.

Design tokens

Set shared theme properties on a theme boundary. Component hooks apply only to their owning component.

Component tokens

TokenDeclared valuesFallback
--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

TokenFallback
--ui-accent-solid—
--ui-border-strong—
--ui-border-width—
--ui-control-bordervar(--ui-border-strong)
--ui-control-border-hovervar(--ui-text-secondary)
--ui-control-focusvar(--ui-accent-solid)
--ui-control-focus-halo-shadow—
--ui-control-inset-block-lgvar(--ui-space-3)
--ui-control-inset-block-mdvar(--ui-space-2)
--ui-control-inset-block-smvar(--ui-space-1)
--ui-control-size-lg—
--ui-control-size-md—
--ui-control-size-sm—
--ui-control-surfacevar(--ui-surface-elevated)
--ui-control-surface-disabledvar(--ui-surface-muted)
--ui-danger-solid—
--ui-disabled-text—
--ui-field-dangervar(--ui-danger-solid)
--ui-field-radiusvar(--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-highlightlinear-gradient(transparent, transparent)
--ui-surface-muted—
--ui-text-primary—
--ui-text-secondary—