Trang này chưa có bản tiếng Việt. Nội dung dưới đây là bản tiếng Anh.
Components
Select
An accessible dropdown built on Radix UI, styled to match the design system. Compose a trigger, value, and items; pair with a label and hint/error text for full form fields.
Basic usage
Compose SelectTrigger, SelectValue, and SelectItem children.
Trigger sizes
The trigger has two heights. size="default" is 36px and lines up with Input and Button; size="sm" is 32px for toolbars and table filter rows. Both set their text the way every field does since 3.13.4 — 16px below md, 14px from md up — so a trigger needs no text class of its own (why).
Groups & separators
Long lists read better in sections. Wrap items in a SelectGroup, title each one with SelectLabel, and divide them with SelectSeparator. Row spacing comes from the item itself (mt-1 first:mt-0), so it stays identical whether an item sits inside a group or directly in the list. A list long enough to overflow the viewport also grows arrows at both ends — SelectScrollUpButton and SelectScrollDownButton. Both are exported, but SelectContent already renders them for you; import them only if you are assembling your own content shell.
States
Pair the select with a label and hint/error text. Set aria-invalid on the trigger for the error style.
Choose your primary region.
Please select a country.
The “nothing selected” row
Radix reserves the empty string for clearing a selection, so <SelectItem value="" /> throws. The package ships one shared sentinel instead of leaving every app to invent its own: SELECT_EMPTY_VALUE plus toSelectValue / fromSelectValue to convert at the boundary. Your model keeps '' and the sentinel never reaches a server payload.
model value: ''
Props
Select root props (forwarded to Radix):
| Prop | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
value | string | — | Controlled selected value. Pair with onValueChange. |
defaultValue | string | — | Initial value when the select is uncontrolled. |
onValueChange | (value: string) => void | — | Fires when the user picks a different item. |
open / defaultOpen | boolean | — | Controlled / uncontrolled open state of the listbox. |
onOpenChange | (open: boolean) => void | — | Fires when the listbox opens or closes. |
disabled | boolean | false | Disables the whole control. |
required | boolean | false | Marks the control required when it takes part in a native form. |
name | string | — | Name of the hidden native input rendered for form submission. |
SelectTrigger props:
| Prop | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
size | 'sm' | 'default' | 'default' | Trigger height — sm is 32px, default is 36px. Rendered as data-size on the element. |
aria-invalid | boolean | — | Switches the border to --destructive-ink and adds the destructive ring. |
className | string | — | Additional Tailwind classes merged onto the trigger. |
...rest | Radix SelectTrigger props | — | Everything else is forwarded to the Radix trigger. The chevron icon is rendered by the component. |
The remaining parts:
| Prop | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
SelectValue placeholder | ReactNode | — | Shown while nothing is selected; picks up the muted-foreground colour via data-placeholder on the trigger. |
SelectContent position | 'popper' | 'item-aligned' | 'popper' | Popper mode anchors the list under the trigger and matches its width; scroll buttons are rendered automatically. |
SelectItem valuebắt buộc | string | — | Value written back by onValueChange. Radix rejects an empty string — use SELECT_EMPTY_VALUE for a "none" row. |
SelectItem disabled | boolean | false | Dims the row and skips it during keyboard navigation. |
SelectGroup / SelectLabel | div / label parts | — | Wrap related items in a group and title it with a label; the label is announced as the group name. |
SelectSeparator | div part | — | A one-pixel divider between groups. Pointer events are disabled so it can never swallow a click. |
Accessibility
- Built on Radix UI Select — full keyboard navigation and typeahead.
- Focus is managed and trapped within the open listbox.
- Associate a
<label>with the trigger viaid/aria-labelledby. - The trigger carries no fill of its own beyond
bg-background, so its border is the only thing outlining the control. Each state gets its own role token, and every one of them is darker than rest, never lighter:border-inputat rest,border-control-edge-strongon hover,border-primary-inkon focus, andborder-destructive-inkwhenaria-invalidis set. Underdata-contrast="high"those four steps measure 3.83:1, 10.55:1, 6.51:1 and 7.15:1, all past the 3:1 threshold of WCAG 1.4.11. The default palette keeps the lighter steps in light mode — 1.16:1 at rest, 1.62:1 on hover, 1.86:1 on focus — so what it guarantees there is the ordering, not the ratio. - The selected item is marked by a check mark, not by its background. The highlight tint (
--accent, shared with the hovered row) sits at roughly 1:1 against the popover and is a soft cue only; the check is a shape, satisfying WCAG 1.4.1.