Pro--% off in--d : --h : --m : --s
HeroUI

Listbox

Migration guide for Listbox (renamed to ListBox with a capital "B") from HeroUI v2 to v3

Refer to the v3 ListBox documentation for complete API reference, styling guide, and advanced examples. This guide only focuses on migrating from HeroUI v2.

Structure Changes

v2: Separate Components

In v2, Listbox used separate components:

import { Listbox, ListboxItem, ListboxSection } from "@heroui/react";

export default function App() {
  return (
    <Listbox>
      <ListboxItem key="1">Item 1</ListboxItem>
    </Listbox>
  );
}

v3: Compound Components

In v3, ListBox uses compound components:

import { ListBox, Label } from "@heroui/react";

export default function App() {
  return (
    <ListBox>
      <ListBox.Item id="1" textValue="Item 1">
        <Label>Item 1</Label>
      </ListBox.Item>
    </ListBox>
  );
}

Key Changes

1. Component Naming

v2: Listbox, ListboxItem, ListboxSection
v3: ListBox, ListBox.Item, ListBox.Section

2. Item Identification

v2: React's key was used for both list reconciliation and item identity (selection, focus).
v3: Use id for state/focus and textValue for accessibility (when content isn't plain text); keep React's key on items in lists.

3. Prop Changes

v2 Propv3 LocationNotes
key (for state)ListBox.ItemUse id for item identity (state)
-textValue (on ListBox.Item)For accessibility (type-ahead)
variant, colorvariant on ListBox or ListBox.ItemSimplified to "default" | "danger" (no color prop)
onActionListBoxCallback fired when an item is pressed ((key: Key) => void)
disabledKeysListBoxSame — set of keys for items that should be non-interactive
startContent, endContent-Place icons manually in item content
descriptionDescriptionUse Description component
title (on Section)HeaderUse Header component
topContent, bottomContent-Removed (handle separately)
itemClasses, classNames-Use className on parts
hideSelectedIcon-Omit ListBox.ItemIndicator
disableAnimation-Removed
isVirtualized, virtualizationReact Aria VirtualizerUse React Aria's <Virtualizer> wrapper (see example below)
selectedKeysListBoxSame (uses Selection type Set)

Migration Examples

Selection

import { useState } from "react";

{/* Single selection */}
const [singleSelected, setSingleSelected] = useState(new Set(["text"]));

<Listbox
  selectedKeys={singleSelected}
  selectionMode="single"
  onSelectionChange={setSingleSelected}
>
  <ListboxItem key="text">Text</ListboxItem>
  <ListboxItem key="number">Number</ListboxItem>
</Listbox>

{/* Multiple selection */}
const [multiSelected, setMultiSelected] = useState(new Set(["text"]));

<Listbox
  selectedKeys={multiSelected}
  selectionMode="multiple"
  onSelectionChange={setMultiSelected}
>
  <ListboxItem key="text">Text</ListboxItem>
  <ListboxItem key="number">Number</ListboxItem>
</Listbox>
import { useState } from "react";
import type { Selection } from "@heroui/react";

{/* Single selection */}
const [singleSelected, setSingleSelected] = useState<Selection>(new Set(["text"]));

<ListBox
  selectedKeys={singleSelected}
  selectionMode="single"
  onSelectionChange={setSingleSelected}
>
  <ListBox.Item id="text" textValue="Text">
    <Label>Text</Label>
    <ListBox.ItemIndicator />
  </ListBox.Item>
  <ListBox.Item id="number" textValue="Number">
    <Label>Number</Label>
    <ListBox.ItemIndicator />
  </ListBox.Item>
</ListBox>

{/* Multiple selection */}
const [multiSelected, setMultiSelected] = useState<Selection>(new Set(["text"]));

<ListBox
  selectedKeys={multiSelected}
  selectionMode="multiple"
  onSelectionChange={setMultiSelected}
>
  <ListBox.Item id="text" textValue="Text">
    <Label>Text</Label>
    <ListBox.ItemIndicator />
  </ListBox.Item>
  <ListBox.Item id="number" textValue="Number">
    <Label>Number</Label>
    <ListBox.ItemIndicator />
  </ListBox.Item>
</ListBox>

With Description

<ListboxItem
  key="new"
  description="Create a new file"
>
  New file
</ListboxItem>
import { Description, Label } from "@heroui/react";

<ListBox.Item id="new" textValue="New file">
  <Label>New file</Label>
  <Description>Create a new file</Description>
</ListBox.Item>

With Icons

<ListboxItem
  key="new"
  startContent={<AddIcon />}
>
  New file
</ListboxItem>
<ListBox.Item id="new" textValue="New file">
  <AddIcon />
  <Label>New file</Label>
</ListBox.Item>

With Sections

<Listbox>
  <ListboxSection title="Actions">
    <ListboxItem key="new">New file</ListboxItem>
    <ListboxItem key="edit">Edit file</ListboxItem>
  </ListboxSection>
  <ListboxSection title="Danger zone">
    <ListboxItem key="delete">Delete</ListboxItem>
  </ListboxSection>
</Listbox>
import { Header, Label, Separator } from "@heroui/react";

<ListBox>
  <ListBox.Section>
    <Header>Actions</Header>
    <ListBox.Item id="new" textValue="New file">
      <Label>New file</Label>
    </ListBox.Item>
    <ListBox.Item id="edit" textValue="Edit file">
      <Label>Edit file</Label>
    </ListBox.Item>
  </ListBox.Section>
  <Separator />
  <ListBox.Section>
    <Header>Danger zone</Header>
    <ListBox.Item id="delete" textValue="Delete" variant="danger">
      <Label>Delete</Label>
    </ListBox.Item>
  </ListBox.Section>
</ListBox>

With Custom Indicator

<ListboxItem
  key="1"
  selectedIcon={<CustomCheckIcon />}
>
  Item 1
</ListboxItem>
<ListBox.Item id="1" textValue="Item 1">
  <Label>Item 1</Label>
  <ListBox.ItemIndicator>
    {({isSelected}) =>
      isSelected ? <CustomCheckIcon /> : null
    }
  </ListBox.ItemIndicator>
</ListBox.Item>

Variant Prop

In v3, both ListBox and ListBox.Item accept a variant prop with values "default" (default) or "danger". Setting variant on the root ListBox applies to all items; setting it on an individual ListBox.Item overrides the root value for that item.

{/* Root-level variant — all items inherit "danger" styling */}
<ListBox variant="danger">
  <ListBox.Item id="delete" textValue="Delete">
    <Label>Delete</Label>
  </ListBox.Item>
</ListBox>

{/* Per-item variant */}
<ListBox>
  <ListBox.Item id="edit" textValue="Edit">
    <Label>Edit</Label>
  </ListBox.Item>
  <ListBox.Item id="delete" textValue="Delete" variant="danger">
    <Label>Delete</Label>
  </ListBox.Item>
</ListBox>

onAction Handler

The onAction callback is fired when an item is pressed (click or Enter). It receives the item's id as a Key.

<ListBox onAction={(key) => alert(`Action on ${key}`)}>
  <ListBox.Item id="copy" textValue="Copy">
    <Label>Copy</Label>
  </ListBox.Item>
  <ListBox.Item id="paste" textValue="Paste">
    <Label>Paste</Label>
  </ListBox.Item>
</ListBox>

Disabled Keys

Use disabledKeys to make specific items non-interactive:

<ListBox disabledKeys={new Set(["paste"])}>
  <ListBox.Item id="copy" textValue="Copy">
    <Label>Copy</Label>
  </ListBox.Item>
  <ListBox.Item id="paste" textValue="Paste">
    <Label>Paste</Label>
  </ListBox.Item>
</ListBox>

Render Props on ListBox.Item

ListBox.Item supports render props, giving you access to the current interaction state. The available render prop values are isSelected, isFocused, isDisabled, and isPressed:

<ListBox selectionMode="single">
  <ListBox.Item id="item1" textValue="Item 1">
    {({isSelected, isFocused, isDisabled, isPressed}) => (
      <>
        <Label className={isSelected ? "font-bold" : ""}>Item 1</Label>
        {isSelected && <ListBox.ItemIndicator />}
      </>
    )}
  </ListBox.Item>
</ListBox>

Virtualization

Virtualization is still supported in v3 via React Aria's <Virtualizer> component. Wrap your ListBox items with Virtualizer for efficient rendering of large lists:

import {Virtualizer} from "react-aria-components";

<ListBox
  aria-label="Large list"
  items={items}
  selectionMode="multiple"
>
  <Virtualizer>
    {(item) => (
      <ListBox.Item id={item.id} textValue={item.name}>
        <Label>{item.name}</Label>
      </ListBox.Item>
    )}
  </Virtualizer>
</ListBox>

Component Anatomy

The v3 ListBox follows this structure:

ListBox (Root)
  ├── ListBox.Item
  │   ├── Icon (optional, manual placement)
  │   ├── Label (required)
  │   ├── Description (optional)
  │   └── ListBox.ItemIndicator (optional)
  └── ListBox.Section (optional)
      ├── Header (optional)
      └── ListBox.Item

Summary

  1. Component Naming: ListboxListBox, ListboxItemListBox.Item, ListboxSectionListBox.Section
  2. Item Structure: Must use Label, Description, ListBox.ItemIndicator components
  3. Icons: Manual placement instead of startContent/endContent props
  4. Sections: Use Header component instead of title prop
  5. Variant Prop: variant and color replaced by a single variant prop ("default" | "danger") on both ListBox and ListBox.Item
  6. onAction: New onAction callback on ListBox for item press handling
  7. disabledKeys: Supported on ListBox to disable specific items
  8. Render Props: ListBox.Item provides isSelected, isFocused, isDisabled, isPressed via render props
  9. Content Props Removed: topContent, bottomContent - handle separately
  10. Virtualization: Still supported via React Aria's <Virtualizer> component (replaces isVirtualized prop)
  11. Selection Type: Uses Selection type (Set) instead of arrays

On this page