Shadcn Listbox for React and Tailwind
Select a single or multiple items from a list.
Installation
bunx --bun shadcn@latest add https://kit.dev/r/listbox.jsonpnpm dlx shadcn@latest add https://kit.dev/r/listbox.jsonnpx shadcn@latest add https://kit.dev/r/listbox.jsonyarn shadcn@latest add https://kit.dev/r/listbox.json<Step>This component depends on Menu. Install it first if you haven't already.</Step>
Install the following dependencies:
bun add @ark-ui/react tailwind-variants lucide-reactpnpm add @ark-ui/react tailwind-variants lucide-reactnpm install @ark-ui/react tailwind-variants lucide-reactyarn add @ark-ui/react tailwind-variants lucide-reactCopy and paste the following code into your project.
"use client";
import {
Listbox as ArkListbox,
useListboxContext,
} from "@ark-ui/react/listbox";
import { CheckIcon } from "lucide-react";
import type React from "react";
import { tv, type VariantProps } from "tailwind-variants";
import { cn } from "@/lib/utils";
import { MenuShortcut } from "@/components/ui/menu";
export const useListbox = useListboxContext;
export const Listbox: ArkListbox.RootComponent = (props) => {
const { className, ...rest } = props;
return (
<ArkListbox.Root
className={cn(
"w-full",
"flex flex-col gap-1.5",
"text-foreground",
className
)}
data-slot="listbox"
{...rest}
/>
);
};
export const ListboxContent = (
props: React.ComponentProps<typeof ArkListbox.Content>
) => {
const { className, ...rest } = props;
return (
<ArkListbox.Content
className={cn(
"w-full",
"flex flex-col gap-1",
"outline-hidden",
"overflow-hidden",
"data-[orientation=horizontal]:max-h-none data-[orientation=horizontal]:flex-row",
className
)}
data-slot="listbox-content"
{...rest}
/>
);
};
const listboxItemVariants = tv({
base: [
"group/listbox-item",
"relative",
"flex items-center gap-2",
"px-2.5 py-2",
"rounded-xl",
"select-none text-sm",
"cursor-pointer",
"outline-hidden",
"data-disabled:pointer-events-none data-disabled:opacity-64",
"[&_svg:not([class*='size-'])]:size-3.5 [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0",
],
variants: {
variant: {
default: [
"text-popover-foreground",
"data-[state=checked]:bg-accent data-[state=checked]:text-accent-foreground",
"hover:bg-accent hover:text-accent-foreground",
"data-highlighted:bg-accent data-highlighted:text-accent-foreground",
],
destructive: [
"text-destructive dark:text-destructive-foreground",
"hover:bg-destructive/10 dark:hover:bg-destructive-foreground/10",
"data-highlighted:bg-destructive/10 dark:data-highlighted:bg-destructive-foreground/10",
"**:[svg]:text-destructive! dark:**:[svg]:text-destructive-foreground!",
],
},
},
defaultVariants: {
variant: "default",
},
});
interface ListboxItemProps
extends React.ComponentProps<typeof ArkListbox.Item>,
VariantProps<typeof listboxItemVariants> {}
export const ListboxItem = (props: ListboxItemProps) => {
const { variant = "default", className, ...rest } = props;
return (
<ArkListbox.Item
className={cn(listboxItemVariants({ variant }), className)}
data-slot="listbox-item"
data-variant={variant}
{...rest}
/>
);
};
export const ListboxItemText = (
props: React.ComponentProps<typeof ArkListbox.ItemText>
) => {
const { className, ...rest } = props;
return (
<ArkListbox.ItemText
className={cn(
"min-w-0",
"flex-1",
"text-ellipsis whitespace-nowrap",
"overflow-hidden",
className
)}
data-slot="listbox-item-text"
{...rest}
/>
);
};
interface ListboxItemGroupProps
extends React.ComponentProps<typeof ArkListbox.ItemGroup> {
/**
* The heading of the listbox item group.
*/
heading?: string;
}
export const ListboxItemGroup = (props: ListboxItemGroupProps) => {
const { heading, className, children, ...rest } = props;
return (
<ArkListbox.ItemGroup
className={cn("flex flex-col gap-1", className)}
data-slot="listbox-item-group"
{...rest}
>
{!!heading && <ListboxItemGroupLabel>{heading}</ListboxItemGroupLabel>}
{children}
</ArkListbox.ItemGroup>
);
};
export const ListboxItemGroupLabel = (
props: React.ComponentProps<typeof ArkListbox.ItemGroupLabel>
) => {
const { className, ...rest } = props;
return (
<ArkListbox.ItemGroupLabel
className={cn(
"px-2.5 py-2",
"font-medium text-muted-foreground",
"pointer-events-none",
className
)}
data-slot="listbox-item-group-label"
{...rest}
/>
);
};
export const ListboxValueText = (
props: React.ComponentProps<typeof ArkListbox.ValueText>
) => {
const { className, ...rest } = props;
return (
<ArkListbox.ValueText
className={cn("font-normal", className)}
data-slot="listbox-value-text"
{...rest}
/>
);
};
export const ListboxItemIndicator = (
props: React.ComponentProps<typeof ArkListbox.ItemIndicator>
) => {
const { className, children, ...rest } = props;
return (
<ArkListbox.ItemIndicator
className={cn(
"flex shrink-0 items-center justify-center",
"[&_svg]:text-primary!",
"zoom-in-95 fade-in-0 animate-in",
"motion-reduce:animate-none!",
className
)}
data-slot="listbox-item-indicator"
{...rest}
>
{children ?? <CheckIcon />}
</ArkListbox.ItemIndicator>
);
};
export const ListboxEmpty = (
props: React.ComponentProps<typeof ArkListbox.Empty>
) => {
const { className, ...rest } = props;
return (
<ArkListbox.Empty
className={cn(
"px-2 py-1.5",
"text-center text-muted-foreground text-sm",
className
)}
data-slot="listbox-empty"
{...rest}
/>
);
};
export const ListboxShortcut = (
props: React.ComponentProps<typeof MenuShortcut>
) => <MenuShortcut data-slot="listbox-shortcut" {...props} />;Update the import paths to match your project setup.
Anatomy
Listbox
├── ListboxValueText
└── ListboxContent
├── ListboxEmpty
└── ListboxItemGroup
├── ListboxItemGroupLabel
└── ListboxItem
├── ListboxItemText
├── ListboxItemIndicator
└── ListboxShortcutUsage
import { createListCollection } from "@ark-ui/react/collection";
import {
Listbox,
ListboxContent,
ListboxItem,
ListboxLabel,
} from "@/components/ui/listbox";const collection = createListCollection({
items: [
{ label: "Brazil", value: "br" },
{ label: "Mexico", value: "mx" },
{ label: "Ireland", value: "ie" },
],
});
<Listbox collection={collection}>
<ListboxContent>
{collection.items.map((item) => (
<ListboxItem item={item} key={item.value}>
{item.label}
</ListboxItem>
))}
</ListboxContent>
</Listbox>Controlled
Use value and onValueChange to control the listbox externally.
States
Disabled
Disable the entire listbox with the disabled prop.
Orientation
Use orientation prop to change the orientation of the listbox.
Horizontal
Selection Mode
Multiple
Set selectionMode="multiple" to allow selecting multiple items.
Extended
Set selectionMode="extended" to select multiple items with Cmd/Ctrl modifier.
None
Set selectionMode="none" to disable selection.
Examples
With Icon
With Description
Image Explorer
Transfer List
Grouping
Use groupBy in createListCollection and <ListboxItemGroup heading={}> with <ListboxItemGroupLabel> to group items.
With Filter
Filter items using useListCollection with useFilter from @ark-ui/react. Call filter when the search input changes to filter items by label.
With Popover
Use the listbox within a popover to create dropdown-like selection menus that overlay other content without taking up permanent screen space.
Grid Layout
Use createGridCollection with columnCount and grid CSS for a grid layout.
API Reference
Listbox
Root component. Manages selection state and keyboard navigation.
| Prop | Type | Default |
|---|---|---|
collection | ListCollection<T> | required |
value | string[] | - |
defaultValue | string[] | [] |
onValueChange | (details: ValueChangeDetails<T>) => void | - |
selectionMode | "single" | "multiple" | "extended" | "single" |
orientation | "vertical" | "horizontal" | "vertical" |
disabled | boolean | - |
deselectable | boolean | - |
loopFocus | boolean | false |
ListboxContent
Holds the list of options.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | - |
className | string | - |
ListboxItem
A single selectable list option.
| Prop | Type | Default |
|---|---|---|
item | T | required |
variant | "default" | "destructive" | "default" |
asChild | boolean | - |
highlightOnHover | boolean | - |
ListboxItemText
Text content for a list item.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | - |
className | string | - |
ListboxItemIndicator
Checkmark or icon shown when the item is selected.
| Prop | Type | Default |
|---|---|---|
children | ReactNode | CheckIcon |
asChild | boolean | - |
className | string | - |
ListboxItemGroup
Groups related options. Use heading or ListboxItemGroupLabel for a label.
| Prop | Type | Default |
|---|---|---|
heading | string | - |
asChild | boolean | - |
className | string | - |
ListboxItemGroupLabel
Label for a group. Must be used inside ListboxItemGroup.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | - |
className | string | - |
ListboxValueText
Shows the selected value(s) as text. Use for custom trigger or display.
| Prop | Type | Default |
|---|---|---|
placeholder | string | - |
asChild | boolean | - |
className | string | - |
ListboxEmpty
Shown when the list has no items. Use for empty states.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | - |
className | string | - |
ListboxShortcut
Optional keyboard shortcut hint shown next to an item.
For a complete list of props, see the Ark UI documentation.