ListBox
A listbox displays a list of options and allows a user to select one or more of them.
ListBox
Preview
Properties
import { ListBox, ListBoxItem } from '@moul-dev/ui';
import { useState } from 'react';
import type { Selection } from 'react-aria-components';
export default function Example() {
const [selected, setSelected] = useState<Selection>(new Set(['read']));
return (
<ListBox
aria-label="Permissions"
selectedKeys={selected}
onSelectionChange={setSelected}
className="w-full max-w-sm"
>
<ListBoxItem id="read" label="Read" description="Read-only access" showCheckmark />
<ListBoxItem id="write" label="Write" description="Read and write access" showCheckmark />
<ListBoxItem id="admin" label="Admin" description="Full administrative access" showCheckmark />
</ListBox>
);
}A listbox displays a list of options and allows a user to select one or more of them. Built on React Aria Components and styled with StyleX.
Import
import {
ListBox,
ListBoxItem,
ListBoxSection,
ListBoxLoadMoreItem,
DropIndicator,
Text,
Header,
} from '@moul-dev/ui';Basic Usage
A simple listbox allowing users to pick an option.
import { ListBox, ListBoxItem } from '@moul-dev/ui';
export default function Example() {
return (
<ListBox aria-label="Favorite animal" selectionMode="single">
<ListBoxItem id="aardvark">Aardvark</ListBoxItem>
<ListBoxItem id="cat">Cat</ListBoxItem>
<ListBoxItem id="dog">Dog</ListBoxItem>
<ListBoxItem id="kangaroo">Kangaroo</ListBoxItem>
<ListBoxItem id="panda">Panda</ListBoxItem>
</ListBox>
);
}Selection
Use selectionMode="single" or selectionMode="multiple" to control item selection. Use showCheckmark on items to display a clean checkmark indicator when selected.
Selected: cat, dog
import { ListBox, ListBoxItem } from '@moul-dev/ui';
import { useState } from 'react';
export default function SelectionExample() {
const [selected, setSelected] = useState(new Set(['cat', 'dog']));
return (
<div className="flex flex-col gap-2">
<ListBox
aria-label="Select pets"
selectionMode="multiple"
selectedKeys={selected}
onSelectionChange={setSelected}
>
<ListBoxItem id="cat" showCheckmark>Cat</ListBoxItem>
<ListBoxItem id="dog" showCheckmark>Dog</ListBoxItem>
<ListBoxItem id="rabbit" showCheckmark>Rabbit</ListBoxItem>
<ListBoxItem id="hamster" showCheckmark isDisabled>Hamster (Unavailable)</ListBoxItem>
</ListBox>
<p className="text-sm text-neutral-500">
Selected: {Array.from(selected).join(', ') || 'None'}
</p>
</div>
);
}Labels and Descriptions
You can author labels and descriptions either via label and description props directly on ListBoxItem, or with <Text slot="label"> and <Text slot="description"> slots.
import { ListBox, ListBoxItem, Text } from '@moul-dev/ui';
export default function TextSlotsExample() {
return (
<ListBox aria-label="Cluster Permissions" selectionMode="single">
{/* Via props */}
<ListBoxItem
id="admin"
label="Administrator"
description="Full access to infrastructure and secret rotation"
showCheckmark
/>
{/* Via slot-based Text */}
<ListBoxItem id="developer" showCheckmark textValue="Developer">
<Text slot="label">Developer</Text>
<Text slot="description">Write access to dev and staging workloads</Text>
</ListBoxItem>
<ListBoxItem
id="viewer"
label="Viewer"
description="Read-only access to monitoring dashboards"
showCheckmark
/>
</ListBox>
);
}Sticky Sections & Headers
Organize items into grouped categories using ListBoxSection. Section headers are sticky by default as the user scrolls.
import { ListBox, ListBoxItem, ListBoxSection } from '@moul-dev/ui';
export default function SectionsExample() {
return (
<ListBox aria-label="Sandwich contents" selectionMode="multiple" className="max-h-60">
<ListBoxSection title="Vegetables">
<ListBoxItem id="lettuce">Lettuce</ListBoxItem>
<ListBoxItem id="tomato">Tomato</ListBoxItem>
<ListBoxItem id="onion">Red Onion</ListBoxItem>
<ListBoxItem id="pickles">Pickles</ListBoxItem>
</ListBoxSection>
<ListBoxSection title="Proteins">
<ListBoxItem id="turkey">Smoked Turkey</ListBoxItem>
<ListBoxItem id="tofu">Crispy Tofu</ListBoxItem>
<ListBoxItem id="ham">Honey Glazed Ham</ListBoxItem>
</ListBoxSection>
<ListBoxSection title="Condiments">
<ListBoxItem id="mustard">Dijon Mustard</ListBoxItem>
<ListBoxItem id="mayo">Japanese Mayo</ListBoxItem>
<ListBoxItem id="aioli">Garlic Aioli</ListBoxItem>
</ListBoxSection>
</ListBox>
);
}Layout Modes
ListBox supports both stack (vertical and horizontal) and multi-column grid layouts via the layout and orientation props:
import { ListBox, ListBoxItem } from '@moul-dev/ui';
export default function LayoutsExample() {
return (
<div className="flex flex-col gap-6">
{/* Horizontal Stack */}
<ListBox
aria-label="Filter chips"
selectionMode="single"
orientation="horizontal"
>
<ListBoxItem id="all">All</ListBoxItem>
<ListBoxItem id="unread">Unread</ListBoxItem>
<ListBoxItem id="starred">Starred</ListBoxItem>
<ListBoxItem id="archived">Archived</ListBoxItem>
</ListBox>
{/* Grid Layout */}
<ListBox
aria-label="Instance sizing"
selectionMode="single"
layout="grid"
>
<ListBoxItem id="nano" label="t4g.nano" description="0.5 GB RAM" />
<ListBoxItem id="micro" label="t4g.micro" description="1 GB RAM" />
<ListBoxItem id="small" label="t4g.small" description="2 GB RAM" />
<ListBoxItem id="medium" label="c6g.med" description="4 GB RAM" />
</ListBox>
</div>
);
}Drag and Drop Reordering
ListBox integrates natively with React Aria's useDragAndDrop hook. Item reordering, dragging opacity, drop target outlines, and insertion line indicators (DropIndicator) are handled automatically.
import { ListBox, ListBoxItem } from '@moul-dev/ui';
import { useDragAndDrop } from 'react-aria-components';
import { useState } from 'react';
export default function ReorderableList() {
const [items, setItems] = useState([
{ id: '1', name: 'Lint and Format code' },
{ id: '2', name: 'Execute unit test suite' },
{ id: '3', name: 'Build production artifacts' },
{ id: '4', name: 'Deploy to edge network' },
]);
const { dragAndDropHooks } = useDragAndDrop({
getItems: (keys) =>
Array.from(keys).map((key) => {
const item = items.find((i) => i.id === key);
return { 'text/plain': item?.name ?? String(key) };
}),
onReorder(e) {
if (e.target.dropPosition === 'before') {
const moving = items.filter((i) => e.keys.has(i.id));
const remaining = items.filter((i) => !e.keys.has(i.id));
const idx = remaining.findIndex((i) => i.id === e.target.key);
const updated = [...remaining];
updated.splice(idx, 0, ...moving);
setItems(updated);
} else if (e.target.dropPosition === 'after') {
const moving = items.filter((i) => e.keys.has(i.id));
const remaining = items.filter((i) => !e.keys.has(i.id));
const idx = remaining.findIndex((i) => i.id === e.target.key);
const updated = [...remaining];
updated.splice(idx + 1, 0, ...moving);
setItems(updated);
}
},
});
return (
<ListBox
aria-label="Reorderable deployment steps"
selectionMode="single"
dragAndDropHooks={dragAndDropHooks}
items={items}
>
{(item) => <ListBoxItem id={item.id}>{item.name}</ListBoxItem>}
</ListBox>
);
}Asynchronous Loading
Use renderEmptyState for initial load states, and ListBoxLoadMoreItem for infinite scrolling lists. ListBoxLoadMoreItem renders @moul-dev/ui's Spinner automatically when loading.
import { ListBox, ListBoxItem, ListBoxLoadMoreItem, Spinner } from '@moul-dev/ui';
export default function AsyncLoadingExample() {
return (
<ListBox
aria-label="Infinite feed"
renderEmptyState={() => (
<div className="flex justify-center p-4">
<Spinner size="md" aria-label="Loading feed..." />
</div>
)}
>
<ListBoxItem id="post-1">First Post</ListBoxItem>
<ListBoxItem id="post-2">Second Post</ListBoxItem>
<ListBoxLoadMoreItem isLoading aria-label="Loading more posts..." />
</ListBox>
);
}Props
ListBox Props
Prop
Type
ListBoxItem Props
Prop
Type
ListBoxSection Props
Prop
Type