Listbox

A list of selectable options with keyboard navigation and typeahead support.

React
Solid
Vue
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 listbox

Manual

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.

React
Solid
Vue
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

React
Solid
Vue
Svelte
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

React
Solid
Vue
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:

React
Solid
Vue
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.