Skip to content
Browse docs

Select

Beta

A styled list of options that submits with forms like a native select.

---
import Select from "@/components/ui/select/Select.astro";
import SelectLabel from "@/components/ui/select/SelectLabel.astro";
import SelectTrigger from "@/components/ui/select/SelectTrigger.astro";
import SelectContent from "@/components/ui/select/SelectContent.astro";
import SelectItem from "@/components/ui/select/SelectItem.astro";
---

<Select class="w-64">
  <SelectLabel>Framework</SelectLabel>
  <SelectTrigger placeholder="Pick a framework" />
  <SelectContent>
    <SelectItem value="astro">Astro</SelectItem>
    <SelectItem value="next">Next.js</SelectItem>
    <SelectItem value="nuxt" disabled>
      Nuxt
    </SelectItem>
    <SelectItem value="remix">Remix</SelectItem>
    <SelectItem value="sveltekit">SvelteKit</SelectItem>
  </SelectContent>
</Select>

Installation

npx eris-ui@latest add select
pnpm dlx eris-ui@latest add select
bunx eris-ui@latest add select
yarn dlx eris-ui@latest add select

Installs @zag-js/select, @zag-js/vanilla.

Starting value and groups

Set value on the Select to the value of the item chosen on load. Without it, the trigger shows its placeholder.

SelectGroup takes a label, shown above its options and read out as the group’s name.

---
import Select from "@/components/ui/select/Select.astro";
import SelectLabel from "@/components/ui/select/SelectLabel.astro";
import SelectTrigger from "@/components/ui/select/SelectTrigger.astro";
import SelectContent from "@/components/ui/select/SelectContent.astro";
import SelectItem from "@/components/ui/select/SelectItem.astro";
import SelectGroup from "@/components/ui/select/SelectGroup.astro";
---

<Select value="carrot" class="w-64">
  <SelectLabel>Food</SelectLabel>
  <SelectTrigger />
  <SelectContent>
    <SelectGroup label="Fruit">
      <SelectItem value="apple">Apple</SelectItem>
      <SelectItem value="banana">Banana</SelectItem>
    </SelectGroup>
    <SelectGroup label="Vegetables">
      <SelectItem value="carrot">Carrot</SelectItem>
      <SelectItem value="leek">Leek</SelectItem>
    </SelectGroup>
  </SelectContent>
</Select>

In a form

Give the select a name. It keeps a hidden native <select> in step with the list, so the form sends the chosen value, required stops the form while nothing is chosen, and a form reset goes back to the starting value.

---
import Select from "@/components/ui/select/Select.astro";
import SelectLabel from "@/components/ui/select/SelectLabel.astro";
import SelectTrigger from "@/components/ui/select/SelectTrigger.astro";
import SelectContent from "@/components/ui/select/SelectContent.astro";
import SelectItem from "@/components/ui/select/SelectItem.astro";
import Button from "@/components/ui/button/Button.astro";
---

<form class="flex w-64 flex-col items-start gap-4">
  <Select name="plan" value="pro" class="w-full">
    <SelectLabel>Plan</SelectLabel>
    <SelectTrigger />
    <SelectContent>
      <SelectItem value="free">Free</SelectItem>
      <SelectItem value="pro">Pro</SelectItem>
      <SelectItem value="team">Team</SelectItem>
    </SelectContent>
  </Select>
  <Select name="region" required class="w-full">
    <SelectLabel>Region</SelectLabel>
    <SelectTrigger placeholder="Pick a region" />
    <SelectContent>
      <SelectItem value="eu">Europe</SelectItem>
      <SelectItem value="us">United States</SelectItem>
    </SelectContent>
  </Select>
  <div class="flex gap-3">
    <Button type="submit" size="sm">
      Save
    </Button>
    <Button type="reset" size="sm" variant="secondary">
      Reset
    </Button>
  </div>
</form>

The hidden select is filled in by the script, so unlike Switch and Checkbox, Select needs JavaScript. If the field must work without it, use a native <select>.

Reacting to a change

The hidden <select> fires a normal change event and has the chosen value, so you listen to it as you would a native one.

Sorting by: name

---
import Select from "@/components/ui/select/Select.astro";
import SelectLabel from "@/components/ui/select/SelectLabel.astro";
import SelectTrigger from "@/components/ui/select/SelectTrigger.astro";
import SelectContent from "@/components/ui/select/SelectContent.astro";
import SelectItem from "@/components/ui/select/SelectItem.astro";
---

<div class="flex w-64 flex-col gap-3">
  <Select id="demo-sort" value="name">
    <SelectTrigger aria-label="Sort by" />
    <SelectContent>
      <SelectItem value="name">Name</SelectItem>
      <SelectItem value="date">Date modified</SelectItem>
      <SelectItem value="size">Size</SelectItem>
    </SelectContent>
  </Select>
  <p class="text-sm text-muted">
    Sorting by: <output id="demo-sort-output">name</output>
  </p>
</div>

<script>
  document.addEventListener("astro:page-load", () => {
    const select = document.querySelector('[data-eris-id="demo-sort"] select');
    const output = document.querySelector("#demo-sort-output");
    select?.addEventListener("change", (event) => {
      if (output) output.textContent = (event.target as HTMLSelectElement).value;
    });
  });
</script>

Naming the select

SelectLabel names the select, and clicking it focuses the trigger. With no visible label, give SelectTrigger an aria-label, as in the demo above.

States

---
import Select from "@/components/ui/select/Select.astro";
import SelectLabel from "@/components/ui/select/SelectLabel.astro";
import SelectTrigger from "@/components/ui/select/SelectTrigger.astro";
import SelectContent from "@/components/ui/select/SelectContent.astro";
import SelectItem from "@/components/ui/select/SelectItem.astro";
---

<div class="flex w-64 flex-col gap-4">
  <Select disabled value="a">
    <SelectLabel>Disabled</SelectLabel>
    <SelectTrigger />
    <SelectContent>
      <SelectItem value="a">Option A</SelectItem>
    </SelectContent>
  </Select>
  <Select invalid>
    <SelectLabel>Invalid</SelectLabel>
    <SelectTrigger />
    <SelectContent>
      <SelectItem value="a">Option A</SelectItem>
    </SelectContent>
  </Select>
</div>

readOnly keeps the select focusable and sends its value, but the list does not open. A disabled item on a SelectItem is skipped by the arrow keys.

Placement

placement and gutter work as on Popover. The default is bottom-start, 6 pixels from the trigger. The list is at least as wide as the trigger and scrolls when it is long.

Keyboard

Key Does
Enter, Space, ↓ or ↑ Opens the list from the trigger
↓ / ↑ Next or previous option. Wraps around with loopFocus
Home / End First or last option
Letters Jumps to the option that starts with what you type
Enter or Space Chooses the option and closes the list
Esc Closes the list without changing the value

Typing letters while the list is closed changes the value directly, as on a native select. Choosing several options at once is not part of this component yet.

Parts

Component Renders
Select The root. Props: id, name, value, disabled, required, readOnly, invalid, form, placement, gutter, loopFocus
SelectLabel The visible label
SelectTrigger The button showing the chosen option. Props: placeholder, aria-label
SelectContent The floating list
SelectItem One option. Props: value, disabled
SelectGroup A labelled set of options. Prop: label