Selects
React’s Select is a compound with a custom popup. Vanilla uses a native <select> with the .select class — the browser owns its dropdown UI, so the two paths share styling but not structure.
Examples
Section titled “Examples”Default
Section titled “Default”<select class="select"> <option value="">Select a status…</option> <option value="open">Open</option> <option value="in-progress">In progress</option> <option value="closed">Closed</option></select><Select name="status" items={{ open: "Open", "in-progress": "In progress", closed: "Closed" }}> <Select.Trigger> <Select.Value placeholder="Select a status…" /> <Select.Icon /> </Select.Trigger> <Select.Popup> <Select.Item value="open"> Open <Select.ItemIndicator /> </Select.Item> <Select.Item value="in-progress"> In progress <Select.ItemIndicator /> </Select.Item> <Select.Item value="closed"> Closed <Select.ItemIndicator /> </Select.Item> </Select.Popup></Select>Variants
Section titled “Variants”<select class="select"> <option>Bordered</option></select><select class="select select-ghost"> <option>Ghost</option></select><select class="select select-danger"> <option>Danger</option></select><Select defaultValue="x" items={{ x: "Bordered" }}> <Select.Trigger> <Select.Value /> <Select.Icon /> </Select.Trigger> <Select.Popup> <Select.Item value="x">Bordered</Select.Item> </Select.Popup></Select><Select defaultValue="x" items={{ x: "Ghost" }}> <Select.Trigger variant="ghost"> <Select.Value /> <Select.Icon /> </Select.Trigger> <Select.Popup> <Select.Item value="x">Ghost</Select.Item> </Select.Popup></Select><Select defaultValue="x" items={{ x: "Danger" }}> <Select.Trigger variant="danger"> <Select.Value /> <Select.Icon /> </Select.Trigger> <Select.Popup> <Select.Item value="x">Danger</Select.Item> </Select.Popup></Select><select class="select select-sm"> <option>Small</option></select><select class="select"> <option>Medium</option></select><select class="select select-lg"> <option>Large</option></select><Select defaultValue="x" items={{ x: "Small" }}> <Select.Trigger triggerSize="sm"> <Select.Value /> <Select.Icon /> </Select.Trigger> <Select.Popup> <Select.Item value="x">Small</Select.Item> </Select.Popup></Select><Select defaultValue="x" items={{ x: "Medium" }}> <Select.Trigger> <Select.Value /> <Select.Icon /> </Select.Trigger> <Select.Popup> <Select.Item value="x">Medium</Select.Item> </Select.Popup></Select><Select defaultValue="x" items={{ x: "Large" }}> <Select.Trigger triggerSize="lg"> <Select.Value /> <Select.Icon /> </Select.Trigger> <Select.Popup> <Select.Item value="x">Large</Select.Item> </Select.Popup></Select>Groups
Section titled “Groups”<select class="select"> <optgroup label="Fruit"> <option>Apple</option> <option>Banana</option> </optgroup> <optgroup label="Veg"> <option>Carrot</option> <option>Daikon</option> </optgroup></select><Select items={{ apple: "Apple", banana: "Banana", carrot: "Carrot", daikon: "Daikon", }}> <Select.Trigger> <Select.Value placeholder="Pick one…" /> <Select.Icon /> </Select.Trigger> <Select.Popup> <Select.Group> <Select.GroupLabel>Fruit</Select.GroupLabel> <Select.Item value="apple"> Apple <Select.ItemIndicator /> </Select.Item> <Select.Item value="banana"> Banana <Select.ItemIndicator /> </Select.Item> </Select.Group> <Select.Group> <Select.GroupLabel>Veg</Select.GroupLabel> <Select.Item value="carrot"> Carrot <Select.ItemIndicator /> </Select.Item> <Select.Item value="daikon"> Daikon <Select.ItemIndicator /> </Select.Item> </Select.Group> </Select.Popup></Select>Disabled
Section titled “Disabled”<select class="select" disabled> <option>Disabled</option></select><Select disabled defaultValue="x" items={{ x: "Disabled" }}> <Select.Trigger> <Select.Value /> <Select.Icon /> </Select.Trigger> <Select.Popup> <Select.Item value="x">Disabled</Select.Item> </Select.Popup></Select>Inside a Field
Section titled “Inside a Field”<div class="field"> <label class="field-label" for="role">Role</label> <select id="role" class="select" required> <option value="">Pick a role…</option> <option value="admin">Admin</option> <option value="member">Member</option> </select></div><Field name="role"> <Field.Label>Role</Field.Label> <Select required items={{ admin: "Admin", member: "Member" }}> <Select.Trigger> <Select.Value placeholder="Pick a role…" /> <Select.Icon /> </Select.Trigger> <Select.Popup> <Select.Item value="admin"> Admin <Select.ItemIndicator /> </Select.Item> <Select.Item value="member"> Member <Select.ItemIndicator /> </Select.Item> </Select.Popup> </Select> <Field.Error match="valueMissing">Pick a role.</Field.Error></Field>The React popup is portaled out of the trigger’s stacking context onto .popup-layer, so an ancestor overflow: hidden can’t clip it — but a host page with a higher stacking context can still paint over it. See Theming › Popup layering.
Reference
Section titled “Reference”| Part | Renders | Class |
|---|---|---|
Select |
nothing — provides context | — |
Select.Trigger |
<button> |
select |
Select.Value |
<span> |
— |
Select.Icon |
<span>, chevron by default |
select-icon |
Select.Popup |
portal → positioner → <div> |
popup-layer, select-popup |
Select.Item |
<div> |
select-item |
Select.ItemText |
<div> |
— |
Select.ItemIndicator |
<span>, checkmark by default |
select-item-indicator |
Select.Group |
<div> |
— |
Select.GroupLabel |
<div> |
select-group-label |
| Part | Prop | Type | Default |
|---|---|---|---|
Select |
items |
Record<string, ReactNode> or {label, value}[] |
— |
Select.Trigger |
variant |
"bordered" | "ghost" | "danger" |
"bordered" |
Select.Trigger |
triggerSize |
"sm" | "md" | "lg" |
"md" |
Select.Popup |
sideOffset |
number |
4 |
Without items, Select.Value shows the raw value instead of the label. sideOffset is the trigger-to-popup gap in px.
Every part also takes its Base UI props — value / defaultValue / onValueChange / name / required / disabled / multiple on the root, value and label on Item. Each part takes className; Select takes no classNames, and the positioner’s class can’t be overridden.
Only the trigger responds to variant and triggerSize. The chevron, popup and items keep one fixed size.
Vanilla
Section titled “Vanilla”| Class / var | Effect |
|---|---|
select |
On a native <select>: 0.75rem/0.5rem padding, text-sm, bordered surface, and a custom chevron |
select-ghost |
Transparent fill and border until hover |
select-danger |
Danger border and focus outline |
select-sm |
text-xs, tighter padding, smaller chevron |
select-lg |
text-base, looser padding |
select-icon |
React chevron slot, 1rem square; rotates 180° while the popup is open |
select-popup |
React popup: min-width tracks the trigger, 18rem max height, scrolls |
select-item |
React option row; [data-highlighted], [data-selected] and [data-disabled] carry its states |
select-item-indicator |
React checkmark slot, pushed to the row end |
select-group-label |
React group heading: uppercase, muted, text-xs |
popup-layer |
Applied to the React positioner so portaled popups paint above host chrome |
--z-popup |
Read by popup-layer, defaults to 1000. Set it on any ancestor to re-layer |
Use <optgroup> for groups. The native chevron is a background image with a fixed neutral stroke — a data URI can’t read CSS variables, so it doesn’t follow your tokens. The five React-only classes above ship in both bundles but have no native equivalent to attach to.