Select
A styled single-select dropdown built on Base UI’s Select primitive. Includes trigger, popup, items, grouped options, and built-in Floating UI positioning.
Import
import { Select } from "chunks-ui";Basic Usage
const fruits = {
apple: "Apple",
banana: "Banana",
cherry: "Cherry",
strawberry: "Strawberry",
};
<Select.Root items={fruits}>
<Select.Trigger aria-label="Fruit">
<Select.Value placeholder="Choose a fruit" />
<Select.Icon />
</Select.Trigger>
<Select.Portal>
<Select.Positioner sideOffset={5} alignItemWithTrigger={false}>
<Select.Popup>
<Select.Item value="apple">
<Select.ItemText>Apple</Select.ItemText>
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="banana">
<Select.ItemText>Banana</Select.ItemText>
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="cherry">
<Select.ItemText>Cherry</Select.ItemText>
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="strawberry" disabled>
<Select.ItemText>Strawberry</Select.ItemText>
<Select.ItemIndicator />
</Select.Item>
</Select.Popup>
</Select.Positioner>
</Select.Portal>
</Select.Root>Grouped Options
const colors = {
red: "Red",
orange: "Orange",
blue: "Blue",
green: "Green",
};
<Select.Root items={colors}>
<Select.Trigger aria-label="Color">
<Select.Value placeholder="Pick a color" />
<Select.Icon />
</Select.Trigger>
<Select.Portal>
<Select.Positioner sideOffset={5} alignItemWithTrigger={false}>
<Select.Popup>
<Select.Group>
<Select.GroupLabel className="uppercase">Warm</Select.GroupLabel>
<Select.Item value="red">
<Select.ItemText>Red</Select.ItemText>
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="orange">
<Select.ItemText>Orange</Select.ItemText>
<Select.ItemIndicator />
</Select.Item>
</Select.Group>
<Select.Group>
<Select.GroupLabel className="uppercase">Cool</Select.GroupLabel>
<Select.Item value="blue">
<Select.ItemText>Blue</Select.ItemText>
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="green">
<Select.ItemText>Green</Select.ItemText>
<Select.ItemIndicator />
</Select.Item>
</Select.Group>
</Select.Popup>
</Select.Positioner>
</Select.Portal>
</Select.Root>Controlled
const statuses = {
active: "Active",
inactive: "Inactive",
};
function ControlledSelect() {
const [value, setValue] = useState<string | null>(null);
return (
<Select.Root items={statuses} value={value} onValueChange={setValue}>
<Select.Trigger aria-label="Status">
<Select.Value placeholder="Select status" />
<Select.Icon />
</Select.Trigger>
<Select.Portal>
<Select.Positioner sideOffset={5} alignItemWithTrigger={false}>
<Select.Popup>
<Select.Item value="active">
<Select.ItemText>Active</Select.ItemText>
<Select.ItemIndicator />
</Select.Item>
<Select.Item value="inactive">
<Select.ItemText>Inactive</Select.ItemText>
<Select.ItemIndicator />
</Select.Item>
</Select.Popup>
</Select.Positioner>
</Select.Portal>
</Select.Root>
);
}Disabled
A disabled Select is dimmed and shows the not-allowed cursor. The disabled prop disables it, and so does a disabled Field.Root, Base UI’s Fieldset or a <fieldset disabled>. A disabled Select stays closed whatever open and defaultOpen say, and it ignores a pick. Disabling a Select with an open listbox closes the listbox without calling onOpenChange. When it is enabled again, a controlled listbox opens if open is true; an uncontrolled one stays closed. The disabled prop acts at once; the other causes act when the Select next renders, because it reads the disabled state of its form input after each render, and until then a pick in an open listbox is ignored and closes it. When a disabled Select’s listbox closes with focus in it, focus moves to the page, as it does when any focused control is disabled.
Animation
The popup enters with a scale-up and fade (from scale-95 and opacity-0): the popup spring (springs.popup) with Motion installed, a CSS transition of opacity and scale without it; reduced motion turns off both. Aligned over its trigger (the default), the popup only fades; below the trigger (alignItemWithTrigger={false}), it grows out of the trigger (origin-(--transform-origin)), and a className such as origin-top replaces that origin. On the CSS path, data-[side=none]:data-starting-style:scale-95 and its data-ending-style pair bring the scale back to an aligned popup.
render on Select.Popup replaces the element the spring animates, so a popup with render uses the CSS transition even with Motion installed. To restyle the popup, pass className.
Compound Parts
| Part | Description |
|---|---|
Select.Root | State container. Manages open/close and selected value. items makes the trigger show the selected item’s label: pass an object that maps each value to its label ({ cherry: "Cherry" }), or an array of { value, label }. |
Select.Trigger | The button that opens the dropdown. Styled like an input. At most as wide as its container (max-w-full), and it shrinks in a flex row (min-w-0); max-w-none lifts the cap. |
Select.Value | Displays the label of the selected item, or the placeholder text. The label comes from items on Select.Root. Without items, itemToStringLabel or a function as children, a string value shows as is (cherry, not Cherry). A value wider than the trigger stays on one line and ends in an ellipsis before the chevron. It fills the trigger’s free space (flex-1), so an element beside it, such as a badge, keeps its width and the value truncates first; flex-initial in className sizes it by its text instead. In a right-to-left trigger, a long Latin value shows its last words: the line starts at the right edge, so its first words overflow on the left. dir="auto" on the value takes the direction from its first letter: a Latin value then shows its first words and sits at the left, and a right-to-left value stays at the right. |
Select.Icon | Chevron icon inside the trigger. Children you pass replace the chevron. With a render element and no children, there is no chevron: the element draws its own, and may be a void element such as <img>. |
Select.Portal | Renders the popup in a portal. |
Select.Positioner | Handles Floating UI positioning. Base UI’s default alignItemWithTrigger places the popup over the trigger, with the selected item on top of it; the examples pass alignItemWithTrigger={false} and sideOffset={5} to open it below the trigger instead. Gives the popup the trigger’s width (w-[var(--anchor-width)]). For a trigger sized by its value, pass w-auto min-w-[var(--anchor-width)] in className: the popup is never narrower than the trigger, and its items keep their text on one line. Bare w-auto sizes the popup by its items. Set a width here, not on Select.Popup: a width on the popup spills out of the positioner, and Floating UI’s collision handling measures the positioner. |
Select.Popup | The dropdown panel containing items. It is as wide as the trigger, over the trigger or below it. Item text wraps to that width, and a word longer than the popup breaks; the check mark keeps its room at the item’s end. |
Select.Item | An individual selectable option. Put its text in Select.ItemText: only that wraps at the popup’s width, so text placed straight in the item can run past a narrow popup. |
Select.ItemText | The text content of an item. It wraps at the popup’s width and breaks a word longer than that; a badge or shortcut beside it keeps its width. |
Select.ItemIndicator | Checkmark shown for the selected item. Children you pass replace the checkmark. With a render element and no children, there is no checkmark: the element draws its own, and may be a void element such as <img>. The indicator is aria-hidden, so screen readers skip its children and announce the option’s selected state instead. |
Select.Group | Groups related items together. |
Select.GroupLabel | Label for a group of items. |
Text Size
The text of Select.Trigger has the size of the text fields: 16px below the md breakpoint (768px by default) and on a touch screen, and 14px otherwise, so a form has one text size. The items follow the field size too, as a popup not opened by touch lays the selected item over the trigger’s value; where the text is 16px, an item is 4px taller (36px). A text-size class on Select.Trigger or Select.Item follows the same rules as on an Input. A size class meant for the whole field goes on both Select.Trigger and Select.Item, or the item opens over the value at a different size. See Text Size in Input.
Props
All parts accept the props from their corresponding Base UI Select sub-components.