27.5k

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 Propv3 PropNotes
selectedKeysvalueChanged from Set/array to single value or array
onSelectionChangeonChangeRenamed event handler
defaultSelectedKeysdefaultValueRenamed 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
selectionModeselectionModeStill exists

4. Removed Props

The following props are no longer available in v3:

  • label - Use Label component instead
  • description - Use Description component instead
  • errorMessage - Use FieldError component instead
  • variant, color, size, radius - Use Tailwind CSS classes
  • classNames - Use className props on individual components
  • startContent, endContent - Customize Select.Trigger directly
  • selectorIcon - Customize Select.Indicator children
  • isClearable - Implement clear button manually
  • renderValue - Use Select.Value render prop
  • labelPlacement - Labels are always outside
  • disableAnimation - Animations handled differently
  • popoverProps, 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 key prop on SelectItem
  • v3: Use id prop on ListBox.Item (required) and textValue prop (required for accessibility)

Selection Value Types

  • v2: selectedKeys was a Set<Key> or array
  • v3: value is Key | null for single selection or Key[] 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

  1. Component Structure: Must use compound components (Select.Trigger, Select.Value, Select.Indicator, Select.Popover)
  2. Item Components: SelectItemListBox.Item, SelectSectionListBox.Section
  3. Label/Description/Error: Use separate components instead of props
  4. Selection Props: selectedKeys/onSelectionChangevalue/onChange
  5. Styling Props Removed: variant, color, size, radius - use Tailwind CSS
  6. ClassNames Removed: Use className props on individual components
  7. Content Props Removed: startContent, endContent - customize trigger directly
  8. Clear Button: isClearable removed - implement manually
  9. Custom Value: Use Select.Value render prop instead of renderValue
  10. Item Props: keyid and textValue (required)

Tips for Migration

  1. Update structure: Wrap Select content in compound components
  2. Add Label: Use Label component for Select label
  3. Add Description: Use Description component
  4. Add Error: Use FieldError component for validation messages
  5. Update items: Replace SelectItem with ListBox.Item (add id and textValue)
  6. Update sections: Replace SelectSection with ListBox.Section and Header
  7. Update selection: Change selectedKeys/onSelectionChange to value/onChange
  8. Add indicators: Include ListBox.ItemIndicator in each item
  9. Update styling: Use Tailwind CSS classes for variants, colors, sizes
  10. Customize trigger: Add custom content directly to Select.Trigger if needed

Need Help?

For v3 Select features and API:

For community support: