Select
Choose one option from a list (DGA: Dropdown Input). Two sizes, three fills, groups and every state, on Base UI Select.
Version 0.4.1 ·
Installation
bunx --bun shadcn@latest add @dga/selectInstalls Field too. This is DGA's Dropdown Input; it is named Select after the Base UI part.
Usage
import { Field, FieldLabel } from "@/components/ui/field";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const services = [
{ value: "issue", label: "Issue a licence" },
{ value: "renew", label: "Renew a licence" },
];
export function ServiceField() {
return (
<Field name="service">
<FieldLabel required>Service</FieldLabel>
<Select items={services} required>
<SelectTrigger>
<SelectValue placeholder="Choose a service" />
</SelectTrigger>
<SelectContent>
{services.map((s) => (
<SelectItem key={s.value} value={s.value}>
{s.label}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
);
}Use Select to pick one option from five to fifteen. For fewer, Radio shows every option at once; for many, add a search field. Pass items so the trigger shows the label of the chosen value, and a placeholder that says what to choose.
Anatomy
- Trigger (
<button>): the border, the chosen label or the placeholder, and a chevron. - Content: the popup on the card surface, as wide as the trigger.
- Item: one option, with a check mark on the end side when chosen.
- Group, Label and Separator (optional): group related options.
Examples
Basic
Click to open, or focus it and press Enter, Space or ArrowDown.
States
Default, filled, read-only, disabled and error, and the medium size with a darker fill.
Groups
Wrap related options in SelectGroup with a SelectLabel, and separate groups with SelectSeparator.
RTL
The trigger text starts on the right and the chevron is on the left. The popup opens below the trigger and aligns to its start (right) edge; the check mark sits on the left.
Accessibility
| Key | Action |
|---|---|
| Tab | Moves focus to the trigger. |
| Enter, Space or ArrowDown | Opens the list. |
| ArrowUp / ArrowDown | Moves between options. |
| Home / End | Jumps to the first or last option. |
| Type a letter | Jumps to the option that starts with it. |
| Enter | Chooses the highlighted option and closes the list. |
| Esc | Closes the list and returns focus to the trigger. |
- The trigger has the
comboboxrole witharia-expanded; the list has thelistboxrole and each optionoptionwitharia-selected. - The label comes from Field. Helper text and errors are linked with
aria-describedby. - The highlighted option has a 2px bar and a tint, not a tint alone (WCAG 2.4.7). The chosen option has a check mark.
- Every state keeps a 3:1 border and 4.5:1 text in light and dark.
API Reference
Select
Lets people pick one option from a list (DGA: Dropdown Input). Compose it from SelectTrigger, SelectValue and SelectContent. Pass `items` so the trigger shows the label of the chosen value, and label it with a Field.
| Prop | Type | Default |
|---|---|---|
{ value, label }[] | Record<string, ReactNode>Data structure of the items rendered in the select popup. When specified, `<Select.Value>` renders the label of the selected item instead of the raw value. | No default | |
| ||
ValueThe value of the select. Use when controlled. | No default | |
| ||
ValueThe uncontrolled value of the select when it's initially rendered. To render a controlled select, use the `value` prop instead. | No default | |
| ||
booleanWhether the component should ignore user interaction. | No default | |
| ||
booleanWhether multiple items can be selected. | No default | |
| ||
stringIdentifies the field when a form is submitted. | No default | |
| ||
(value: Value, details) => voidEvent handler called when the value of the select changes. | No default | |
| ||
booleanWhether the user should be unable to choose a different option from the select popup. | No default | |
| ||
booleanWhether the user must choose a value before submitting a form. | No default | |
| ||
Also accepts every prop of the Base UI Select (opens in a new tab).
SelectContent
The popup list. It opens below the trigger by default and is as wide as the trigger.
| Prop | Type | Default |
|---|---|---|
"top" | "bottom" | "inline-start" | "inline-end"Which side of the anchor element to align the popup against. May automatically change to avoid collisions. | "bottom" | |
| ||
"start" | "center" | "end"How to align the popup relative to the specified side. | "start" | |
| ||
numberDistance between the anchor and the popup in pixels. Also accepts a function that returns the distance to read the dimensions of the anchor and positioner elements, along with its side and alignment. The function takes a `data` object parameter with the following properties: - `data.anchor`: the dimensions of the anchor element with properties `width` and `height`. - `data.positioner`: the dimensions of the positioner element with properties `width` and `height`. - `data.side`: which side of the anchor element the positioner is aligned against. - `data.align`: how the positioner is aligned relative to the specified side. | 4 | |
| ||
booleanWhether the positioner overlaps the trigger so the selected item's text is aligned with the trigger's value text. This only applies to mouse input and is automatically disabled if there is not enough space. | false | |
| ||
Also accepts every prop of the Base UI Select (opens in a new tab).
SelectGroup
Groups related options under a SelectLabel.
Also accepts every prop of the Base UI Select (opens in a new tab).
SelectItem
One option. The chosen option shows a check mark on the end side.
| Prop | Type | Default |
|---|---|---|
ValueA unique value that identifies this select item. | No default | |
| ||
booleanWhether the component should ignore user interaction. | No default | |
| ||
Also accepts every prop of the Base UI Select (opens in a new tab).
SelectLabel
The heading of a SelectGroup.
Also accepts every prop of the Base UI Select (opens in a new tab).
SelectSeparator
Also accepts every prop of the Base UI Select (opens in a new tab).
SelectTrigger
The button that shows the chosen option and opens the list.
| Prop | Type | Default |
|---|---|---|
"md" | "lg"Height: `md` 32px, `lg` 40px. | "lg" | |
| ||
"default" | "lighter" | "darker"Fill: `default` (white), `lighter` or `darker`. All three keep a visible border. | "default" | |
| ||
Also accepts every prop of the Base UI Select (opens in a new tab).
SelectValue
The chosen option, or the `placeholder` while there is none.
| Prop | Type | Default |
|---|---|---|
ReactNodeThe placeholder value to display when no value is selected. This is overridden by `children` if specified, or by a null item's label in `items`. | No default | |
| ||
Also accepts every prop of the Base UI Select (opens in a new tab).
Deviations from DGA
- Named Select (Figma: Dropdown Input). Figma draws only the open list; the popup surface is ours.
- The highlighted option has a bar as well as a tint, because the tint alone is 1.1:1.
- Same states as Input: no outline, a full underline when focused or open, and the press shows the half underline. A mouse press opens the list straight away, so it goes from rest to open.
| Change | Reason |
|---|---|
Select: popup | The tint alone is 1.1:1 against the popup, so keyboard users could not see which option is highlighted (WCAG 2.4.7). |
Select: name | Named after the Base UI part and the shadcn convention, so it is found when people search for it. The docs page says it is DGA's Dropdown Input. |
Contrast
282 pairs checked · all pass WCAG 2.1 AAShow 51 statesHide
| Variant / state | Light | Dark |
|---|---|---|
| filled text | 18.09:1 AAA | 15.87:1 AAA |
| hovered text | 18.09:1 AAA | 15.87:1 AAA |
| focused text | 10.17:1 AAA | 12.04:1 AAA |
| pressed text | 9.24:1 AAA | 9.64:1 AAA |
| pressed placeholder | 6.69:1 AA | 5.06:1 AA |
| pressed prefix and suffix text | 9.24:1 AAA | 9.64:1 AAA |
| underline, focused and pressed | 18.74:1 3:1 | 16.80:1 3:1 |
| placeholder | 4.77:1 AA | 6.32:1 AA |
| prefix and suffix text | 10.17:1 AAA | 12.04:1 AAA |
| icon | 4.77:1 3:1 | 9.38:1 3:1 |
| border, default | 4.57:1 3:1 | 3.80:1 3:1 |
| border, hovered | 9.73:1 3:1 | 12.04:1 3:1 |
| border, focused and pressed | 17.93:1 3:1 | 16.80:1 3:1 |
| underline, error | 4.62:1 3:1 | 4.65:1 3:1 |
| border, error | 4.62:1 3:1 | 4.65:1 3:1 |
| filled text (lighter) | 17.64:1 AAA | 14.42:1 AAA |
| hovered text (lighter) | 17.64:1 AAA | 14.42:1 AAA |
| focused text (lighter) | 10.17:1 AAA | 12.04:1 AAA |
| pressed text (lighter) | 9.24:1 AAA | 9.64:1 AAA |
| pressed placeholder (lighter) | 6.69:1 AA | 5.06:1 AA |
| pressed prefix and suffix text (lighter) | 9.24:1 AAA | 9.64:1 AAA |
| underline, focused and pressed (lighter) | 18.28:1 3:1 | 15.27:1 3:1 |
| placeholder (lighter) | 4.65:1 AA | 5.75:1 AA |
| prefix and suffix text (lighter) | 9.92:1 AAA | 10.95:1 AAA |
| icon (lighter) | 4.65:1 3:1 | 8.52:1 3:1 |
| border, hovered (lighter) | 9.73:1 3:1 | 12.04:1 3:1 |
| border, focused and pressed (lighter) | 17.93:1 3:1 | 16.80:1 3:1 |
| underline, error (lighter) | 4.62:1 3:1 | 4.65:1 3:1 |
| border, error (lighter) | 4.62:1 3:1 | 4.65:1 3:1 |
| filled text (darker) | 16.44:1 AAA | 17.04:1 AAA |
| hovered text (darker) | 16.44:1 AAA | 17.04:1 AAA |
| focused text (darker) | 10.17:1 AAA | 12.04:1 AAA |
| pressed text (darker) | 9.24:1 AAA | 9.64:1 AAA |
| pressed placeholder (darker) | 6.69:1 AA | 5.06:1 AA |
| pressed prefix and suffix text (darker) | 9.24:1 AAA | 9.64:1 AAA |
| underline, focused and pressed (darker) | 17.03:1 3:1 | 18.03:1 3:1 |
| placeholder (darker) | 6.69:1 AA | 6.79:1 AA |
| prefix and suffix text (darker) | 9.24:1 AAA | 12.93:1 AAA |
| icon (darker) | 4.34:1 3:1 | 10.07:1 3:1 |
| border, hovered (darker) | 9.73:1 3:1 | 12.04:1 3:1 |
| border, focused and pressed (darker) | 17.93:1 3:1 | 16.80:1 3:1 |
| underline, error (darker) | 4.62:1 3:1 | 4.65:1 3:1 |
| border, error (darker) | 4.62:1 3:1 | 4.65:1 3:1 |
| border, hover and focus (lighter and darker) | 4.57:1 3:1 | 3.80:1 3:1 |
| read-only text | 18.09:1 AAA | 15.87:1 AAA |
| option text | 18.09:1 AAA | 15.87:1 AAA |
| option text, highlighted | 16.44:1 AAA | 12.71:1 AAA |
| group label | 4.77:1 AA | 6.32:1 AA |
| selected check | 4.74:1 3:1 | 10.38:1 3:1 |
| selected check, highlighted | 4.31:1 3:1 | 8.31:1 3:1 |
| highlight bar | 18.09:1 3:1 | 17.49:1 3:1 |
Text needs 4.5:1, icons and focus rings 3:1. Disabled states are exempt. Full contrast report