Skip to content
Lenso UI

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.

Structure Changes

In v2, Select used a simple structure with props:

import { Select, SelectItem } from "@lenso/ui";
export default function App() {  return (    <Select label="Select animal" placeholder="Choose one">      <SelectItem key="cat">Cat</SelectItem>      <SelectItem key="dog">Dog</SelectItem>    </Select>  );}

In v3, Select requires compound components and uses ListBox for items:

import { Select, Label, ListBox } from "@lenso/ui";
export default function App() {  return (    <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.ClearButton, Select.Indicator, Select.Popover) with ListBox for items

2. Item Components

v2: SelectItem, SelectSection v3: ListBox.Item, ListBox.Section (with Header for section titles and Separator between sections)

3. Prop Changes

v2 Propv3 LocationNotes
selectedKeysvalueChanged from Set/array to single value or array
onSelectionChangeonChangeRenamed event handler
defaultSelectedKeysdefaultValueRenamed prop
label—Use Label component
description—Use Description component
errorMessage—Use FieldError component
variantvariantSimplified to primary | secondary only
color—Removed (use Tailwind CSS)
size—Removed (use Tailwind CSS)
radius—Removed (use Tailwind CSS)
classNames—Use className props on individual components
startContent—Customize Select.Trigger directly
endContent—Customize Select.Trigger directly
selectorIcon—Customize Select.Indicator children
isClearableSelect.ClearButtonBuilt-in subcomponent; do not nest a <button> in Select.Trigger
renderValue—Use Select.Value render prop
labelPlacement—Labels are always outside
isRequiredisRequiredStill exists
disabledKeysdisabledKeysStill exists
isOpenisOpenNew: controls the open state of the popover (controlled)
defaultOpendefaultOpenNew: sets the default open state of the popover (uncontrolled)
onOpenChangeonOpenChangeNew: handler called when the open state changes
selectionModeselectionModeStill exists
disableAnimation—Removed (animations handled differently)
popoverProps, listboxProps, scrollShadowProps—Use props on Select.Popover, ListBox, etc. directly

Migration Examples

Selection

import { useState } from "react";
{/* Single selection */}const [singleValue, setSingleValue] = useState(new Set([]));<Select  selectedKeys={singleValue}  onSelectionChange={setSingleValue}  label="Select animal">  <SelectItem key="cat">Cat</SelectItem>  <SelectItem key="dog">Dog</SelectItem></Select>
{/* Multiple selection */}const [multiValue, setMultiValue] = useState(new Set([]));<Select  selectionMode="multiple"  selectedKeys={multiValue}  onSelectionChange={setMultiValue}  label="Select animals">  <SelectItem key="cat">Cat</SelectItem>  <SelectItem key="dog">Dog</SelectItem></Select>

Form Validation

{/* With description */}<Select  label="Select animal"  description="Choose your favorite"  placeholder="Choose one">  <SelectItem key="cat">Cat</SelectItem></Select>
{/* With validation */}<Select  isInvalid  errorMessage="Please select an option"  label="Select animal">  <SelectItem key="cat">Cat</SelectItem></Select>

With Sections

import { Select, SelectItem, SelectSection } from "@lenso/ui";
<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>

Controlled Open State

{/* v2 did not have controlled open state */}<Select label="Select animal">  <SelectItem key="cat">Cat</SelectItem>  <SelectItem key="dog">Dog</SelectItem></Select>

Disabled Options

import { Select, SelectItem } from "@lenso/ui";
<Select label="Select animal" disabledKeys={["dog"]}>  <SelectItem key="cat">Cat</SelectItem>  <SelectItem key="dog">Dog</SelectItem>  <SelectItem key="parrot">Parrot</SelectItem></Select>

Required

import { Select, SelectItem } from "@lenso/ui";
<Select isRequired label="Select animal">  <SelectItem key="cat">Cat</SelectItem>  <SelectItem key="dog">Dog</SelectItem></Select>

Custom Indicator

<Select selectorIcon={<CustomIcon />} label="Select animal">  <SelectItem key="cat">Cat</SelectItem></Select>

Component Anatomy

The v3 Select follows this structure:

Select (Root)  ├── Label (optional)  ├── Select.Trigger  │   ├── Select.Value  │   ├── Select.ClearButton (optional)  │   └── Select.Indicator  ├── Description (optional)  ├── Select.Popover  │   └── ListBox  │       ├── ListBox.Item  │       │   ├── Label (optional)  │       │   ├── Description (optional)  │       │   └── ListBox.ItemIndicator  │       ├── ListBox.Section (optional)  │       │   ├── Header (section title)  │       │   └── ListBox.Item  │       └── Separator (optional, between sections)  └── FieldError (optional)

Important Notes

Item Identification

  • v2: React's key was used for both list reconciliation and item identity for selection.
  • v3: Use id for state/focus and textValue for accessibility on ListBox.Item; keep React's key for list reconciliation.

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. Use Select.ClearButton inside Select.Trigger. Do not nest a <button> in the trigger — Select.Trigger is already a button.

The clear control is a pointer affordance; the trigger also clears on Backspace or Delete for keyboard and screen reader users.

<Select value={value} onChange={setValue}>  <Select.Trigger>    <Select.Value />    <Select.ClearButton />    <Select.Indicator />  </Select.Trigger>  {/* ... */}</Select>

Summary

  1. Component Structure: Must use compound components (Select.Trigger, Select.Value, Select.ClearButton, Select.Indicator, Select.Popover)
  2. Item Components: SelectItem → ListBox.Item, SelectSection → ListBox.Section (use Header for section titles and Separator between sections)
  3. Label/Description/Error: Use separate components instead of props
  4. Selection Props: selectedKeys/onSelectionChange → value/onChange
  5. Controlled Open State: New isOpen, defaultOpen, and onOpenChange props for controlling the popover open state
  6. Disabled Options: disabledKeys prop still supported for disabling specific items
  7. Required: isRequired prop still supported for marking the field as required
  8. Styling: variant simplified to primary | secondary; color, size, radius removed - use Tailwind for more
  9. ClassNames Removed: Use className props on individual components
  10. Content Props Removed: startContent, endContent - customize trigger directly
  11. Clear Button: isClearable removed - use Select.ClearButton inside Select.Trigger (do not nest a <button>)
  12. Custom Value: Use Select.Value render prop instead of renderValue