Forms
List Box
A selectable list with single or multiple selection, sections, descriptions, and drag handles. Built on react-aria-components, reuses the shared dropdown item styling, and is styled with the quebi dark surface and brand-teal selection.
listselectoptionsectionselectioninteractive
Single selection
Pick one row; the selected item fills with brand teal and a check.
Multiple selection
Allow selecting several rows at once.
Icons & descriptions
Each row can carry a leading icon and a secondary description line.
Sections
Group related rows under titled sections.
Disabled items
Individual rows can be disabled.
Source
Copy this into your project. Resolve its dependencies from the registryDependencies in the component's API entry.
"use client"
import { Check } from "lucide-react"
import type { ListBoxItemProps, ListBoxProps, ListBoxSectionProps } from "react-aria-components"
import {
composeRenderProps,
ListBoxItem as ListBoxItemPrimitive,
ListBox as ListBoxPrimitive,
} from "react-aria-components"
import {
DropdownDescription,
DropdownLabel,
DropdownSection,
type DropdownSectionProps,
dropdownItemStyles,
} from "@/components/dropdown"
import { cn } from "@/lib/utils"
/**
* List Box — quebi design system
*
* A selectable list rendered on the quebi dark surface with cyan hairlines and
* brand-teal selection. Foundational: Multiple Select builds on top of it.
* Reuses the shared dropdown item styling so items match menus and selects.
*/
const ListBox = <T extends object>({ className, ...props }: ListBoxProps<T>) => (
<ListBoxPrimitive
{...props}
data-slot="list-box"
className={cn(
"grid max-h-96 w-full min-w-56 scroll-py-1 grid-cols-[auto_1fr] flex-col gap-y-1 overflow-y-auto overscroll-contain rounded-quebi-md border border-cyan-500/10 bg-quebi-bg p-1 shadow-quebi-glow outline-hidden [scrollbar-width:thin] has-data-[slot=drag-icon]:grid-cols-[auto_auto_1fr] [&::-webkit-scrollbar]:size-0.5 *:[[role='group']+[role=group]]:mt-4 *:[[role='group']+[role=separator]]:mt-1",
className,
)}
/>
)
const ListBoxItem = <T extends object>({ children, className, ...props }: ListBoxItemProps<T>) => {
const textValue = typeof children === "string" ? children : undefined
return (
<ListBoxItemPrimitive
textValue={textValue}
className={composeRenderProps(className, (className, renderProps) =>
dropdownItemStyles({
...renderProps,
className: cn(
"group not-has-[[slot=description]]:items-start",
"has-data-[slot=drag-icon]:*:[[slot=label]]:col-start-3",
"has-data-[slot=drag-icon]:*:data-[slot=icon]:col-start-2",
"href" in props ? "cursor-pointer" : "cursor-default",
className,
),
}),
)}
data-slot="list-box-item"
{...props}
>
{(renderProps) => {
const { allowsDragging, isSelected } = renderProps
return (
<>
{allowsDragging && (
<svg
aria-hidden="true"
data-slot="drag-icon"
className="me-2 size-5 h-lh text-quebi-fg-muted sm:w-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
>
<path
d="M11 5.5C11 6.32843 10.3284 7 9.5 7C8.67157 7 8 6.32843 8 5.5C8 4.67157 8.67157 4 9.5 4C10.3284 4 11 4.67157 11 5.5Z"
fill="currentColor"
/>
<path
d="M16 5.5C16 6.32843 15.3284 7 14.5 7C13.6716 7 13 6.32843 13 5.5C13 4.67157 13.6716 4 14.5 4C15.3284 4 16 4.67157 16 5.5Z"
fill="currentColor"
/>
<path
d="M11 18.5C11 19.3284 10.3284 20 9.5 20C8.67157 20 8 19.3284 8 18.5C8 17.6716 8.67157 17 9.5 17C10.3284 17 11 17.6716 11 18.5Z"
fill="currentColor"
/>
<path
d="M16 18.5C16 19.3284 15.3284 20 14.5 20C13.6716 20 13 19.3284 13 18.5C13 17.6716 13.6716 17 14.5 17C15.3284 17 16 17.6716 16 18.5Z"
fill="currentColor"
/>
<path
d="M11 12C11 12.8284 10.3284 13.5 9.5 13.5C8.67157 13.5 8 12.8284 8 12C8 11.1716 8.67157 10.5 9.5 10.5C10.3284 10.5 11 11.1716 11 12Z"
fill="currentColor"
/>
<path
d="M16 12C16 12.8284 15.3284 13.5 14.5 13.5C13.6716 13.5 13 12.8284 13 12C13 11.1716 13.6716 10.5 14.5 10.5C15.3284 10.5 16 11.1716 16 12Z"
fill="currentColor"
/>
</svg>
)}
{isSelected && (
<Check
className="-mx-0.5 me-2 h-lh w-5 shrink-0 group-allows-dragging:col-start-2 sm:w-4"
data-slot="check-icon"
/>
)}
{typeof children === "function" ? (
children(renderProps)
) : typeof children === "string" ? (
<DropdownLabel>{children}</DropdownLabel>
) : (
children
)}
</>
)
}}
</ListBoxItemPrimitive>
)
}
const ListBoxSection = <T extends object>({ className, ...props }: DropdownSectionProps<T>) => {
return (
<DropdownSection
className={cn("gap-y-1 *:data-[slot=list-box-item]:last:-mb-1.5", className)}
{...props}
/>
)
}
const ListBoxLabel = DropdownLabel
const ListBoxDescription = DropdownDescription
export type { ListBoxItemProps, ListBoxSectionProps }
export { ListBox, ListBoxDescription, ListBoxItem, ListBoxLabel, ListBoxSection }