Skip to content
Lenso UI

ListBox

A listbox displays a list of options and allows a user to select one or more of them

Usage

import { ListBox } from '@lenso/ui';
"use client";
// Adapted from HeroUI v3.2.6 list-box-default (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { descriptionStyles } from "@lenso/tokens/description";import { labelStyles } from "@lenso/tokens/label";import { Avatar, ListBox, ListBoxItem } from "@lenso/ui";
const users = [  { key: "1", textValue: "Bob", color: "blue" },  { key: "2", textValue: "Fred", color: "green" },  { key: "3", textValue: "Martha", color: "purple" },] as const;const styles = stylex.create({  root: { width: 220 },  details: { display: "flex", flexDirection: "column" },});
export function Default() {  return (    <ListBox aria-label="Users" xstyle={styles.root} selectionMode="single">      {users.map((user) => (        <ListBoxItem key={user.key} itemKey={user.key} textValue={user.textValue}>          <Avatar size="sm">            <Avatar.Image              alt={user.textValue}              src={`https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/avatars/${user.color}.jpg`}            />            <Avatar.Fallback>{user.textValue[0]}</Avatar.Fallback>          </Avatar>          <div {...stylex.props(styles.details)}>            <span {...stylex.props(labelStyles.label)}>{user.textValue}</span>            <span {...stylex.props(descriptionStyles.description)}>              {user.textValue.toLowerCase()}@heroui.com            </span>          </div>          <ListBoxItem.Indicator />        </ListBoxItem>      ))}    </ListBox>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Anatomy

import { ListBox, Label, Description, Header } from '@lenso/ui';
export default () => (  <ListBox>    <ListBox.Item>      <Label />      <Description />      <ListBox.ItemIndicator />    </ListBox.Item>    <ListBox.Section>      <Header />      <ListBox.Item>        <Label />      </ListBox.Item>    </ListBox.Section>  </ListBox>)

Examples

With Disabled Items

"use client";// HeroUI v3.2.6 with-disabled-items adaptation (Apache-2.0).import { Actions } from "./actions";export function WithDisabledItems() {  return <Actions disabled />;}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

With Sections

"use client";// HeroUI v3.2.6 with-sections adaptation (Apache-2.0).import { Actions } from "./actions";export function WithSections() {  return <Actions />;}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Multi Select

"use client";// HeroUI v3.2.6 multi-select adaptation (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { UsersList, styles } from "./users";export function MultiSelect() {  return (    <div {...stylex.props(styles.surface)}>      <UsersList selectionMode="multiple" />    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Controlled

"use client";// HeroUI v3.2.6 controlled adaptation (Apache-2.0).import { useState } from "react";import * as stylex from "@stylexjs/stylex";import { UsersList, styles } from "./users";export function Controlled() {  const [selected, setSelected] = useState<Set<React.Key>>(new Set(["1"]));  return (    <div {...stylex.props(styles.stack)}>      <div {...stylex.props(styles.surface)}>        <UsersList          selectionMode="multiple"          selectedKeys={selected}          onSelectionChange={setSelected}          customCheck        />      </div>      <p {...stylex.props(styles.muted)}>        Selected: {selected.size ? [...selected].join(", ") : "None"}      </p>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Virtualization

ListBox supports virtualization through Virtualizer, enabling efficient rendering of large datasets by displaying only the rows visible within the viewport.

"use client";// HeroUI v3.2.6 virtualization adaptation (Apache-2.0); native fixed-row window.import * as stylex from "@stylexjs/stylex";import { ListBox, ListBoxItem } from "@lenso/ui";import { Description, Label } from "./text";const first = [  "Emma",  "Liam",  "Olivia",  "Noah",  "Ava",  "James",  "Sophia",  "Oliver",  "Isabella",  "Lucas",  "Mia",  "Ethan",  "Charlotte",  "Mason",  "Amelia",  "Logan",  "Harper",  "Alexander",  "Ella",  "Benjamin",];const last = [  "Smith",  "Johnson",  "Williams",  "Brown",  "Jones",  "Garcia",  "Miller",  "Davis",  "Rodriguez",  "Martinez",  "Anderson",  "Taylor",  "Thomas",  "Jackson",  "White",  "Harris",  "Clark",  "Lewis",  "Robinson",  "Walker",];const users = Array.from({ length: 1000 }, (_, index) => ({  key: index + 1,  textValue: `${first[index % 20]} ${last[Math.floor(index / 20) % 20]}`,  email: `${first[index % 20]!.toLowerCase()}.${last[Math.floor(index / 20) % 20]!.toLowerCase()}@acme.com`,}));const styles = stylex.create({  list: { width: 300 },  detail: { display: "flex", flexDirection: "column" },});export function Virtualization() {  return (    <ListBox      aria-label="Virtualized list with 1000 items"      xstyle={styles.list}      items={users}      virtualized={{ rowHeight: 50, height: 400 }}    >      {(item) => (        <ListBoxItem itemKey={item.key} textValue={item.textValue}>          <div {...stylex.props(styles.detail)}>            <Label>{item.textValue}</Label>            <Description>{users[Number(item.key) - 1]!.email}</Description>          </div>          <ListBoxItem.Indicator />        </ListBoxItem>      )}    </ListBox>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Custom Check Icon

"use client";// HeroUI v3.2.6 custom-check-icon adaptation (Apache-2.0).import * as stylex from "@stylexjs/stylex";import { UsersList, styles } from "./users";export function CustomCheckIcon() {  return (    <div {...stylex.props(styles.surface)}>      <UsersList selectionMode="multiple" customCheck />    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Render Function

"use client";// HeroUI v3.2.6 render-function adaptation (Apache-2.0).import { UsersList, styles } from "./users";export function RenderFunction() {  return (    <UsersList      xstyle={styles.list}      selectionMode="single"      render={(props) => <div {...props} data-custom="true" />}      renderItems    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Customization

Tailwind CSS

"use client";// HeroUI v3.2.6 custom-styles adaptation (Apache-2.0).import { UsersList, styles } from "./users";export function CustomStyles() {  return (    <UsersList      aria-label="Assignee"      xstyle={styles.custom}      selectionMode="single"      customStyles      count={2}    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Global CSS

To customize the ListBox component classes, you can use the @layer components directive. Learn more.

@layer components {  .list-box {    @apply rounded-lg border border-border bg-surface p-2;  }
  .list-box-item {    @apply rounded px-2 py-1 cursor-pointer;  }
  .list-box-item--danger {    @apply text-danger;  }
  .list-box-item__indicator {    @apply text-accent;  }}

Styling Reference

HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.

CSS Classes

The ListBox component uses these CSS classes (View source styles):

Base Classes [!toc]

  • .list-box - Base listbox container
  • .list-box-item - Individual listbox item
  • .list-box-item__indicator - Selection indicator icon
  • .list-box-section - Section container for grouping items

Variant Classes [!toc]

  • .list-box--default - Default variant styling
  • .list-box--danger - Danger variant styling
  • .list-box-item--default - Default item variant
  • .list-box-item--danger - Danger item variant

State Classes [!toc]

  • .list-box-item[data-selected="true"] - Selected item state
  • .list-box-item[data-focus-visible="true"] - Focused item state
  • .list-box-item[data-disabled="true"] - Disabled item state
  • .list-box-item__indicator[data-visible="true"] - Visible indicator state

Interactive States

The component supports both CSS pseudo-classes and data attributes for flexibility:

  • Hover: :hover or [data-hovered="true"] on item
  • Focus: :focus-visible or [data-focus-visible="true"] on item
  • Selected: [data-selected="true"] on item
  • Disabled: :disabled or [data-disabled="true"] on item

API Reference

ListBox

PropTypeDefaultDescription
aria-labelstring-Accessibility label for the listbox
aria-labelledbystring-ID of element that labels the listbox
selectionMode"none" | "single" | "multiple""single"Selection behavior
selectedKeysSelection-Controlled selected keys
defaultSelectedKeysSelection-Initial selected keys
onSelectionChange(keys: Selection) => void-Handler called when selection changes
disabledKeysIterable<Key>-Keys of disabled items
onAction(key: Key) => void-Handler called when an item is activated
variant"default" | "danger""default"Visual variant
classNamestring-Additional CSS classes
childrenReactNode-ListBox items and sections
renderDOMRenderFunction<keyof React.JSX.IntrinsicElements, ListBoxRenderProps>-Overrides the default DOM element with a custom render function.

ListBox.Item

PropTypeDefaultDescription
idKey-Unique identifier for the item
textValuestring-Text value for accessibility and typeahead
isDisabledbooleanfalseWhether this item is disabled
variant"default" | "danger""default"Visual variant
classNamestring-Additional CSS classes
childrenReactNode | RenderFunction-Item content or render function
render(props: DetailedHTMLProps<LinkWithRequiredHref, HTMLAnchorElement> | React.JSX.IntrinsicElements[keyof React.JSX.IntrinsicElements], renderProps: ListBoxItemRenderProps) => ReactElement-Overrides the default DOM element with a custom render function.

ListBox.ItemIndicator

PropTypeDefaultDescription
classNamestring-Additional CSS classes
childrenReactNode | RenderFunction-Custom indicator content or render function

ListBox.Section

PropTypeDefaultDescription
classNamestring-Additional CSS classes
childrenReactNode-Section content including Header and Items

Render Props

When using render functions with ListBox.Item or ListBox.ItemIndicator, these values are provided:

PropTypeDescription
isSelectedbooleanWhether the item is selected
isFocusedbooleanWhether the item is focused
isDisabledbooleanWhether the item is disabled
isPressedbooleanWhether the item is being pressed

ListLayout

NameTypeDefaultDescription
rowHeightnumber | undefined48The fixed height of a row in px.
estimatedRowHeightnumber | undefined—The estimated height of a row, when row heights are variable.
headingHeightnumber | undefined48The fixed height of a section header in px.
estimatedHeadingHeightnumber | undefined—The estimated height of a section header, when the height is variable.
loaderHeightnumber | undefined48The fixed height of a loader element in px. This loader is specifically for "load more" elements rendered when loading more rows at the root level or inside nested row/sections.
dropIndicatorThicknessnumber | undefined2The thickness of the drop indicator.
gapnumber | undefined0The gap between items.
paddingnumber | undefined0The padding around the list.

Examples

Basic Usage

import { ListBox, Label, Description } from '@lenso/ui';
<ListBox aria-label="Users" selectionMode="single">  <ListBox.Item id="1" textValue="Bob">    <Label>Bob</Label>    <Description>[email protected]</Description>  </ListBox.Item>  <ListBox.Item id="2" textValue="Alice">    <Label>Alice</Label>    <Description>[email protected]</Description>  </ListBox.Item></ListBox>

With Sections

import { ListBox, Header, Separator } from '@lenso/ui';
<ListBox aria-label="Actions" selectionMode="none" onAction={(key) => console.log(key)}>  <ListBox.Section>    <Header>Actions</Header>    <ListBox.Item id="new" textValue="New file">New file</ListBox.Item>    <ListBox.Item id="edit" textValue="Edit file">Edit file</ListBox.Item>  </ListBox.Section>  <Separator />  <ListBox.Section>    <Header>Danger zone</Header>    <ListBox.Item id="delete" textValue="Delete" variant="danger">Delete</ListBox.Item>  </ListBox.Section></ListBox>

Controlled Selection

import { ListBox, Selection } from '@lenso/ui';import { useState } from 'react';
function ControlledListBox() {  const [selected, setSelected] = useState<Selection>(new Set(["1"]));
  return (    <ListBox      aria-label="Options"      selectedKeys={selected}      selectionMode="multiple"      onSelectionChange={setSelected}    >      <ListBox.Item id="1" textValue="Option 1">Option 1</ListBox.Item>      <ListBox.Item id="2" textValue="Option 2">Option 2</ListBox.Item>      <ListBox.Item id="3" textValue="Option 3">Option 3</ListBox.Item>    </ListBox>  );}

Custom Indicator

import { ListBox, ListBoxItemIndicator } from '@lenso/ui';import { Icon } from '@iconify/react';
<ListBox aria-label="Options" selectionMode="multiple">  <ListBox.Item id="1" textValue="Option 1">    Option 1    <ListBox.ItemIndicator>      {({isSelected}) =>        isSelected ? <Icon icon="gravity-ui:check" /> : null      }    </ListBox.ItemIndicator>  </ListBox.Item></ListBox>

Accessibility

The ListBox component implements the ARIA listbox pattern and provides:

  • Full keyboard navigation support
  • Screen reader announcements for selection changes
  • Proper focus management
  • Support for disabled states
  • Typeahead search functionality

For more information, see the React Aria ListBox documentation.