All components
Listbox
An always-visible option list with selection indicators in subtle and solid panels.
Notifications
All activity
Mentions only
Weekly digest
Muted
123456789101112131415161718192021222324import { Listbox, createListCollection } from "#components/ui/ark-ui/listbox.tsx";
const options = createListCollection({ items: ["All activity", "Mentions only", "Weekly digest", "Muted"],});
export function Example() { return ( // the variant resolves once on Root; every part inherits it via // context — ItemIndicator ships a default check and only shows on // selected items <Listbox.Root collection={options} defaultValue={["Weekly digest"]} variant="subtle"> <Listbox.Label>Notifications</Listbox.Label> <Listbox.Content> {options.items.map((option) => ( <Listbox.Item key={option} item={option}> <Listbox.ItemText>{option}</Listbox.ItemText> <Listbox.ItemIndicator /> </Listbox.Item> ))} </Listbox.Content> </Listbox.Root> );}Variants
Notifications
subtleAll activity
Mentions only
Weekly digest
Muted
Notifications
solidAll activity
Mentions only
Weekly digest
Muted
Install
1npx stylexblocks add listboxSource
components/ui/ark-ui/listbox.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248import * as stylex from "@stylexjs/stylex";import { Listbox as ArkListbox, type CollectionItem } from "@ark-ui/react/listbox";import { createSlotRecipeContext } from "#lib/ark-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe, type SlotRecipeVariants, type StyleOverrideProps, type WithStyleOverrides,} from "#lib/recipe-props.ts";import { listboxItemIndicatorIconVars } from '@stylexblocks/ui/slot-recipes/listbox.stylex.ts'
import { cursor } from '@stylexblocks/ui/tokens/cursor.stylex.ts'import { fontSizes } from '@stylexblocks/ui/tokens/font-sizes.stylex.ts'import { fontWeights } from '@stylexblocks/ui/tokens/font-weights.stylex.ts'import { lineHeights } from '@stylexblocks/ui/tokens/line-heights.stylex.ts'import { semanticColors } from '@stylexblocks/ui/semantic-tokens/colors.stylex.ts'import { semanticRadii } from '@stylexblocks/ui/semantic-tokens/radii.stylex.ts'import { sizes } from '@stylexblocks/ui/tokens/sizes.stylex.ts'import { spacing } from '@stylexblocks/ui/tokens/spacing.stylex.ts'
const listboxRoot = stylex.create({ base: { display: 'flex', flexDirection: 'column', gap: spacing._1_5, width: sizes.full, },})
const listboxContent = stylex.create({ base: { display: 'flex', flexDirection: 'column', overflowY: 'auto', maxHeight: sizes._96, padding: spacing._1, gap: spacing._1, fontSize: fontSizes.sm, lineHeight: lineHeights.sm, outline: 'none', scrollPadding: 1, '--listbox-item-padding-x': spacing._2, '--listbox-item-padding-y': spacing._1_5, },})
const listboxContentVariants = stylex.create({ subtle: { backgroundColor: semanticColors.bgPanel, borderWidth: '1px', borderRadius: semanticRadii.panelMd, }, solid: { backgroundColor: semanticColors.bgPanel, borderWidth: '1px', borderRadius: semanticRadii.panelMd, },})
const listboxItem = stylex.create({ base: { position: 'relative', userSelect: 'none', display: 'flex', alignItems: 'center', gap: spacing._2, cursor: cursor.option, justifyContent: 'space-between', flex: 1, textAlign: 'start', borderRadius: semanticRadii.controlMd, paddingBlock: 'var(--listbox-item-padding-y)', paddingInline: 'var(--listbox-item-padding-x)', pointerEvents: { default: null, ':is(:disabled, [disabled], [data-disabled])': 'none', }, opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, },})
const listboxItemVariants = stylex.create({ subtle: { backgroundColor: { default: null, ':hover': `color-mix(in oklch, ${semanticColors.bgEmphasized} 60%, transparent)`, }, }, solid: {},})
const listboxEmpty = stylex.create({ base: { paddingBlock: 'var(--listbox-item-padding-y)', paddingInline: 'var(--listbox-item-padding-x)', },})
const listboxItemText = stylex.create({ base: { flex: 1, },})
const listboxItemGroup = stylex.create({ base: { marginTop: { default: spacing._1_5, ':first-child': 0, }, },})
const listboxItemGroupLabel = stylex.create({ base: { paddingBlock: spacing._1_5, paddingInline: spacing._2, fontWeight: fontWeights.medium, },})
const listboxLabel = stylex.create({ base: { fontWeight: fontWeights.medium, userSelect: 'none', fontSize: fontSizes.sm, lineHeight: lineHeights.sm, opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, cursor: { default: null, ':is(:disabled, [disabled], [data-disabled])': 'not-allowed', }, },})
const listboxValueText = stylex.create({ base: { lineClamp: 1, maxWidth: '80%', },})
const listboxItemIndicator = stylex.create({ base: { display: 'flex', alignItems: 'center', justifyContent: 'center', [listboxItemIndicatorIconVars.width]: sizes._4, [listboxItemIndicatorIconVars.height]: sizes._4, },})
const listboxItemIndicatorIcon = stylex.create({ base: { width: listboxItemIndicatorIconVars.width, height: listboxItemIndicatorIconVars.height, },})
export type ListboxVariant = keyof typeof listboxContentVariants
const listboxRecipe = defineSlotRecipe({ slots: { root: { styles: listboxRoot }, content: { styles: listboxContent, variants: listboxContentVariants }, item: { styles: listboxItem, variants: listboxItemVariants }, empty: { styles: listboxEmpty }, itemText: { styles: listboxItemText }, itemGroup: { styles: listboxItemGroup }, itemGroupLabel: { styles: listboxItemGroupLabel }, label: { styles: listboxLabel }, valueText: { styles: listboxValueText }, itemIndicator: { styles: listboxItemIndicator }, }, defaultVariants: { variant: "subtle" },});
function CheckIcon(props: React.ComponentProps<"svg">) { return ( <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" {...props}> <path d="m3.5 8.5 3 3 6-7" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> </svg> );}
const { withProvider, withContext } = createSlotRecipeContext(listboxRecipe);
type ListboxVariants = SlotRecipeVariants<typeof listboxRecipe>;
const StyledRoot = withProvider(ArkListbox.Root, "root", "ListboxRoot");
function Root<T extends CollectionItem>( props: WithStyleOverrides<ArkListbox.RootProps<T>> & Partial<ListboxVariants>,) { return <StyledRoot {...(props as React.ComponentProps<typeof StyledRoot>)} />;}Root.displayName = "ListboxRoot";
const StyledItemIndicator = withContext( ArkListbox.ItemIndicator, "itemIndicator", "ListboxItemIndicator",);
function ItemIndicator( props: React.ComponentProps<typeof ArkListbox.ItemIndicator> & StyleOverrideProps,) { const { children, ...rest } = props; return ( <StyledItemIndicator {...rest}> {children ?? <CheckIcon {...stylex.props(listboxItemIndicatorIcon.base)} />} </StyledItemIndicator> );}ItemIndicator.displayName = "ListboxItemIndicator";
/** * Listbox — Ark UI's listbox machine parts styled with the StyleX slot * recipe. `<Listbox.Root variant>` resolves the recipe once; every part * inherits it through context. ItemIndicator renders a check icon by default * and only appears on selected items. */export const Listbox = { Root, Label: withContext(ArkListbox.Label, "label", "ListboxLabel"), Content: withContext(ArkListbox.Content, "content", "ListboxContent"), Item: withContext(ArkListbox.Item, "item", "ListboxItem"), ItemText: withContext(ArkListbox.ItemText, "itemText", "ListboxItemText"), ItemIndicator, ItemGroup: withContext(ArkListbox.ItemGroup, "itemGroup", "ListboxItemGroup"), ItemGroupLabel: withContext( ArkListbox.ItemGroupLabel, "itemGroupLabel", "ListboxItemGroupLabel", ), ValueText: withContext(ArkListbox.ValueText, "valueText", "ListboxValueText"), Empty: withContext(ArkListbox.Empty, "empty", "ListboxEmpty"),};
export { createListCollection } from "@ark-ui/react/listbox";