Select
BetaA 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 selectpnpm dlx eris-ui@latest add selectbunx eris-ui@latest add selectyarn dlx eris-ui@latest add selectInstalls @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:
---
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 loop |
| 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, read, invalid, form, placement, gutter, loop |
Select |
The visible label |
Select |
The button showing the chosen option. Props: placeholder, aria-label |
Select |
The floating list |
Select |
One option. Props: value, disabled |
Select |
A labelled set of options. Prop: label |