Select
Migration guide for Select from HeroUI v2 to v3
Refer to the v3 Select documentation for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.
Overview
The Select component in HeroUI v3 has been redesigned with a compound component pattern, requiring explicit structure with Select.Trigger, Select.Value, Select.Indicator, and Select.Popover components. Items use ListBox components instead of SelectItem and SelectSection.
Structure Changes
v2: Simple Structure
In v2, Select used a simple structure with props:
import { Select, SelectItem } from "@heroui/react";
<Select label="Select animal" placeholder="Choose one">
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
</Select>v3: Compound Components
In v3, Select requires compound components and uses ListBox for items:
import { Select, Label, ListBox } from "@heroui/react";
<Select placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>Key Changes
1. Component Structure
v2: Simple Select with SelectItem children
v3: Compound components (Select.Trigger, Select.Value, Select.Indicator, Select.Popover) with ListBox for items
2. Item Components
v2: SelectItem, SelectSection
v3: ListBox.Item, ListBox.Section (with Header and Separator)
3. Prop Changes
| v2 Prop | v3 Prop | Notes |
|---|---|---|
selectedKeys | value | Changed from Set/array to single value or array |
onSelectionChange | onChange | Renamed event handler |
defaultSelectedKeys | defaultValue | Renamed prop |
label | - | Removed (use Label component) |
description | - | Removed (use Description component) |
errorMessage | - | Removed (use FieldError component) |
variant | - | Removed (use Tailwind CSS) |
color | - | Removed (use Tailwind CSS) |
size | - | Removed (use Tailwind CSS) |
radius | - | Removed (use Tailwind CSS) |
classNames | - | Use className props |
startContent | - | Removed (customize trigger) |
endContent | - | Removed (customize trigger) |
selectorIcon | - | Removed (use Select.Indicator children) |
isClearable | - | Removed (implement manually) |
renderValue | - | Use Select.Value render prop |
labelPlacement | - | Removed |
selectionMode | selectionMode | Still exists |
4. Removed Props
The following props are no longer available in v3:
label- UseLabelcomponent insteaddescription- UseDescriptioncomponent insteaderrorMessage- UseFieldErrorcomponent insteadvariant,color,size,radius- Use Tailwind CSS classesclassNames- UseclassNameprops on individual componentsstartContent,endContent- CustomizeSelect.TriggerdirectlyselectorIcon- CustomizeSelect.IndicatorchildrenisClearable- Implement clear button manuallyrenderValue- UseSelect.Valuerender proplabelPlacement- Labels are always outsidedisableAnimation- Animations handled differentlypopoverProps,listboxProps,scrollShadowProps- Use component props directly
Migration Examples
Basic Usage
import { Select, SelectItem } from "@heroui/react";
const animals = [
{ key: "cat", label: "Cat" },
{ key: "dog", label: "Dog" },
{ key: "elephant", label: "Elephant" },
];
export default function App() {
return (
<Select className="max-w-xs" label="Select an animal">
{animals.map((animal) => (
<SelectItem key={animal.key}>{animal.label}</SelectItem>
))}
</Select>
);
}import { Select, Label, ListBox } from "@heroui/react";
const animals = [
{ id: "cat", name: "Cat" },
{ id: "dog", name: "Dog" },
{ id: "elephant", name: "Elephant" },
];
export default function App() {
return (
<Select className="w-[256px]" placeholder="Select one">
<Label>Select an animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
{animals.map((animal) => (
<ListBox.Item key={animal.id} id={animal.id} textValue={animal.name}>
{animal.name}
<ListBox.ItemIndicator />
</ListBox.Item>
))}
</ListBox>
</Select.Popover>
</Select>
);
}With Description
<Select
label="Select animal"
description="Choose your favorite"
placeholder="Choose one"
>
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
</Select>import { Description } from "@heroui/react";
<Select placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Description>Choose your favorite</Description>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>Controlled Selection
import { useState } from "react";
const [value, setValue] = useState(new Set([]));
<Select
selectedKeys={value}
onSelectionChange={setValue}
label="Select animal"
>
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
</Select>import { useState } from "react";
import type { Key } from "@heroui/react";
const [value, setValue] = useState<Key | null>(null);
<Select value={value} onChange={setValue} placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>Multiple Selection
import { useState } from "react";
const [value, setValue] = useState(new Set([]));
<Select
selectionMode="multiple"
selectedKeys={value}
onSelectionChange={setValue}
label="Select animals"
>
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
</Select>import { useState } from "react";
import type { Key } from "@heroui/react";
const [value, setValue] = useState<Key[]>([]);
<Select
selectionMode="multiple"
value={value}
onChange={setValue}
placeholder="Choose multiple"
>
<Label>Select animals</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>With Sections
import { Select, SelectItem, SelectSection } from "@heroui/react";
<Select label="Select animal">
<SelectSection title="Mammals">
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
</SelectSection>
<SelectSection title="Birds">
<SelectItem key="eagle">Eagle</SelectItem>
<SelectItem key="parrot">Parrot</SelectItem>
</SelectSection>
</Select>import { Select, Label, ListBox, Header, Separator } from "@heroui/react";
<Select placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Section>
<Header>Mammals</Header>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox.Section>
<Separator />
<ListBox.Section>
<Header>Birds</Header>
<ListBox.Item id="eagle" textValue="Eagle">
Eagle
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="parrot" textValue="Parrot">
Parrot
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox.Section>
</ListBox>
</Select.Popover>
</Select>With Validation
<Select
isInvalid
errorMessage="Please select an option"
label="Select animal"
>
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
</Select>import { FieldError } from "@heroui/react";
<Select isInvalid placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
<FieldError>Please select an option</FieldError>
</Select>Disabled Items
<Select disabledKeys={["dog"]} label="Select animal">
<SelectItem key="cat">Cat</SelectItem>
<SelectItem key="dog">Dog</SelectItem>
</Select><Select disabledKeys={["dog"]} placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
<ListBox.Item id="dog" textValue="Dog">
Dog
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>Custom Indicator
<Select selectorIcon={<CustomIcon />} label="Select animal">
<SelectItem key="cat">Cat</SelectItem>
</Select><Select placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator>
<CustomIcon />
</Select.Indicator>
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>Custom Value Display
<Select
renderValue={(items) => {
return items.map((item) => item.data?.name).join(", ");
}}
label="Select animal"
>
<SelectItem key="cat">Cat</SelectItem>
</Select><Select placeholder="Choose one">
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value>
{({ defaultChildren, state }) => {
if (state.selectedItems.length === 0) {
return defaultChildren;
}
return state.selectedItems.map((item) => item.textValue).join(", ");
}}
</Select.Value>
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>Controlled Open State
import { useState } from "react";
const [isOpen, setIsOpen] = useState(false);
<Select
isOpen={isOpen}
onOpenChange={setIsOpen}
label="Select animal"
>
<SelectItem key="cat">Cat</SelectItem>
</Select>import { useState } from "react";
const [isOpen, setIsOpen] = useState(false);
<Select
isOpen={isOpen}
onOpenChange={setIsOpen}
placeholder="Choose one"
>
<Label>Select animal</Label>
<Select.Trigger>
<Select.Value />
<Select.Indicator />
</Select.Trigger>
<Select.Popover>
<ListBox>
<ListBox.Item id="cat" textValue="Cat">
Cat
<ListBox.ItemIndicator />
</ListBox.Item>
</ListBox>
</Select.Popover>
</Select>Component Anatomy
The v3 Select follows this structure:
Select (Root)
├── Label (optional)
├── Select.Trigger
│ ├── Select.Value
│ └── Select.Indicator
├── Description (optional)
├── Select.Popover
│ └── ListBox
│ ├── ListBox.Item
│ │ ├── Label (optional)
│ │ ├── Description (optional)
│ │ └── ListBox.ItemIndicator
│ └── ListBox.Section (optional)
│ ├── Header
│ └── ListBox.Item
└── FieldError (optional)Important Notes
Item Keys
- v2: Used
keyprop onSelectItem - v3: Use
idprop onListBox.Item(required) andtextValueprop (required for accessibility)
Selection Value Types
- v2:
selectedKeyswas aSet<Key>or array - v3:
valueisKey | nullfor single selection orKey[]for multiple selection
Clear Button
The isClearable prop has been removed. To implement a clear button:
<Select value={value} onChange={setValue}>
<Select.Trigger>
<Select.Value />
{value && (
<button onClick={() => setValue(null)}>Clear</button>
)}
<Select.Indicator />
</Select.Trigger>
{/* ... */}
</Select>Breaking Changes Summary
- Component Structure: Must use compound components (
Select.Trigger,Select.Value,Select.Indicator,Select.Popover) - Item Components:
SelectItem→ListBox.Item,SelectSection→ListBox.Section - Label/Description/Error: Use separate components instead of props
- Selection Props:
selectedKeys/onSelectionChange→value/onChange - Styling Props Removed:
variant,color,size,radius- use Tailwind CSS - ClassNames Removed: Use
classNameprops on individual components - Content Props Removed:
startContent,endContent- customize trigger directly - Clear Button:
isClearableremoved - implement manually - Custom Value: Use
Select.Valuerender prop instead ofrenderValue - Item Props:
key→idandtextValue(required)
Tips for Migration
- Update structure: Wrap Select content in compound components
- Add Label: Use
Labelcomponent for Select label - Add Description: Use
Descriptioncomponent - Add Error: Use
FieldErrorcomponent for validation messages - Update items: Replace
SelectItemwithListBox.Item(addidandtextValue) - Update sections: Replace
SelectSectionwithListBox.SectionandHeader - Update selection: Change
selectedKeys/onSelectionChangetovalue/onChange - Add indicators: Include
ListBox.ItemIndicatorin each item - Update styling: Use Tailwind CSS classes for variants, colors, sizes
- Customize trigger: Add custom content directly to
Select.Triggerif needed
Need Help?
For v3 Select features and API:
- See the API Reference
- Check interactive examples
For community support: