@loke/ui
Version:
225 lines (170 loc) • 6.85 kB
Markdown
---
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