ZaidanHomeDocsComponentsBlocksChartsTypesetCreate

Command Palette

Search for a command to run...

GitHub
New
Sections
  • Introduction
  • Components
  • Blocks
  • Installation
  • Customization
  • Dark Mode
  • Typeset
  • Zaidan Skills
  • FAQ
  • Roadmap
  • Changelog
Installation
  • Astro
  • Manual
  • Solid Start
  • TanStack Router
  • TanStack Start
  • Vite
Blocks
  • Data Grid
  • Event Calendar
  • Filters
  • Gantt
  • Image Crop
  • Kanban
  • Message Scroller
  • Questionnaire
  • Sortable
Components
  • Accordion
  • Alert
  • Alert Dialog
  • Aspect Ratio
  • Attachment
  • Avatar
  • Badge
  • Breadcrumb
  • Bubble
  • Button
  • Button Group
  • Calendar
  • Card
  • Carousel
  • Chart
  • Checkbox
  • Collapsible
  • Combobox
  • Command
  • Context Menu
  • Dialog
  • Drawer
  • Dropdown Menu
  • Empty
  • Field
  • Hover Card
  • Input
  • Input Group
  • Input OTP
  • Item
  • Kbd
  • Label
  • Marker
  • Menubar
  • Message
  • Native Select
  • Navigation Menu
  • Pagination
  • Popover
  • Progress
  • Radio Group
  • Resizable
  • Scroll Area
  • Select
  • Separator
  • Sheet
  • Sidebar
  • Skeleton
  • Slider
  • Spinner
  • Switch
  • Table
  • Tabs
  • Textarea
  • Toast
  • Toggle
  • Toggle Group
  • Tooltip

Select

Displays a list of options for the user to pick from—triggered by a button.

Kobalte
Kobalte

The Select component wraps Kobalte's Select primitive. It is data-driven rather than compositional: pass an options array plus optionValue / optionTextValue accessors, and render each option through itemComponent.

1
import {
2
Select,
3
SelectContent,
4
SelectItem,
5
SelectTrigger,
6
SelectValue,
7
} from "~/components/ui/select";
8
9
const items = [
10
{ label: "Apple", value: "apple" },
11
{ label: "Banana", value: "banana" },
12
{ label: "Blueberry", value: "blueberry" },
13
{ label: "Grapes", value: "grapes" },
14
{ label: "Pineapple", value: "pineapple" },
15
];
16
17
export default function SelectDemo() {
18
return (
19
<Select
20
options={items}
21
optionValue="value"
22
optionTextValue="label"
23
placeholder="Select a fruit"
24
itemComponent={(props) => (
25
<SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem>
26
)}
27
>
28
<SelectTrigger aria-label="Fruit" class="w-full max-w-48">
29
<SelectValue<(typeof items)[number]>>{(state) => state.selectedOption().label}</SelectValue>
30
</SelectTrigger>
31
<SelectContent />
32
</Select>
33
);
34
}

Installation

pnpm dlx shadcn@latest add @zaidan/select
npx shadcn@latest add @zaidan/select
yarn dlx shadcn@latest add @zaidan/select
bunx --bun shadcn@latest add @zaidan/select

Usage

1
import {
2
Select,
3
SelectContent,
4
SelectItem,
5
SelectTrigger,
6
SelectValue,
7
} from "~/components/ui/select";
8
9
const items = [
10
{ label: "Light", value: "light" },
11
{ label: "Dark", value: "dark" },
12
{ label: "System", value: "system" },
13
];
1
<Select
2
options={items}
3
optionValue="value"
4
optionTextValue="label"
5
placeholder="Theme"
6
itemComponent={(props) => (
7
<SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem>
8
)}
9
>
10
<SelectTrigger aria-label="Theme" class="w-45">
11
<SelectValue<(typeof items)[number]>>{(state) => state.selectedOption().label}</SelectValue>
12
</SelectTrigger>
13
<SelectContent />
14
</Select>

See the Kobalte Select documentation for the full set of data-driven props (multiple, value / onChange, optionGroupChildren, etc.) supported by the root.

Composition

Select is data-driven: options are supplied once on the root, and SelectContent renders them internally through itemComponent. Use the following composition to build a Select:

Select (options, optionValue, optionTextValue, itemComponent, sectionComponent?)
├── SelectTrigger
│ └── SelectValue
└── SelectContent

itemComponent renders a SelectItem for each option. sectionComponent, used together with optionGroupChildren, renders a SelectGroup (with a SelectLabel and, optionally, a SelectSeparator) for each group of options.

Groups

Pass grouped data with optionGroupChildren and render a SelectGroup / SelectLabel per section from sectionComponent.

