Select primitive wrappers
Fruits
import {
Select,
...import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
export default function SelectDemoExample() {
return (
<Select>
<SelectTrigger class="w-full max-w-48">
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
{['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple'].map(fruit => (
<SelectItem value={fruit.toLowerCase()}>{fruit}</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}
Installation
npx @fictjs/shadcn@latest add selectInstall the runtime dependencies:
npm install @fictjs/radix-uiCopy the Fict shadcn component into your project.
Source
components/ui/select.tsx
src/components/ui/select.tsx
import { Select as SelectPrimitive } from '@fictjs/radix-ui'
import { cn } from '@/lib/cn'
export const Select = SelectPrimitive.Root
type GenericProps = {
class?: string
children?: unknown
[key: string]: unknown
}
export function SelectTrigger(props: GenericProps) {
const { class: className, children, ...rest } = props
return (
<SelectPrimitive.Trigger
class={cn(
'flex h-9 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
{...rest}
>
{children}
</SelectPrimitive.Trigger>
)
}
export function SelectValue(props: GenericProps) {
const { class: className, ...rest } = props
return <SelectPrimitive.Value class={cn('truncate', className)} {...rest} />
}
export function SelectContent(props: GenericProps) {
const { class: className, children, ...rest } = props
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
class={cn('relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md', className)}
{...rest}
>
<SelectPrimitive.Viewport class='p-1'>{children}</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
export function SelectGroup(props: GenericProps) {
return <SelectPrimitive.Group {...props} />
}
export function SelectLabel(props: GenericProps) {
const { class: className, ...rest } = props
return <SelectPrimitive.Label class={cn('px-2 py-1.5 text-sm font-semibold', className)} {...rest} />
}
export function SelectSeparator(props: GenericProps) {
const { class: className, ...rest } = props
return <SelectPrimitive.Separator class={cn('-mx-1 my-1 h-px bg-muted', className)} {...rest} />
}
export function SelectItem(props: GenericProps) {
const { class: className, children, ...rest } = props
return (
<SelectPrimitive.Item
class={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground',
className,
)}
{...rest}
>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
<SelectPrimitive.ItemIndicator class='absolute right-2'>✓</SelectPrimitive.ItemIndicator>
</SelectPrimitive.Item>
)
}
Usage
Examples
Align Item
import {
Select,
...import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { Switch } from '@/components/ui/switch'
export default function SelectAlignItemExample() {
let aligned = $state(true)
return (
<div class="grid w-full max-w-xs gap-4">
<div class="flex items-center justify-between gap-4">
<div>
<label for="align-item">Align Item</label>
<p class="text-sm text-muted-foreground">Toggle to align the item with the trigger.</p>
</div>
<Switch
id="align-item"
checked={() => aligned}
onCheckedChange={value => {
aligned = value
}}
/>
</div>
<Select defaultValue="banana">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent position={aligned ? 'item-aligned' : 'popper'}>
<SelectGroup>
{['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple'].map(fruit => (
<SelectItem value={fruit.toLowerCase()}>{fruit}</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</div>
)
}
Groups
FruitsVegetables
import {
Select,
...import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
export default function SelectGroupsExample() {
return (
<Select>
<SelectTrigger class="w-full max-w-48">
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Fruits</SelectLabel>
{['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple'].map(item => (
<SelectItem value={item.toLowerCase()}>{item}</SelectItem>
))}
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Vegetables</SelectLabel>
{['Carrot', 'Broccoli', 'Spinach'].map(item => (
<SelectItem value={item.toLowerCase()}>{item}</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}
Scrollable
North AmericaEurope & AfricaAsiaAustralia & PacificSouth America
import {
Select,
...import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
const zones = {
'North America': [
'Eastern Standard Time',
'Central Standard Time',
'Mountain Standard Time',
'Pacific Standard Time',
'Alaska Standard Time',
'Hawaii Standard Time',
],
'Europe & Africa': [
'Greenwich Mean Time',
'Central European Time',
'Eastern European Time',
'Western European Summer Time',
'Central Africa Time',
'East Africa Time',
],
Asia: [
'Moscow Time',
'India Standard Time',
'China Standard Time',
'Japan Standard Time',
'Korea Standard Time',
'Indonesia Central Standard Time',
],
'Australia & Pacific': [
'Australian Western Standard Time',
'Australian Central Standard Time',
'Australian Eastern Standard Time',
'New Zealand Standard Time',
'Fiji Time',
],
'South America': ['Argentina Time', 'Bolivia Time', 'Brasilia Time', 'Chile Standard Time'],
}
export default function SelectScrollableExample() {
return (
<Select>
<SelectTrigger class="w-full max-w-64">
<SelectValue placeholder="Select a timezone" />
</SelectTrigger>
<SelectContent>
{Object.entries(zones).map(([region, items]) => (
<SelectGroup>
<SelectLabel>{region}</SelectLabel>
{items.map((item, index) => (
<SelectItem value={`${region}-${index}`}>{item}</SelectItem>
))}
</SelectGroup>
))}
</SelectContent>
</Select>
)
}
Disabled
import {
Select,
...import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
export default function SelectDisabledExample() {
return (
<Select disabled>
<SelectTrigger class="w-full max-w-48">
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple'].map(fruit => (
<SelectItem value={fruit.toLowerCase()} disabled={fruit === 'Grapes'}>
{fruit}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
)
}
Invalid
Please select a fruit.
import {
Select,
...import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
export default function SelectInvalidExample() {
return (
<div class="grid w-full max-w-48 gap-2">
<label>Fruit</label>
<Select>
<SelectTrigger aria-invalid="true">
<SelectValue placeholder="Select a fruit" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{['Apple', 'Banana', 'Blueberry', 'Grapes', 'Pineapple'].map(fruit => (
<SelectItem value={fruit.toLowerCase()}>{fruit}</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
<p class="text-sm text-destructive">Please select a fruit.</p>
</div>
)
}
Rtl
الفواكهالخضروات
import {
Select,
...import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
const translations = {
ar: {
dir: 'rtl',
placeholder: 'اختر فاكهة',
groups: [
[
'الفواكه',
[
['apple', 'تفاح'],
['banana', 'موز'],
['blueberry', 'توت أزرق'],
['grapes', 'عنب'],
['pineapple', 'أناناس'],
],
],
[
'الخضروات',
[
['carrot', 'جزر'],
['broccoli', 'بروكلي'],
['spinach', 'سبانخ'],
],
],
],
},
he: {
dir: 'rtl',
placeholder: 'בחר פרי',
groups: [
[
'פירות',
[
['apple', 'תפוח'],
['banana', 'בננה'],
['blueberry', 'אוכמניה'],
['grapes', 'ענבים'],
['pineapple', 'אננס'],
],
],
[
'ירקות',
[
['carrot', 'גזר'],
['broccoli', 'ברוקולי'],
['spinach', 'תרד'],
],
],
],
},
en: {
dir: 'ltr',
placeholder: 'Select a fruit',
groups: [
[
'Fruits',
[
['apple', 'Apple'],
['banana', 'Banana'],
['blueberry', 'Blueberry'],
['grapes', 'Grapes'],
['pineapple', 'Pineapple'],
],
],
[
'Vegetables',
[
['carrot', 'Carrot'],
['broccoli', 'Broccoli'],
['spinach', 'Spinach'],
],
],
],
},
} as const
export default function SelectRtlExample() {
let language = $state<keyof typeof translations>('ar')
let value = $state('')
const text = () => translations[language]
return (
<div class="grid gap-4">
<select
value={language}
onChange={event => {
language = event.currentTarget.value as keyof typeof translations
}}
>
<option value="ar">Arabic (العربية)</option>
<option value="he">Hebrew (עברית)</option>
<option value="en">English</option>
</select>
<Select
value={() => value}
onValueChange={next => {
value = next
}}
>
<SelectTrigger class="w-32" dir={text().dir}>
<SelectValue placeholder={text().placeholder} />
</SelectTrigger>
<SelectContent dir={text().dir}>
{text().groups.map(([label, items], groupIndex) => (
<>
<SelectGroup>
<SelectLabel>{label}</SelectLabel>
{items.map(([itemValue, itemLabel]) => (
<SelectItem value={itemValue}>{itemLabel}</SelectItem>
))}
</SelectGroup>
{groupIndex === 0 ? <SelectSeparator /> : null}
</>
))}
</SelectContent>
</Select>
</div>
)
}