Pickers and overlays
Select
A button that opens a list and keeps one chosen value.
When to use it: Use it for one form choice from a known list.
Example
import {
Label,
Select,
SelectOptGroup,
SelectOption,
SelectPopover,
SelectTrigger,
SelectValue,
} from "@comp0/react";
import { ChevronDownIcon } from "@heroicons/react/16/solid";
export function Example() {
return (
<Select as="div" className="flex max-w-xs flex-col gap-1.5" defaultValue="medium" name="size">
<Label className="text-base font-medium text-zinc-900 sm:text-sm dark:text-zinc-100">
Size
</Label>
<SelectTrigger className="flex w-full items-center justify-between gap-2 rounded border border-zinc-950/10 bg-white px-3 py-2.5 text-left text-base text-zinc-950 outline-teal-600 focus-visible:outline-2 sm:py-2 sm:text-sm dark:border-white/10 dark:bg-zinc-900 dark:text-zinc-50 dark:outline-teal-400">
<SelectValue />
<ChevronDownIcon className="size-4 shrink-0 text-zinc-400" aria-hidden="true" />
</SelectTrigger>
<SelectPopover
placement="bottom"
offset={4}
className="max-h-60 w-[anchor-size(width)] max-w-[anchor-size(width)] overflow-y-auto rounded border-0 bg-white p-1 opacity-100 shadow-lg ring-1 ring-zinc-950/10 transition-[opacity,translate] duration-150 ease-out starting:-translate-y-1 starting:opacity-0 motion-reduce:transition-none dark:bg-zinc-900 dark:shadow-none dark:ring-white/10"
>
<SelectOptGroup label="Standard sizes">
<div
aria-hidden="true"
className="px-3 pb-1 pt-2 text-xs font-medium text-zinc-500 dark:text-zinc-400"
>
Standard sizes
</div>
<SelectOption
className="cursor-pointer rounded px-3 py-2.5 text-base text-zinc-800 hover:bg-zinc-100 data-active:bg-teal-100 data-active:text-zinc-950 data-selected:bg-teal-100 focus-visible:bg-teal-200 focus-visible:outline-2 focus-visible:outline-teal-600 sm:py-2 sm:text-sm dark:text-zinc-100 dark:hover:bg-zinc-800 dark:data-active:bg-teal-950 dark:data-active:text-zinc-50 dark:data-selected:bg-teal-950 dark:focus-visible:bg-teal-800 dark:focus-visible:outline-teal-400"
value="small"
>
Small
</SelectOption>
<SelectOption
className="cursor-pointer rounded px-3 py-2.5 text-base text-zinc-800 hover:bg-zinc-100 data-active:bg-teal-100 data-active:text-zinc-950 data-selected:bg-teal-100 focus-visible:bg-teal-200 focus-visible:outline-2 focus-visible:outline-teal-600 sm:py-2 sm:text-sm dark:text-zinc-100 dark:hover:bg-zinc-800 dark:data-active:bg-teal-950 dark:data-active:text-zinc-50 dark:data-selected:bg-teal-950 dark:focus-visible:bg-teal-800 dark:focus-visible:outline-teal-400"
value="medium"
>
Medium
</SelectOption>
</SelectOptGroup>
<SelectOptGroup label="Extended sizes">
<div
aria-hidden="true"
className="px-3 pb-1 pt-2 text-xs font-medium text-zinc-500 dark:text-zinc-400"
>
Extended sizes
</div>
<SelectOption
className="cursor-pointer rounded px-3 py-2.5 text-base text-zinc-800 hover:bg-zinc-100 data-active:bg-teal-100 data-active:text-zinc-950 data-selected:bg-teal-100 focus-visible:bg-teal-200 focus-visible:outline-2 focus-visible:outline-teal-600 sm:py-2 sm:text-sm dark:text-zinc-100 dark:hover:bg-zinc-800 dark:data-active:bg-teal-950 dark:data-active:text-zinc-50 dark:data-selected:bg-teal-950 dark:focus-visible:bg-teal-800 dark:focus-visible:outline-teal-400"
value="large"
>
Large
</SelectOption>
</SelectOptGroup>
</SelectPopover>
</Select>
);
}Anatomy
Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.
A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.
Select
Selected value, open state, and optional visually hidden native select provider. Does not add a DOM element.
Label
Visible name connected to the trigger. Owns a DOM element.
SelectTrigger
Button that opens choices. Owns a DOM element.
SelectValue
Selected option text. Owns a DOM element.
SelectPopover
The listbox surface. Owns a DOM element.
SelectOptGroupOptional
Optional native-style group of related options. Owns a DOM element.
SelectOption
Selectable option. Owns a DOM element.
Step by step
- 1
Add the main part
Start Select with a value or defaultValue.
- 2
Add the supporting parts
Put SelectValue inside SelectTrigger, then add SelectPopover beside it; wrap related options in a labelled SelectOptGroup.
- 3
Make the behavior clear
Select owns its value, field, form serialization, and open state; control the list with open, defaultOpen, and onToggle.
Exampletsx <Select name="size" defaultValue="small"><Label>Size</Label><SelectTrigger><SelectValue /></SelectTrigger><SelectPopover><SelectOption value="small">Small</SelectOption></SelectPopover></Select>
Keyboard
- ↵
- Opens or chooses.
- Space
- Opens or chooses.
- ↓
- Moves through options.
- Esc
- Closes the list.
Forms and accessibility
When name or required is set, Select renders a visually hidden native select proxy for submission and validation; it is not an input type=hidden.
Accessibility checklist
- Use a visible Label so the trigger and its listbox share a clear name; without one, name both parts explicitly.
- Give every SelectOptGroup a native label so grouped options have a name.
- Make the chosen value readable in SelectValue.
- Use native required feedback or FieldError to explain a missing choice.
API reference
import { Label, Select, SelectOptGroup, SelectPopover, SelectOption, SelectTrigger, SelectValue } from "@comp0/react";Select
Context onlySelected value, open state, and optional visually hidden native select provider.
| Prop | Type | Description |
|---|---|---|
value | string | Controlled choice. |
defaultValue | string | Initial choice. |
onChange | (value: string) => void | Receives the next choice. |
open / defaultOpen | boolean | Controlled or initial open state of the listbox. |
onToggle | (open: boolean) => void | Receives the next open state. |
name | string | Submission name for the hidden native select. |
form | string | Associates the native select with a form by id. |
disabled / invalid / required | boolean | Field-wide states. |
Label
DOM elementVisible name connected to the trigger.
SelectTrigger
DOM elementButton that opens choices.
| Prop | Type | Description |
|---|---|---|
disabled | boolean | Disables opening. |
aria-label | string | Names the trigger when there is no visible Label. |
SelectValue
DOM elementSelected option text.
| Prop | Type | Description |
|---|---|---|
placeholder | ReactNode | Shown while nothing is selected. |
value | ReactNode | Overrides the displayed text for the selected option. |
SelectPopover
DOM elementThe listbox surface.
| Prop | Type | Description |
|---|---|---|
aria-label | string | Names the listbox when there is no visible Label. |
placement | PopoverPlacement | Trigger side to open on, such as "bottom"; flips when there is no room. |
offset | number | Pixel gap between the trigger and the listbox. |
SelectOptGroup
OptionalDOM elementOptional native-style group of related options.
| Prop | Type | Description |
|---|---|---|
label | string | Names the group before its options. |
SelectOption
DOM elementSelectable option.
| Prop | Type | Description |
|---|---|---|
value | string | This option’s value. |
disabled | boolean | Disables the option. |
textValue | string | Overrides the text crawled from children when markup makes it ambiguous. |
Style hooks
Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.
[data-open]on SelectTrigger, SelectPopover
- Choices are open.
:popover-openon SelectPopover
- Native pseudo-class equivalent.
[data-placeholder]on SelectValue
- No value is selected.
[data-value]on SelectValue
- A value is selected.