1
import { Show } from "solid-js";
2
import {
3
Select,
4
SelectContent,
5
SelectGroup,
6
SelectItem,
7
SelectLabel,
8
SelectSeparator,
9
SelectTrigger,
10
SelectValue,
11
} from "~/components/ui/select";
12
13
type FoodOption = {
14
label: string;
15
value: string;
16
};
17
18
type Food = {
19
label: string;
20
options: FoodOption[];
21
};
22
23
const foods: Food[] = [
24
{
25
label: "Fruits",
26
options: [
27
{ label: "Apple", value: "apple" },
28
{ label: "Banana", value: "banana" },
29
{ label: "Blueberry", value: "blueberry" },
30
],
31
},
32
{
33
label: "Vegetables",
34
options: [
35
{ label: "Carrot", value: "carrot" },
36
{ label: "Broccoli", value: "broccoli" },
37
{ label: "Spinach", value: "spinach" },
38
],
39
},
40
];
41
42
export default function SelectGroups() {
43
return (
44
<Select<FoodOption, Food>
45
options={foods}
46
optionValue="value"
47
optionTextValue="label"
48
optionGroupChildren="options"
49
placeholder="Select a food"
50
itemComponent={(props) => (
51
<SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem>
52
)}
53
sectionComponent={(props) => (
54
<>
55
<Show when={props.section.index !== 0}>
56
<SelectSeparator />
57
</Show>
58
<SelectGroup>
59
<SelectLabel>{props.section.rawValue.label}</SelectLabel>
60
</SelectGroup>
61
</>
62
)}
63
>
64
<SelectTrigger aria-label="Food" class="w-full max-w-48">
65
<SelectValue<FoodOption>>{(state) => state.selectedOption().label}</SelectValue>
66
</SelectTrigger>
67
<SelectContent />
68
</Select>
69
);
70
}

Scrollable

A select with many items, grouped by region, that scrolls.

1
import { Show } from "solid-js";
2
import {
3
Select,
4
SelectContent,
5
SelectGroup,
6
SelectItem,
7
SelectLabel,
8
SelectSeparator,
9
SelectTrigger,
10
SelectValue,
11
} from "~/components/ui/select";
12
13
type TimezoneOption = {
14
label: string;
15
value: string;
16
};
17
18
type Region = {
19
label: string;
20
options: TimezoneOption[];
21
};
22
23
const regions: Region[] = [
24
{
25
label: "North America",
26
options: [
27
{ label: "Eastern Standard Time", value: "est" },
28
{ label: "Central Standard Time", value: "cst" },
29
{ label: "Mountain Standard Time", value: "mst" },
30
{ label: "Pacific Standard Time", value: "pst" },
31
{ label: "Alaska Standard Time", value: "akst" },
32
{ label: "Hawaii Standard Time", value: "hst" },
33
],
34
},
35
{
36
label: "Europe & Africa",
37
options: [
38
{ label: "Greenwich Mean Time", value: "gmt" },
39
{ label: "Central European Time", value: "cet" },
40
{ label: "Eastern European Time", value: "eet" },
41
{ label: "Western European Summer Time", value: "west" },
42
{ label: "Central Africa Time", value: "cat" },
43
{ label: "East Africa Time", value: "eat" },
44
],
45
},
46
{
47
label: "Asia",
48
options: [
49
{ label: "Moscow Time", value: "msk" },
50
{ label: "India Standard Time", value: "ist" },
51
{ label: "China Standard Time", value: "cst_china" },
52
{ label: "Japan Standard Time", value: "jst" },
53
{ label: "Korea Standard Time", value: "kst" },
54
{ label: "Indonesia Central Standard Time", value: "ist_indonesia" },
55
],
56
},
57
{
58
label: "Australia & Pacific",
59
options: [
60
{ label: "Australian Western Standard Time", value: "awst" },
61
{ label: "Australian Central Standard Time", value: "acst" },
62
{ label: "Australian Eastern Standard Time", value: "aest" },
63
{ label: "New Zealand Standard Time", value: "nzst" },
64
{ label: "Fiji Time", value: "fjt" },
65
],
66
},
67
{
68
label: "South America",
69
options: [
70
{ label: "Argentina Time", value: "art" },
71
{ label: "Bolivia Time", value: "bot" },
72
{ label: "Brasilia Time", value: "brt" },
73
{ label: "Chile Standard Time", value: "clt" },
74
],
75
},
76
];
77
78
export default function SelectScrollable() {
79
return (
80
<Select<TimezoneOption, Region>
81
options={regions}
82
optionValue="value"
83
optionTextValue="label"
84
optionGroupChildren="options"
85
placeholder="Select a timezone"
86
itemComponent={(props) => (
87
<SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem>
88
)}
89
sectionComponent={(props) => (
90
<>
91
<Show when={props.section.index !== 0}>
92
<SelectSeparator />
93
</Show>
94
<SelectGroup>
95
<SelectLabel>{props.section.rawValue.label}</SelectLabel>
96
</SelectGroup>
97
</>
98
)}
99
>
100
<SelectTrigger aria-label="Time zone" class="w-full max-w-64">
101
<SelectValue<TimezoneOption>>{(state) => state.selectedOption().label}</SelectValue>
102
</SelectTrigger>
103
<SelectContent />
104
</Select>
105
);
106
}

Disabled

Pass disabled on Select to disable the whole control, or optionDisabled to disable individual options.

