Listbox
A list of selectable options with keyboard navigation and typeahead support.
import { createListCollection } from "@ark-ui/solid";
import { Index } from "solid-js";
import { Listbox, ListboxItem } from "~/components/listbox";
const frameworks = createListCollection({
items: [
{ label: "React", value: "react" },
{ label: "Solid", value: "solid" },
{ label: "Vue", value: "vue" },
],
});
export default function ListboxBasicDemo() {
return (
<Listbox collection={frameworks}>
<Index each={frameworks.items}>
{(item) => <ListboxItem item={item()}>{item().label}</ListboxItem>}
</Index>
</Listbox>
);
}Installation
CLI
Run the following command to add the component to your project:
npx @ark-preset/cli@latest add listboxManual
Create the recipe file at src/components/recipes/listbox.ts:
import { tv, type VariantProps } from "tailwind-variants";
export const listboxVariants = tv({
slots: {
root: "flex flex-col gap-1",
content: "flex flex-col gap-0.5 rounded-md border border-border bg-background p-1 outline-none",
item: [
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none transition-colors",
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"hover:bg-muted",
"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground",
"data-[state=checked]:bg-primary/15 data-[state=checked]:text-primary data-[state=checked]:font-medium",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
],
itemText: "flex-1 text-sm",
itemIndicator: "absolute right-2 flex size-4 items-center justify-center",
},
variants: {
orientation: {
vertical: {
root: "flex-col",
content: "flex-col",
},
horizontal: {
root: "flex-row",
content: "flex-row",
},
},
},
defaultVariants: {
orientation: "vertical",
},
});
export type ListboxVariants = VariantProps<typeof listboxVariants>;Create the component directory and files.
src/components/listbox/listbox.base.tsx:
import { Listbox as ArkListbox, type CollectionItem } from "@ark-ui/solid/listbox";
import { createContext, useContext, splitProps, type Component } from "solid-js";
import { listboxVariants, type ListboxVariants } from "../recipes/listbox";
type ListboxVariantContextValue = Pick<ListboxVariants, "orientation">;
const ListboxVariantContext = createContext<ListboxVariantContextValue>();
const useListboxVariant = () => useContext(ListboxVariantContext);
const styles = listboxVariants();
const Root = <T extends CollectionItem>(props: ArkListbox.RootProps<T> & ListboxVariants) => {
const [local, others] = splitProps(props, ["class", "orientation"] as const);
return (
<ListboxVariantContext.Provider value={{ orientation: local.orientation }}>
<ArkListbox.Root
class={styles.root({
class: local.class,
orientation: local.orientation,
})}
orientation={local.orientation}
{...others}
/>
</ListboxVariantContext.Provider>
);
};
const RootProvider = <T extends CollectionItem>(
props: ArkListbox.RootProviderProps<T> & ListboxVariants,
) => {
const [local, others] = splitProps(props, ["class", "orientation"] as const);
return (
<ListboxVariantContext.Provider value={{ orientation: local.orientation }}>
<ArkListbox.RootProvider
class={styles.root({
class: local.class,
orientation: local.orientation,
})}
{...others}
/>
</ListboxVariantContext.Provider>
);
};
const Content: Component<ArkListbox.ContentProps & ListboxVariants> = (props) => {
const ctx = useListboxVariant();
const [local, others] = splitProps(props, ["class", "orientation"] as const);
return (
<ArkListbox.Content
class={styles.content({
class: local.class,
orientation: local.orientation ?? ctx?.orientation,
})}
{...others}
/>
);
};
const Item: Component<ArkListbox.ItemProps & ListboxVariants> = (props) => {
const ctx = useListboxVariant();
const [local, others] = splitProps(props, ["class", "orientation"] as const);
return (
<ArkListbox.Item
class={styles.item({
class: local.class,
orientation: local.orientation ?? ctx?.orientation,
})}
{...others}
/>
);
};
const ItemText: Component<ArkListbox.ItemTextProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return <ArkListbox.ItemText class={styles.itemText({ class: local.class })} {...others} />;
};
const ItemIndicator: Component<ArkListbox.ItemIndicatorProps> = (props) => {
const [local, others] = splitProps(props, ["class"]);
return (
<ArkListbox.ItemIndicator class={styles.itemIndicator({ class: local.class })} {...others} />
);
};
// Parts without tv() — direct spread, no splitProps
const Empty: Component<ArkListbox.EmptyProps> = (props) => <ArkListbox.Empty {...props} />;
const ItemGroup: Component<ArkListbox.ItemGroupProps> = (props) => (
<ArkListbox.ItemGroup {...props} />
);
const ItemGroupLabel: Component<ArkListbox.ItemGroupLabelProps> = (props) => (
<ArkListbox.ItemGroupLabel {...props} />
);
const ValueText: Component<ArkListbox.ValueTextProps> = (props) => (
<ArkListbox.ValueText {...props} />
);
const Input: Component<ArkListbox.InputProps> = (props) => <ArkListbox.Input {...props} />;
export const Listbox = {
Root,
RootProvider,
Content,
Item,
ItemText,
ItemIndicator,
Empty,
ItemGroup,
ItemGroupLabel,
ValueText,
Input,
};
export { ListboxVariantContext, useListboxVariant };src/components/listbox/index.tsx:
import { splitProps, type Component } from "solid-js";
import { Listbox as ListboxBase } from "./listbox.base";
import { Listbox as ArkListbox, type CollectionItem } from "@ark-ui/solid/listbox";
import type { ListboxVariants } from "../recipes/listbox";
const Listbox: Component<ArkListbox.RootProps<CollectionItem>> = (props) => {
const [local, others] = splitProps(props, ["collection", "children"]);
return (
<ListboxBase.Root collection={local.collection} {...others}>
<ListboxBase.Content>{local.children}</ListboxBase.Content>
</ListboxBase.Root>
);
};
const ListboxItem: Component<ArkListbox.ItemProps & ListboxVariants> = (props) => {
const [local, others] = splitProps(props, ["children"]);
return (
<ListboxBase.Item {...others}>
<ListboxBase.ItemText>{local.children}</ListboxBase.ItemText>
<ListboxBase.ItemIndicator>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-4"
>
<path d="M20 6 9 17l-5-5" />
</svg>
</ListboxBase.ItemIndicator>
</ListboxBase.Item>
);
};
const ListboxContent: Component<ArkListbox.ContentProps> = (props) => {
const [local, others] = splitProps(props, ["children"]);
return <ListboxBase.Content {...others}>{local.children}</ListboxBase.Content>;
};
const ListboxEmpty: Component<ArkListbox.EmptyProps> = (props) => {
const [local, others] = splitProps(props, ["children"]);
return <ListboxBase.Empty {...others}>{local.children}</ListboxBase.Empty>;
};
export { Listbox, ListboxItem, ListboxContent, ListboxEmpty, ListboxBase };
export { listboxVariants, type ListboxVariants } from "../recipes/listbox";Note: Make sure your project has the Tailwind CSS theme variables set up (
--background,--foreground,--ring,--border, etc.) or override the utility classes to match your design system.
Usage
Basic Usage
A basic listbox for single-item selection.
import { createListCollection } from "@ark-ui/solid";
import { Index } from "solid-js";
import { Listbox, ListboxItem } from "~/components/listbox";
const frameworks = createListCollection({
items: [
{ label: "React", value: "react" },
{ label: "Solid", value: "solid" },
{ label: "Vue", value: "vue" },
],
});
export default function ListboxBasicDemo() {
return (
<Listbox collection={frameworks}>
<Index each={frameworks.items}>
{(item) => <ListboxItem item={item()}>{item().label}</ListboxItem>}
</Index>
</Listbox>
);
}Multiple Selection
Use selectionMode="multiple" to allow selecting multiple items:
Multiple selection
import { createListCollection } from "@ark-ui/solid";
import { Index } from "solid-js";
import { Listbox, ListboxItem } from "~/components/listbox";
const frameworks = createListCollection({
items: [
{ label: "React", value: "react" },
{ label: "Solid", value: "solid" },
{ label: "Vue", value: "vue" },
{ label: "Svelte", value: "svelte" },
],
});
export default function ListboxMultipleDemo() {
return (
<div>
<p class="text-sm text-muted-foreground mb-2">Multiple selection</p>
<Listbox collection={frameworks} selectionMode="multiple">
<Index each={frameworks.items}>
{(item) => <ListboxItem item={item()}>{item().label}</ListboxItem>}
</Index>
</Listbox>
</div>
);
}Horizontal Orientation
Use orientation="horizontal" for a horizontal layout:
Horizontal orientation
import { createListCollection } from "@ark-ui/solid";
import { Index } from "solid-js";
import { Listbox, ListboxItem } from "~/components/listbox";
const frameworks = createListCollection({
items: [
{ label: "React", value: "react" },
{ label: "Solid", value: "solid" },
{ label: "Vue", value: "vue" },
],
});
export default function ListboxHorizontalDemo() {
return (
<div>
<p class="text-sm text-muted-foreground mb-2">Horizontal orientation</p>
<Listbox collection={frameworks} orientation="horizontal">
<Index each={frameworks.items}>
{(item) => <ListboxItem item={item()}>{item().label}</ListboxItem>}
</Index>
</Listbox>
</div>
);
}Advanced Usage
When the composite Listbox doesn’t provide enough control, import the raw primitive parts from the base file directly:
import { Listbox } from "~/components/listbox/listbox.base";Or import ListboxBase (the raw parts namespace) from the composite entry point:
import { ListboxBase } from "~/components/listbox";RootProvider Pattern
For full control over the listbox machine, use useListbox with ListboxBase.RootProvider:
import { createListCollection } from "@ark-ui/solid";
import { useListbox } from "@ark-ui/solid/listbox";
import { Index } from "solid-js";
import { ListboxBase } from "~/components/listbox";
const frameworks = createListCollection({
items: [
{ label: "React", value: "react" },
{ label: "Solid", value: "solid" },
{ label: "Vue", value: "vue" },
],
});
export default function ListboxRootProviderDemo() {
const listbox = useListbox({ collection: frameworks });
return (
<ListboxBase.RootProvider value={listbox}>
<ListboxBase.Content>
<Index each={frameworks.items}>
{(item) => (
<ListboxBase.Item item={item()}>
<ListboxBase.ItemText>{item().label}</ListboxBase.ItemText>
<ListboxBase.ItemIndicator>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="size-4"
>
<path d="M20 6 9 17l-5-5" />
</svg>
</ListboxBase.ItemIndicator>
</ListboxBase.Item>
)}
</Index>
</ListboxBase.Content>
</ListboxBase.RootProvider>
);
}API Reference
See the Ark UI Listbox documentation.