Select
A customizable select component that supports single/multiple selections, search functionality, floating labels, and both desktop (popover) and mobile (sheet) experiences.
Installation
npx shadcn@latest add selectUsage
import { useState } from "react";
import {
Select,
SelectInputTrigger,
SelectValue,
SelectBody,
SelectContentAutoLayout,
type Option,
} from "@hoag/ui/select";
export default function Demo() {
const [value, setValue] = useState<Option[]>([]);
const options: Option[] = [
{ name: "React", value: "react" },
{ name: "Vue", value: "vue" },
{ name: "Angular", value: "angular" },
];
return (
<Select value={value} onValueChange={setValue}>
<SelectInputTrigger placeholder="Select framework">
<SelectValue />
</SelectInputTrigger>
<SelectBody>
<SelectContentAutoLayout options={options} />
</SelectBody>
</Select>
);
}Basic Select
const [value, setValue] = useState<Option[]>([]);
const options: Option[] = [
{ name: "React", value: "react" },
{ name: "Vue", value: "vue" },
{ name: "Angular", value: "angular" },
{ name: "Svelte", value: "svelte" },
];
<Select value={value} onValueChange={setValue}>
<SelectInputTrigger placeholder="Select framework">
<SelectValue />
</SelectInputTrigger>
<SelectBody>
<SelectContentAutoLayout options={options} />
</SelectBody>
</Select>;With Label
<Select value={value} onValueChange={setValue}>
<SelectInputTrigger label="Framework" placeholder="Select framework">
<SelectValue />
</SelectInputTrigger>
<SelectBody>
<SelectContentAutoLayout options={options} />
</SelectBody>
</Select>Required Field
<Select value={value} onValueChange={setValue}>
<SelectInputTrigger
label="Country"
isRequired
placeholder="Select your country"
>
<SelectValue />
</SelectInputTrigger>
<SelectBody>
<SelectContentAutoLayout options={countries} />
</SelectBody>
</Select>With Search
Enable search functionality to filter options by passing hasSearch prop.
<Select value={value} onValueChange={setValue}>
<SelectInputTrigger
label="Country"
placeholder="Select country"
hasRemoveIcon
>
<SelectValue />
</SelectInputTrigger>
<SelectBody>
<SelectContentAutoLayout hasSearch options={countries} />
</SelectBody>
</Select>Multiple Selection
Allow users to select multiple options with the multiple prop.
<Select value={value} onValueChange={setValue}>
<SelectInputTrigger
label="Skills"
placeholder="Select your skills"
hasRemoveIcon
>
<SelectValue>
{(value) => {
if (!value?.length) return "";
return value.length === 1 ? value[0]?.name : `${value.length} selected`;
}}
</SelectValue>
</SelectInputTrigger>
<SelectBody>
<SelectContentAutoLayout hasSearch multiple options={skills} />
</SelectBody>
</Select>Sizes
Select supports different sizes for the trigger input.
<Select value={value} onValueChange={setValue}>
<SelectInputTrigger size="default" placeholder="Default size">
<SelectValue />
</SelectInputTrigger>
<SelectBody>
<SelectContentAutoLayout options={options} />
</SelectBody>
</Select>
<Select value={value} onValueChange={setValue}>
<SelectInputTrigger size="lg" placeholder="Large size">
<SelectValue />
</SelectInputTrigger>
<SelectBody>
<SelectContentAutoLayout options={options} />
</SelectBody>
</Select>Disabled
<Select value={value} onValueChange={setValue} disabled>
<SelectInputTrigger label="Disabled Select" placeholder="Cannot interact">
<SelectValue />
</SelectInputTrigger>
<SelectBody>
<SelectContentAutoLayout options={options} />
</SelectBody>
</Select>Error State
<Select value={value} onValueChange={setValue}>
<SelectInputTrigger
label="Framework"
isRequired
isError
errorText="Please select a framework"
placeholder="Select framework"
>
<SelectValue />
</SelectInputTrigger>
<SelectBody>
<SelectContentAutoLayout options={frameworks} />
</SelectBody>
</Select>With Helper Text
<Select value={value} onValueChange={setValue}>
<SelectInputTrigger
label="Newsletter"
helpText="Choose how often you'd like to receive updates"
placeholder="Select frequency"
>
<SelectValue />
</SelectInputTrigger>
<SelectBody>
<SelectContentAutoLayout options={frequencies} />
</SelectBody>
</Select>API Reference
Select Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | Option[] | - | Controlled selected value(s) |
onValueChange | (value: Option[]) => void | - | Callback when value changes |
defaultValue | Option[] | - | Default selected value(s) |
disabled | boolean | false | Disable the select |
open | boolean | - | Controlled open state |
onOpenChange | (open: boolean) => void | - | Callback when open state changes |
defaultOpen | boolean | - | Default open state |
SelectInputTrigger Props
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | - | Label text (acts as floating label) |
placeholder | string | - | Placeholder text |
isRequired | boolean | false | Show required indicator (*) |
isError | boolean | false | Error state styling |
errorText | string | - | Error message to display below input |
helpText | string | - | Helper text below input |
size | "default" | "lg" | "default" | Input size |
hasRemoveIcon | boolean | false | Show clear/remove icon when value selected |
enableFloatingLabel | boolean | true | Enable floating label animation |
disabled | boolean | false | Disable the trigger |
addonBefore | ReactNode | - | Icon/content before input |
icon | ReactNode | - | Custom icon (overrides default caret) |
SelectContentAutoLayout Props
| Prop | Type | Default | Description |
|---|---|---|---|
options | Option[] | Required | Array of options to display |
hasSearch | boolean | false | Enable search/filter functionality |
multiple | boolean | false | Enable multiple selection with checkboxes |
searchPlaceholder | string | "Search..." | Placeholder for search input |
Option Type
type Option = {
name: string; // Display text
value: string; // Unique value
description?: string; // Optional description text
disabled?: boolean; // Disable this option
};Accessibility
- ✅ Keyboard navigation support (Arrow keys, Enter, Escape)
- ✅ ARIA attributes for screen readers
- ✅ Focus management and trap
- ✅ Built-in search with keyboard input (when
hasSearchenabled) - ✅ Responsive: Popover on desktop, Sheet on mobile
- ✅ Disabled state for both select and individual options
- ✅ Clear button with
hasRemoveIconprop
Notes
- Auto Layout: Use
SelectContentAutoLayoutto automatically render options from an array - Responsive Design: Component automatically uses Popover on desktop and Sheet on mobile
- Search: Enable with
hasSearchprop - filters options in real-time - Multiple Selection: Use
multipleprop - includes checkboxes and custom value display - Floating Labels: Labels animate automatically when value is selected (controlled by
enableFloatingLabel) - State Management: Always use controlled component pattern with
valueandonValueChange - Custom Rendering: Use render prop in
SelectValuefor custom display logic in multiple selection mode