1
import {
2
Select,
3
SelectContent,
4
SelectItem,
5
SelectTrigger,
6
SelectValue,
7
} from "~/components/ui/select";
8
9
const items = [
10
{ label: "Apple", value: "apple", disabled: false },
11
{ label: "Banana", value: "banana", disabled: false },
12
{ label: "Blueberry", value: "blueberry", disabled: false },
13
{ label: "Grapes", value: "grapes", disabled: true },
14
{ label: "Pineapple", value: "pineapple", disabled: false },
15
];
16
17
export default function SelectDisabled() {
18
return (
19
<Select
20
options={items}
21
optionValue="value"
22
optionTextValue="label"
23
optionDisabled="disabled"
24
placeholder="Select a fruit"
25
disabled
26
itemComponent={(props) => (
27
<SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem>
28
)}
29
>
30
<SelectTrigger aria-label="Fruit" class="w-full max-w-48">
31
<SelectValue<(typeof items)[number]>>{(state) => state.selectedOption().label}</SelectValue>
32
</SelectTrigger>
33
<SelectContent />
34
</Select>
35
);
36
}

Invalid

Add the data-invalid attribute to the Field component, set validationState="invalid" on Select, and add the aria-invalid attribute to the SelectTrigger component to show an error state.

1
<Field data-invalid>
2
<FieldLabel for="fruit">Fruit</FieldLabel>
3
<Select validationState="invalid" /* options, itemComponent, ... */>
4
<SelectTrigger id="fruit" aria-invalid>
5
<SelectValue />
6
</SelectTrigger>
7
<SelectContent />
8
</Select>
9
</Field>
Please select a fruit.
1
import { Field, FieldError, FieldLabel } from "~/components/ui/field";
2
import {
3
Select,
4
SelectContent,
5
SelectItem,
6
SelectTrigger,
7
SelectValue,
8
} from "~/components/ui/select";
9
10
const items = [
11
{ label: "Apple", value: "apple" },
12
{ label: "Banana", value: "banana" },
13
{ label: "Blueberry", value: "blueberry" },
14
];
15
16
export default function SelectInvalid() {
17
return (
18
<Field data-invalid class="w-full max-w-48">
19
<FieldLabel for="invalid-fruit">Fruit</FieldLabel>
20
<Select
21
options={items}
22
optionValue="value"
23
optionTextValue="label"
24
placeholder="Select a fruit"
25
validationState="invalid"
26
itemComponent={(props) => (
27
<SelectItem item={props.item}>{props.item.rawValue.label}</SelectItem>
28
)}
29
>
30
<SelectTrigger id="invalid-fruit" aria-invalid>
31
<SelectValue<(typeof items)[number]>>
32
{(state) => state.selectedOption().label}
33
</SelectValue>
34
</SelectTrigger>
35
<SelectContent />
36
</Select>
37
<FieldError>Please select a fruit.</FieldError>
38
</Field>
39
);
40
}

API Reference

The Select component uses Kobalte's Select primitive under the hood. Here are the main props:

PropTypeDefaultDescription
optionsT[]-The array of options (or option groups) to display
optionValuekeyof T | ((option: T) => string)-Key or function to get the option's value
optionTextValuekeyof T | ((option: T) => string)-Key or function to get the option's text
optionLabelkeyof T | ((option: T) => string)-Key or function to get the option's display label
optionDisabledkeyof T | ((option: T) => boolean)-Key or function to determine if an option is disabled
optionGroupChildrenkeyof T-Key on a group item that holds its child options
itemComponent(props: { item }) => JSX.Element-Renders a SelectItem for each option
sectionComponent(props: { section }) => JSX.Element-Renders a SelectGroup for each option group
valueT-The controlled value of the select
defaultValueT-The default value of the select
onChange(value: T) => void-Handler called when the value changes
placeholderJSX.Element-Placeholder content when no value is selected
disabledbooleanfalseWhether the select is disabled
validationState"valid" | "invalid"-The validation state of the select
multiplebooleanfalseWhether multiple options can be selected
sameWidthbooleantrueWhether the content has the same width as the trigger
gutternumber4Distance between the trigger and content
placementPlacement"bottom"Preferred placement of the content

SelectTrigger

PropTypeDefaultDescription
size"sm" | "default""default"The size variant of the trigger
classstring-Additional CSS classes

SelectContent

Renders the dropdown portal and listbox. It takes no children—options are rendered internally from the root's options and itemComponent.

SelectItem

PropTypeDefaultDescription
itemthe item passed to itemComponent-The item object from Kobalte

SelectGroup / SelectLabel / SelectSeparator

Used inside sectionComponent to render a labeled group of options, optionally separated from the previous group by a SelectSeparator.

See the Kobalte Select API reference for the complete prop list.

On This Page

  • Installation
  • Usage
  • Composition
  • Groups
  • Scrollable
  • Disabled
  • Invalid
  • API Reference
    • SelectTrigger
    • SelectContent
    • SelectItem
    • SelectGroup / SelectLabel / SelectSeparator
Built by Kevin Abatan. The source code is available on GitHub.