UNPKG

@loke/ui

Version:
225 lines (170 loc) 6.85 kB
--- name: select type: core domain: forms requires: [loke-ui] description: > Select listbox with SelectTrigger, SelectValue, SelectPortal, SelectContent, SelectViewport, SelectGroup, SelectLabel, SelectItem + SelectItemText (required), SelectItemIndicator, SelectScrollUpButton, SelectScrollDownButton, SelectSeparator, SelectIcon, SelectArrow. Two positioning modes: item-aligned (default) and popper. Empty string value throws. All items must be in tree at open time — use Popover+Command for async/searchable lists. --- # Select `@loke/ui/select` — accessible listbox-style select built on a custom `role="combobox"` trigger. A hidden native `<select>` handles form participation and autofill. See [`references/select-components.md`](references/select-components.md) for the full sub-component prop reference. ## Setup ```tsx import { Select, SelectContent, SelectGroup, SelectItem, SelectItemText, SelectLabel, SelectPortal, SelectScrollDownButton, SelectScrollUpButton, SelectTrigger, SelectValue, SelectViewport, } from "@loke/ui/select"; function CountrySelect() { return ( <Select name="country" defaultValue="au"> <SelectTrigger style={{ width: 200 }}> <SelectValue placeholder="Select a country" /> </SelectTrigger> <SelectPortal> <SelectContent> <SelectScrollUpButton /> <SelectViewport> <SelectGroup> <SelectLabel>Asia Pacific</SelectLabel> <SelectItem value="au"> <SelectItemText>Australia</SelectItemText> </SelectItem> <SelectItem value="nz"> <SelectItemText>New Zealand</SelectItemText> </SelectItem> </SelectGroup> </SelectViewport> <SelectScrollDownButton /> </SelectContent> </SelectPortal> </Select> ); } ``` ## Core Patterns ### Controlled value ```tsx const [country, setCountry] = useState<string | undefined>(undefined); <Select value={country} onValueChange={setCountry} name="country"> <SelectTrigger> <SelectValue placeholder="Choose…" /> </SelectTrigger> <SelectPortal> <SelectContent> <SelectViewport> <SelectItem value="au"> <SelectItemText>Australia</SelectItemText> </SelectItem> <SelectItem value="nz"> <SelectItemText>New Zealand</SelectItemText> </SelectItem> </SelectViewport> </SelectContent> </SelectPortal> </Select> ``` To clear selection, set `value` to `undefined` (not `""`). ### Popper positioning `SelectContent` defaults to `position="item-aligned"` which centers the open list over the selected item (native select behavior). Set `position="popper"` for a floating dropdown anchored below the trigger. ```tsx <SelectContent position="popper" sideOffset={4}> <SelectViewport> <SelectItem value="a"><SelectItemText>Option A</SelectItemText></SelectItem> </SelectViewport> </SelectContent> ``` `position="popper"` enables the full Popper props: `side`, `sideOffset`, `align`, `alignOffset`, `collisionBoundary`, `collisionPadding`, `avoidCollisions`. ### Groups with labels ```tsx <SelectViewport> <SelectGroup> <SelectLabel>Fruits</SelectLabel> <SelectItem value="apple"><SelectItemText>Apple</SelectItemText></SelectItem> <SelectItem value="banana"><SelectItemText>Banana</SelectItemText></SelectItem> </SelectGroup> <SelectSeparator /> <SelectGroup> <SelectLabel>Vegetables</SelectLabel> <SelectItem value="carrot"><SelectItemText>Carrot</SelectItemText></SelectItem> </SelectGroup> </SelectViewport> ``` ## Common Mistakes ### 1. Passing empty string as SelectItem value — throws `SelectItem` validates its `value` prop and throws if it is an empty string. **Wrong:** ```tsx <SelectItem value="">None</SelectItem> ``` **Correct:** Use a non-empty sentinel value, or leave `Select` uncontrolled with no `defaultValue` for an empty initial state. ```tsx <SelectItem value="none">None</SelectItem> ``` Source: `src/components/select/select.tsx` — value validation ### 2. Missing SelectItemText — trigger shows nothing, typeahead fails `SelectItemText` serves two purposes: its text is portaled into `SelectValue` when the item is selected, and it provides the string for keyboard typeahead matching. If omitted, the trigger renders blank and typing letters does not jump to items. **Wrong:** ```tsx <SelectItem value="apple">Apple</SelectItem> ``` **Correct:** ```tsx <SelectItem value="apple"> <SelectItemText>Apple</SelectItemText> </SelectItem> ``` If you need an icon inside an item, put only the label text inside `SelectItemText`: ```tsx <SelectItem value="apple"> <SelectItemText>Apple</SelectItemText> <FruitIcon /> </SelectItem> ``` Source: `src/components/select/select.tsx` — `SelectItemText` portals into `SelectValue` ### 3. Not knowing the default is item-aligned, not popper The default `position="item-aligned"` opens the list so the currently selected item sits directly over the trigger (like a native `<select>`). This can appear broken on first open if you expect a dropdown anchored below. **Wrong assumption:** Select will always open downward like a `<div>` dropdown. **Correct:** Set `position="popper"` explicitly when you want anchored floating behavior. Source: `src/components/select/select.tsx` — `position` prop default `"item-aligned"` ### 4. Missing SelectViewport — items may not scroll `SelectViewport` is the scroll container and injects cross-browser scrollbar-hiding styles. Without it, overflow on `SelectContent` may clip items or produce double scrollbars. **Wrong:** ```tsx <SelectContent> <SelectItem value="a"><SelectItemText>A</SelectItemText></SelectItem> </SelectContent> ``` **Correct:** ```tsx <SelectContent> <SelectViewport> <SelectItem value="a"><SelectItemText>A</SelectItemText></SelectItem> </SelectViewport> </SelectContent> ``` Source: `src/components/select/select.tsx` — `SelectViewport` scroll container role ### 5. Using Select for async or filterable option lists Select requires all `SelectItem` elements to be in the React tree at the time the dropdown opens. It has no built-in search input. **Wrong:** Fetching options inside `SelectContent` on open, or expecting a search/filter input inside the select. **Correct:** Compose `Popover` + `Command` for a combobox with async loading or text filtering. See the Command skill for the pattern. Source: maintainer interview ## Cross-references - [`references/select-components.md`](references/select-components.md) — full sub-component prop listing - **Command** (`@loke/ui/command`) — async / searchable option lists via Popover + Command - **Choosing the Right Component** — Select vs Popover+Command decision guide