All components outlinesubtleflushedxssmmdlg
Combobox
Filterable input with a suggestion popup, item indicators, and an empty state slot.
Amsterdam
Berlin
Lisbon
Oslo
Porto
12345678910111213141516171819202122232425262728293031323334353637383940import { useMemo, useState } from "react";import { Combobox, createListCollection } from "#components/ui/ark-ui/combobox.tsx";
const cities = ["Amsterdam", "Berlin", "Lisbon", "Oslo", "Porto"];
export function Example() { const [items, setItems] = useState(cities); const collection = useMemo(() => createListCollection({ items }), [items]); return ( // variant/size resolve once on Root; every part inherits them via // context — IndicatorGroup and ItemIndicator ship default icons <Combobox.Root collection={collection} onInputValueChange={(d) => setItems(cities.filter((c) => c.toLowerCase().includes(d.inputValue.toLowerCase()))) } openOnClick variant="outline" size="md" > <Combobox.Label>City</Combobox.Label> <Combobox.Control> <Combobox.Input placeholder="Search cities" /> <Combobox.IndicatorGroup /> </Combobox.Control> {/* Positioner portals the panel and positions it under the input */} <Combobox.Positioner> <Combobox.Content> {collection.items.map((city) => ( <Combobox.Item key={city} item={city}> <Combobox.ItemText>{city}</Combobox.ItemText> <Combobox.ItemIndicator /> </Combobox.Item> ))} {collection.items.length === 0 ? <Combobox.Empty>No matches</Combobox.Empty> : null} </Combobox.Content> </Combobox.Positioner> </Combobox.Root> );}Variants
Amsterdam
Berlin
Lisbon
Oslo
Porto
Amsterdam
Berlin
Lisbon
Oslo
Porto
Amsterdam
Berlin
Lisbon
Oslo
Porto
Sizes
Amsterdam
Berlin
Lisbon
Oslo
Porto
Amsterdam
Berlin
Lisbon
Oslo
Porto
Amsterdam
Berlin
Lisbon
Oslo
Porto
Amsterdam
Berlin
Lisbon
Oslo
Porto
Install
1npx stylexblocks add comboboxSource
components/ui/ark-ui/combobox.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767import * as stylex from "@stylexjs/stylex";import { useContext } from "react";import { Combobox as ArkCombobox, type CollectionItem } from "@ark-ui/react/combobox";import { Portal } from "@ark-ui/react/portal";import { mergeProps } from "@ark-ui/react/utils";import { createSlotRecipeContext } from "#lib/ark-ui/create-slot-recipe-context.tsx";import { defineSlotRecipe, resolveSlotStyles, type SlotRecipeVariants, type StyleOverrideProps, type WithStyleOverrides,} from "#lib/recipe-props.ts";import { semanticShadows } from "@stylexblocks/ui/semantic-tokens/shadows.stylex.ts";import { comboboxIndicatorGroupIconVars, comboboxItemIconVars,} from '@stylexblocks/ui/slot-recipes/combobox.stylex.ts'
import { colorPalette } from '@stylexblocks/ui/color-palette.stylex.ts'import { cursor } from '@stylexblocks/ui/tokens/cursor.stylex.ts'import { durations } from '@stylexblocks/ui/tokens/durations.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 { radii } from '@stylexblocks/ui/tokens/radii.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'import { zIndices } from '@stylexblocks/ui/tokens/z-indices.stylex.ts'
const comboboxRoot = stylex.create({ base: { display: 'flex', flexDirection: 'column', gap: spacing._1_5, width: sizes.full, },})
const comboboxRootSizes = stylex.create({ xs: { '--combobox-input-height': sizes.controlXs, '--combobox-input-padding-x': spacing._2, '--combobox-indicator-size': sizes._3_5, }, sm: { '--combobox-input-height': sizes.controlSm, '--combobox-input-padding-x': spacing._2_5, '--combobox-indicator-size': sizes._4, }, md: { '--combobox-input-height': sizes.controlMd, '--combobox-input-padding-x': spacing._3, '--combobox-indicator-size': sizes._4, }, lg: { '--combobox-input-height': sizes.controlLg, '--combobox-input-padding-x': spacing._4, '--combobox-indicator-size': sizes._5, },})
const comboboxLabel = 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 comboboxInput = stylex.create({ base: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', width: sizes.full, minHeight: 'var(--combobox-input-height)', paddingInline: 'var(--combobox-input-padding-x)', '--input-height': 'var(--combobox-input-height)', borderRadius: semanticRadii.control, outline: 'none', userSelect: 'none', textAlign: 'start', opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, cursor: { default: null, ':is(:disabled, [disabled], [data-disabled])': 'not-allowed', }, '--focus-color': colorPalette.focusRing, '--error-color': semanticColors.borderError, '--focus-ring-color': { default: null, ':is([data-invalid], [aria-invalid="true"])': 'var(--error-color)', }, borderColor: { default: null, ':is([data-invalid], [aria-invalid="true"])': 'var(--error-color)', }, },})
const comboboxInputSizes = stylex.create({ xs: { fontSize: fontSizes.xs, lineHeight: lineHeights.xs, }, sm: { fontSize: fontSizes.sm, lineHeight: lineHeights.sm, }, md: { fontSize: fontSizes.sm, lineHeight: lineHeights.sm, }, lg: { fontSize: fontSizes.md, lineHeight: lineHeights.md, },})
const comboboxInputVariants = stylex.create({ outline: { backgroundColor: semanticColors.bg, borderWidth: '1px', borderColor: { default: semanticColors.border, ':is([data-invalid], [aria-invalid="true"])': 'var(--error-color)', }, outlineWidth: { default: null, ':focus-visible': 'var(--focus-ring-width, 0)', }, outlineOffset: { default: null, ':focus-visible': '0px', }, outlineStyle: { default: null, ':focus-visible': 'solid', }, outlineColor: { default: null, ':focus-visible': colorPalette.focusRing, }, }, subtle: { borderWidth: '1px', borderColor: { default: 'transparent', ':is([data-invalid], [aria-invalid="true"])': 'var(--error-color)', }, backgroundColor: semanticColors.bgMuted, outlineWidth: { default: null, ':focus-visible': 'var(--focus-ring-width, 0)', }, outlineOffset: { default: null, ':focus-visible': '0px', }, outlineStyle: { default: null, ':focus-visible': 'solid', }, outlineColor: { default: null, ':focus-visible': colorPalette.focusRing, }, }, flushed: { backgroundColor: 'transparent', borderBottomWidth: '1px', borderBottomColor: semanticColors.border, borderRadius: 0, paddingInline: 0, borderColor: { default: null, ':is([data-invalid], [aria-invalid="true"])': 'var(--error-color)', ':focus-visible': 'var(--focus-color)', }, boxShadow: { default: null, ':focus-visible': '0px 1px 0px 0px var(--focus-color)', }, },})
const comboboxTrigger = stylex.create({ base: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', pointerEvents: 'auto', '--input-height': 'var(--combobox-input-height)', },})
const comboboxTriggerSizes = stylex.create({ xs: { fontSize: fontSizes.xs, lineHeight: lineHeights.xs, gap: spacing._1, }, sm: { fontSize: fontSizes.xs, lineHeight: lineHeights.xs, gap: spacing._1, }, md: { fontSize: fontSizes.sm, lineHeight: lineHeights.sm, gap: spacing._2, }, lg: { fontSize: fontSizes.md, lineHeight: lineHeights.md, paddingBlock: spacing._3, gap: spacing._2, },})
const comboboxClearTrigger = stylex.create({ base: { color: semanticColors.fgMuted, pointerEvents: 'auto', outlineWidth: { default: null, ':focus-visible': 'var(--focus-ring-width, 0)', }, outlineOffset: { default: null, ':focus-visible': '0px', }, outlineStyle: { default: null, ':focus-visible': 'solid', }, outlineColor: { default: null, ':focus-visible': colorPalette.focusRing, }, focusRingWidth: '2px', borderRadius: semanticRadii.control, },})
const comboboxControl = stylex.create({ base: { position: 'relative', },})
const comboboxIndicatorGroup = stylex.create({ base: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: spacing._1, position: 'absolute', insetInlineEnd: 0, top: 0, bottom: 0, pointerEvents: 'none', paddingInline: 'var(--combobox-input-padding-x)', [comboboxIndicatorGroupIconVars.width]: 'var(--combobox-indicator-size)', [comboboxIndicatorGroupIconVars.height]: 'var(--combobox-indicator-size)', },})
const comboboxIndicatorGroupVariants = stylex.create({ flushed: { paddingInline: 0, },})
const comboboxIndicatorGroupIcon = stylex.create({ base: { width: comboboxIndicatorGroupIconVars.width, height: comboboxIndicatorGroupIconVars.height, },})
const comboboxContent = stylex.create({ base: { display: 'flex', flexDirection: 'column', '--menu-z-index': zIndices.layer3, zIndex: 'calc(var(--menu-z-index) + var(--layer-index, 0))', outline: 'none', maxHeight: 'var(--available-height)', overflowY: 'auto', animationName: { default: null, ':is([data-open], [data-state="open"])': 'fade-in, slide-from-top', ':is([data-closed], [data-state="closed"])': 'fade-out', }, animationDuration: { default: null, ':is([data-open], [data-state="open"])': durations.fast, ':is([data-closed], [data-state="closed"])': durations.faster, }, },})
const comboboxContentSizes = stylex.create({ xs: { '--combobox-item-padding-x': spacing._1, '--combobox-item-padding-y': spacing._0_5, '--combobox-indicator-size': sizes._3_5, borderRadius: semanticRadii.panel, padding: spacing._0_5, fontSize: fontSizes.xs, lineHeight: lineHeights.xs, }, sm: { '--combobox-item-padding-x': spacing._1_5, '--combobox-item-padding-y': spacing._1, '--combobox-indicator-size': sizes._4, borderRadius: semanticRadii.panel, padding: spacing._1, fontSize: fontSizes.xs, lineHeight: lineHeights.xs, }, md: { '--combobox-item-padding-x': spacing._2, '--combobox-item-padding-y': spacing._1_5, '--combobox-indicator-size': sizes._4, borderRadius: semanticRadii.panel, padding: spacing._1, fontSize: fontSizes.sm, lineHeight: lineHeights.sm, }, lg: { '--combobox-item-padding-y': spacing._2, '--combobox-item-padding-x': spacing._3, '--combobox-indicator-size': sizes._5, borderRadius: semanticRadii.panel, padding: spacing._1, fontSize: fontSizes.md, lineHeight: lineHeights.md, },})
const comboboxItem = stylex.create({ base: { position: 'relative', userSelect: 'none', display: 'flex', alignItems: 'center', gap: spacing._2, paddingBlock: 'var(--combobox-item-padding-y)', paddingInline: 'var(--combobox-item-padding-x)', cursor: cursor.option, justifyContent: 'space-between', flex: 1, textAlign: 'start', borderRadius: semanticRadii.control, pointerEvents: { default: null, ':is(:disabled, [disabled], [data-disabled])': 'none', }, opacity: { default: null, ':is(:disabled, [disabled], [data-disabled])': 0.5, }, [comboboxItemIconVars.width]: 'var(--combobox-indicator-size)', [comboboxItemIconVars.height]: 'var(--combobox-indicator-size)', },})
const comboboxItemSizes = stylex.create({ xs: { borderRadius: `max(0px, calc(${semanticRadii.panel} - ${sizes._0_5}))`, }, sm: { borderRadius: `max(0px, calc(${semanticRadii.panel} - ${sizes._0_5}))`, }, md: { borderRadius: `max(0px, calc(${semanticRadii.panel} - ${sizes._1}))`, }, lg: { borderRadius: `max(0px, calc(${semanticRadii.panel} - ${sizes._1}))`, },})
const comboboxItemIcon = stylex.create({ base: { width: comboboxItemIconVars.width, height: comboboxItemIconVars.height, },})
const comboboxEmpty = stylex.create({ base: { paddingBlock: 'var(--combobox-item-padding-y)', paddingInline: 'var(--combobox-item-padding-x)', },})
const comboboxItemText = stylex.create({ base: { flex: 1, },})
const comboboxItemGroup = stylex.create({ base: { paddingBottom: { default: 'var(--combobox-item-padding-y)', ':last-child': 0, }, },})
const comboboxItemGroupLabel = stylex.create({ base: { fontWeight: fontWeights.medium, paddingBlock: 'var(--combobox-item-padding-y)', paddingInline: 'var(--combobox-item-padding-x)', },})
const comboboxItemIndicator = stylex.create({ base: {},})
const comboboxItemIndicatorSizes = stylex.create({ md: { display: 'flex', alignItems: 'center', justifyContent: 'center', },})
export type ComboboxVariant = keyof typeof comboboxInputVariants
export type ComboboxSize = keyof typeof comboboxRootSizes
const comboboxRecipe = defineSlotRecipe({ slots: { root: { styles: comboboxRoot, sizes: comboboxRootSizes }, label: { styles: comboboxLabel }, input: { styles: comboboxInput, sizes: comboboxInputSizes, variants: comboboxInputVariants, }, trigger: { styles: comboboxTrigger, sizes: comboboxTriggerSizes }, clearTrigger: { styles: comboboxClearTrigger }, control: { styles: comboboxControl }, indicatorGroup: { styles: comboboxIndicatorGroup, variants: comboboxIndicatorGroupVariants, }, content: { styles: comboboxContent, sizes: comboboxContentSizes }, item: { styles: comboboxItem, sizes: comboboxItemSizes }, empty: { styles: comboboxEmpty }, itemText: { styles: comboboxItemText }, itemGroup: { styles: comboboxItemGroup }, itemGroupLabel: { styles: comboboxItemGroupLabel }, itemIndicator: { styles: comboboxItemIndicator, sizes: comboboxItemIndicatorSizes }, }, defaultVariants: { size: "md", variant: "outline" },});
// chrome the generated recipe leaves to the composition layer: unstyled// trigger buttons, popup panel surface, and item hover feedbackconst chrome = stylex.create({ btnReset: { appearance: "none", backgroundColor: "transparent", borderWidth: 0, borderStyle: "none", padding: 0, margin: 0, fontFamily: "inherit", fontSize: "inherit", color: "inherit", cursor: "pointer", textAlign: "inherit", }, anchor: { position: "relative" }, panel: { backgroundColor: semanticColors.bgPanel, boxShadow: semanticShadows.overlay, minWidth: "var(--reference-width)", }, itemHover: { ":hover": { backgroundColor: semanticColors.bgMuted } },});
const panelLocal = stylex.create({ fieldTrigger: { // the variant recipes set border width/color but rely on the UA border // style of a native input; a reset button needs it explicit borderStyle: "solid", cursor: "pointer", textAlign: "start", }, fieldValue: { alignItems: "center", display: "inline-flex", minWidth: 0, }, fieldPlaceholder: { color: semanticColors.fgSubtle, }, panel: { display: "flex", flexDirection: "column", overflow: "hidden", padding: 0, }, search: { borderBlockEndColor: semanticColors.border, borderBlockEndStyle: "solid", borderBlockEndWidth: "1px", borderRadius: 0, flexShrink: 0, }, searchInput: { backgroundColor: "transparent", borderRadius: 0, borderStyle: "none", color: "inherit", minHeight: "unset", outlineStyle: "none", paddingBlock: "var(--combobox-item-padding-y)", paddingInline: "var(--combobox-item-padding-x)", width: "100%", }, panelList: { borderRadius: 0, flexGrow: 1, overflowY: "auto", },});
function ChevronDownIcon(props: React.ComponentProps<"svg">) { return ( <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" {...props}> <path d="m4 6 4 4 4-4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> </svg> );}
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, VariantContext } = createSlotRecipeContext(comboboxRecipe);
type ComboboxVariants = SlotRecipeVariants<typeof comboboxRecipe>;
const ComboboxRoot = withProvider(ArkCombobox.Root, "root", "ComboboxRoot");
function Root<T extends CollectionItem>( props: WithStyleOverrides<ArkCombobox.RootProps<T>> & Partial<ComboboxVariants>,) { return <ComboboxRoot {...(props as React.ComponentProps<typeof ComboboxRoot>)} />;}Root.displayName = "ComboboxRoot";
const StyledIndicatorGroup = withContext("div", "indicatorGroup", "ComboboxIndicatorGroup");
function IndicatorGroup( props: React.ComponentProps<typeof StyledIndicatorGroup>,) { const { children, ...rest } = props; return ( <StyledIndicatorGroup {...rest}> {children ?? <ChevronDownIcon {...stylex.props(comboboxIndicatorGroupIcon.base)} />} </StyledIndicatorGroup> );}IndicatorGroup.displayName = "ComboboxIndicatorGroup";
const StyledItemIndicator = withContext( ArkCombobox.ItemIndicator, (v) => [ ...resolveSlotStyles(comboboxRecipe.slots.itemIndicator, v), comboboxItemIndicatorSizes.md, ], "ComboboxItemIndicator",);
function ItemIndicator( props: React.ComponentProps<typeof ArkCombobox.ItemIndicator> & StyleOverrideProps,) { const { children, ...rest } = props; return ( <StyledItemIndicator {...rest}> {children ?? <CheckIcon {...stylex.props(comboboxItemIcon.base)} />} </StyledItemIndicator> );}ItemIndicator.displayName = "ComboboxItemIndicator";
function Positioner({ children, ...rest }: ArkCombobox.PositionerProps) { return ( <Portal> <ArkCombobox.Positioner {...rest}>{children}</ArkCombobox.Positioner> </Portal> );}
function FieldTriggerNode(props: ArkCombobox.TriggerProps) { return <ArkCombobox.Trigger focusable {...props} />;}
const StyledFieldTrigger = withContext( FieldTriggerNode, (v) => [ chrome.btnReset, ...resolveSlotStyles(comboboxRecipe.slots.input, v), panelLocal.fieldTrigger, ], "ComboboxFieldTrigger",);
/** * FieldTrigger — the select-look trigger for the panel layout: wears the * input recipe as a button, ships the default chevron, and is tabbable (the * machine's plain Trigger assumes an adjacent focusable input and renders * tabindex=-1). */function FieldTrigger( props: React.ComponentProps<typeof ArkCombobox.Trigger> & StyleOverrideProps,) { const { children, ...rest } = props; return ( <StyledFieldTrigger {...rest}> {children} <ChevronDownIcon {...stylex.props(comboboxIndicatorGroupIcon.base)} /> </StyledFieldTrigger> );}FieldTrigger.displayName = "ComboboxFieldTrigger";
/** * FieldValue — the trigger's value slot: lays out the selection (icon + * label compositions stay on one line) and falls back to a muted * `placeholder` while nothing is selected. */function FieldValue( props: React.ComponentProps<"span"> & StyleOverrideProps & { placeholder?: React.ReactNode },) { const { placeholder, children, ...rest } = props; return ( <FieldValueBox {...rest}> {children ?? <span {...stylex.props(panelLocal.fieldPlaceholder)}>{placeholder}</span>} </FieldValueBox> );}FieldValue.displayName = "ComboboxFieldValue";
const FieldValueBox = withContext("span", () => [panelLocal.fieldValue], "ComboboxFieldValueBox");
/** * Search — the panel's input row. The row mirrors the content recipe's * padding and the input adopts the item metrics, so the input's text column * lines up exactly with the item text below it. */function Search( props: WithStyleOverrides<ArkCombobox.InputProps> & { className?: string },) { const { palette, style, className, ...rest } = props; const v = useContext(VariantContext); return ( <div {...stylex.props(comboboxContentSizes[v.size], panelLocal.search, style)}> <ArkCombobox.Input {...mergeProps(rest, stylex.props(palette, panelLocal.searchInput), { className })} /> </div> );}Search.displayName = "ComboboxSearch";
/** * Combobox — Ark UI's combobox machine wearing the generated slot recipe. * `<Combobox.Root variant size>` resolves the recipe once; every part * inherits it through context. Content portals through * `Combobox.Positioner`; IndicatorGroup and ItemIndicator ship default * chevron / check icons. */export const Combobox = { Root, Label: withContext(ArkCombobox.Label, "label", "ComboboxLabel"), Control: withContext( ArkCombobox.Control, (v) => [...resolveSlotStyles(comboboxRecipe.slots.control, v), chrome.anchor], "ComboboxControl", ), Input: withContext(ArkCombobox.Input, "input", "ComboboxInput"), Trigger: withContext( ArkCombobox.Trigger, (v) => [chrome.btnReset, ...resolveSlotStyles(comboboxRecipe.slots.trigger, v)], "ComboboxTrigger", ), ClearTrigger: withContext( ArkCombobox.ClearTrigger, (v) => [chrome.btnReset, ...resolveSlotStyles(comboboxRecipe.slots.clearTrigger, v)], "ComboboxClearTrigger", ), IndicatorGroup, Positioner, Content: withContext( ArkCombobox.Content, (v) => [...resolveSlotStyles(comboboxRecipe.slots.content, v), chrome.panel], "ComboboxContent", ), Item: withContext( ArkCombobox.Item, (v) => [...resolveSlotStyles(comboboxRecipe.slots.item, v), chrome.itemHover], "ComboboxItem", ), ItemText: withContext(ArkCombobox.ItemText, "itemText", "ComboboxItemText"), ItemIndicator, Empty: withContext(ArkCombobox.Empty, "empty", "ComboboxEmpty"), ItemGroup: withContext(ArkCombobox.ItemGroup, "itemGroup", "ComboboxItemGroup"), ItemGroupLabel: withContext( ArkCombobox.ItemGroupLabel, "itemGroupLabel", "ComboboxItemGroupLabel", ), // input-inside-panel layout FieldTrigger, FieldValue, Panel: withContext( ArkCombobox.Content, (v) => [ ...resolveSlotStyles(comboboxRecipe.slots.content, v), chrome.panel, panelLocal.panel, ], "ComboboxPanel", ), Search, PanelList: withContext( "div", (v) => [comboboxContentSizes[v.size], panelLocal.panelList], "ComboboxPanelList", ), Context: ArkCombobox.Context,};
export { createListCollection } from "@ark-ui/react/combobox";
Examples
Installable blocks built with Combobox — real use cases, delivered as source you own.
Country Picker
Searchable country picker with flags, Intl-localized names, and ISO 3166-1 values — built on the accessible combobox pattern.
Model Picker
Settings menu for choosing an inference model — Fast switch, Effort submenu, and a searchable combobox for the model list.
Timezone Picker
Select-style timezone picker with a search input inside the panel — IANA zones and live offsets from Intl, filtered accessibly.
Afghanistan
Åland Islands
Albania
Algeria
American Samoa
Andorra
Angola
Anguilla
Antarctica
Antigua & Barbuda
Argentina
Armenia
Aruba
Australia
Austria
Azerbaijan
Bahamas
Bahrain
Bangladesh
Barbados
Belarus
Belgium
Belize
Benin
Bermuda
Bhutan
Bolivia
Bosnia & Herzegovina
Botswana
Bouvet Island
Brazil
British Indian Ocean Territory
British Virgin Islands
Brunei
Bulgaria
Burkina Faso
Burundi
Cambodia
Cameroon
Canada
Cape Verde
Caribbean Netherlands
Cayman Islands
Central African Republic
Chad
Chile
China
Christmas Island
Cocos (Keeling) Islands
Colombia
Comoros
Congo - Brazzaville
Congo - Kinshasa
Cook Islands
Costa Rica
Côte d’Ivoire
Croatia
Cuba
Curaçao
Cyprus
Czechia
Denmark
Djibouti
Dominica
Dominican Republic
Ecuador
Egypt
El Salvador
Equatorial Guinea
Eritrea
Estonia
Eswatini
Ethiopia
Falkland Islands
Faroe Islands
Fiji
Finland
France
French Guiana
French Polynesia
French Southern Territories
Gabon
Gambia
Georgia
Germany
Ghana
Gibraltar
Greece
Greenland
Grenada
Guadeloupe
Guam
Guatemala
Guernsey
Guinea
Guinea-Bissau
Guyana
Haiti
Heard & McDonald Islands
Honduras
Hong Kong SAR China
Hungary
Iceland
India
Indonesia
Iran
Iraq
Ireland
Isle of Man
Israel
Italy
Jamaica
Japan
Jersey
Jordan
Kazakhstan
Kenya
Kiribati
Kosovo
Kuwait
Kyrgyzstan
Laos
Latvia
Lebanon
Lesotho
Liberia
Libya
Liechtenstein
Lithuania
Luxembourg
Macao SAR China
Madagascar
Malawi
Malaysia
Maldives
Mali
Malta
Marshall Islands
Martinique
Mauritania
Mauritius
Mayotte
Mexico
Micronesia
Moldova
Monaco
Mongolia
Montenegro
Montserrat
Morocco
Mozambique
Myanmar (Burma)
Namibia
Nauru
Nepal
Netherlands
New Caledonia
New Zealand
Nicaragua
Niger
Nigeria
Niue
Norfolk Island
North Korea
North Macedonia
Northern Mariana Islands
Norway
Oman
Pakistan
Palau
Palestinian Territories
Panama
Papua New Guinea
Paraguay
Peru
Philippines
Pitcairn Islands
Poland
Portugal
Puerto Rico
Qatar
Réunion
Romania
Russia
Rwanda
Samoa
San Marino
São Tomé & Príncipe
Saudi Arabia
Senegal
Serbia
Seychelles
Sierra Leone
Singapore
Sint Maarten
Slovakia
Slovenia
Solomon Islands
Somalia
South Africa
South Georgia & South Sandwich Islands
South Korea
South Sudan
Spain
Sri Lanka
St. Barthélemy
St. Helena
St. Kitts & Nevis
St. Lucia
St. Martin
St. Pierre & Miquelon
St. Vincent & Grenadines
Sudan
Suriname
Svalbard & Jan Mayen
Sweden
Switzerland
Syria
Taiwan
Tajikistan
Tanzania
Thailand
Timor-Leste
Togo
Tokelau
Tonga
Trinidad & Tobago
Tunisia
Türkiye
Turkmenistan
Turks & Caicos Islands
Tuvalu
U.S. Outlying Islands
U.S. Virgin Islands
Uganda
Ukraine
United Arab Emirates
United Kingdom
United States
Uruguay
Uzbekistan
Vanuatu
Vatican City
Venezuela
Vietnam
Wallis & Futuna
Western Sahara
Yemen
Zambia
Zimbabwe
MidwayGMT-11
NiueGMT-11
Pago PagoGMT-11
HonoluluGMT-10
RarotongaGMT-10
TahitiGMT-10
MarquesasGMT-9:30
AdakGMT-9
GambierGMT-9
AnchorageGMT-8
JuneauGMT-8
MetlakatlaGMT-8
NomeGMT-8
PitcairnGMT-8
SitkaGMT-8
YakutatGMT-8
CrestonGMT-7
DawsonGMT-7
Dawson CreekGMT-7
Fort NelsonGMT-7
HermosilloGMT-7
Los AngelesGMT-7
MazatlanGMT-7
PhoenixGMT-7
TijuanaGMT-7
VancouverGMT-7
WhitehorseGMT-7
Bahia BanderasGMT-6
BelizeGMT-6
BoiseGMT-6
Cambridge BayGMT-6
ChihuahuaGMT-6
Ciudad JuarezGMT-6
Costa RicaGMT-6
DenverGMT-6
EdmontonGMT-6
El SalvadorGMT-6
GalapagosGMT-6
GuatemalaGMT-6
InuvikGMT-6
ManaguaGMT-6
MeridaGMT-6
Mexico CityGMT-6
MonterreyGMT-6
ReginaGMT-6
Swift CurrentGMT-6
TegucigalpaGMT-6
BeulahGMT-5
BogotaGMT-5
CancunGMT-5
CaymanGMT-5
CenterGMT-5
ChicagoGMT-5
Coral HarbourGMT-5
EasterGMT-5
EirunepeGMT-5
GuayaquilGMT-5
JamaicaGMT-5
KnoxGMT-5
LimaGMT-5
MatamorosGMT-5
MenomineeGMT-5
New SalemGMT-5
OjinagaGMT-5
PanamaGMT-5
Rankin InletGMT-5
ResoluteGMT-5
Rio BrancoGMT-5
Tell CityGMT-5
WinnipegGMT-5
AnguillaGMT-4
AntiguaGMT-4
ArubaGMT-4
BarbadosGMT-4
Blanc-SablonGMT-4
Boa VistaGMT-4
Campo GrandeGMT-4
CaracasGMT-4
CuiabaGMT-4
CuracaoGMT-4
DetroitGMT-4
DominicaGMT-4
Grand TurkGMT-4
GrenadaGMT-4
GuadeloupeGMT-4
GuyanaGMT-4
HavanaGMT-4
IndianapolisGMT-4
IqaluitGMT-4
KralendijkGMT-4
La PazGMT-4
LouisvilleGMT-4
Lower PrincesGMT-4
ManausGMT-4
MarengoGMT-4
MarigotGMT-4
MartiniqueGMT-4
MonticelloGMT-4
MontserratGMT-4
NassauGMT-4
New YorkGMT-4
PetersburgGMT-4
Port of SpainGMT-4
Port-au-PrinceGMT-4
Porto VelhoGMT-4
Puerto RicoGMT-4
Santo DomingoGMT-4
St BarthelemyGMT-4
St KittsGMT-4
St LuciaGMT-4
St ThomasGMT-4
St VincentGMT-4
TorontoGMT-4
TortolaGMT-4
VevayGMT-4
VincennesGMT-4
WinamacGMT-4
AraguainaGMT-3
AsuncionGMT-3
BahiaGMT-3
BelemGMT-3
BermudaGMT-3
Buenos AiresGMT-3
CatamarcaGMT-3
CayenneGMT-3
CordobaGMT-3
CoyhaiqueGMT-3
FortalezaGMT-3
Glace BayGMT-3
Goose BayGMT-3
HalifaxGMT-3
JujuyGMT-3
La RiojaGMT-3
MaceioGMT-3
MendozaGMT-3
MonctonGMT-3
MontevideoGMT-3
PalmerGMT-3
ParamariboGMT-3
Punta ArenasGMT-3
RecifeGMT-3
Rio GallegosGMT-3
RotheraGMT-3
SaltaGMT-3
San JuanGMT-3
San LuisGMT-3
SantaremGMT-3
SantiagoGMT-3
Sao PauloGMT-3
StanleyGMT-3
ThuleGMT-3
TucumanGMT-3
UshuaiaGMT-3
St JohnsGMT-2:30
MiquelonGMT-2
NoronhaGMT-2
South GeorgiaGMT-2
Cape VerdeGMT-1
GodthabGMT-1
ScoresbysundGMT-1
AbidjanGMT+0
AccraGMT+0
AzoresGMT+0
BamakoGMT+0
BanjulGMT+0
BissauGMT+0
ConakryGMT+0
DakarGMT+0
DanmarkshavnGMT+0
FreetownGMT+0
LomeGMT+0
MonroviaGMT+0
NouakchottGMT+0
OuagadougouGMT+0
ReykjavikGMT+0
Sao TomeGMT+0
St HelenaGMT+0
AlgiersGMT+1
BanguiGMT+1
BrazzavilleGMT+1
CanaryGMT+1
CasablancaGMT+1
DoualaGMT+1
DublinGMT+1
El AaiunGMT+1
FaeroeGMT+1
GuernseyGMT+1
Isle of ManGMT+1
JerseyGMT+1
KinshasaGMT+1
LagosGMT+1
LibrevilleGMT+1
LisbonGMT+1
LondonGMT+1
LuandaGMT+1
MadeiraGMT+1
MalaboGMT+1
NdjamenaGMT+1
NiameyGMT+1
Porto-NovoGMT+1
TunisGMT+1
AmsterdamGMT+2
AndorraGMT+2
BelgradeGMT+2
BerlinGMT+2
BlantyreGMT+2
BratislavaGMT+2
BrusselsGMT+2
BudapestGMT+2
BujumburaGMT+2
BusingenGMT+2
CeutaGMT+2
CopenhagenGMT+2
GaboroneGMT+2
GibraltarGMT+2
HarareGMT+2
JohannesburgGMT+2
JubaGMT+2
KaliningradGMT+2
KhartoumGMT+2
KigaliGMT+2
LjubljanaGMT+2
LongyearbyenGMT+2
LubumbashiGMT+2
LusakaGMT+2
LuxembourgGMT+2
MadridGMT+2
MaltaGMT+2
MaputoGMT+2
MaseruGMT+2
MbabaneGMT+2
MonacoGMT+2
OsloGMT+2
ParisGMT+2
PodgoricaGMT+2
PragueGMT+2
RomeGMT+2
San MarinoGMT+2
SarajevoGMT+2
SkopjeGMT+2
StockholmGMT+2
TiraneGMT+2
TripoliGMT+2
TrollGMT+2
VaduzGMT+2
VaticanGMT+2
ViennaGMT+2
WarsawGMT+2
WindhoekGMT+2
ZagrebGMT+2
ZurichGMT+2
Addis AbabaGMT+3
AdenGMT+3
AmmanGMT+3
AntananarivoGMT+3
AsmeraGMT+3
AthensGMT+3
BaghdadGMT+3
BahrainGMT+3
BeirutGMT+3
BucharestGMT+3
CairoGMT+3
ChisinauGMT+3
ComoroGMT+3
DamascusGMT+3
Dar es SalaamGMT+3
DjiboutiGMT+3
FamagustaGMT+3
GazaGMT+3
HebronGMT+3
HelsinkiGMT+3
IstanbulGMT+3
JerusalemGMT+3
KampalaGMT+3
KievGMT+3
KirovGMT+3
KuwaitGMT+3
MariehamnGMT+3
MayotteGMT+3
MinskGMT+3
MogadishuGMT+3
MoscowGMT+3
NairobiGMT+3
NicosiaGMT+3
QatarGMT+3
RigaGMT+3
RiyadhGMT+3
SimferopolGMT+3
SofiaGMT+3
SyowaGMT+3
TallinnGMT+3
VilniusGMT+3
VolgogradGMT+3
TehranGMT+3:30
AstrakhanGMT+4
BakuGMT+4
DubaiGMT+4
MaheGMT+4
MauritiusGMT+4
MuscatGMT+4
ReunionGMT+4
SamaraGMT+4
SaratovGMT+4
TbilisiGMT+4
UlyanovskGMT+4
YerevanGMT+4
KabulGMT+4:30
AlmatyGMT+5
AqtauGMT+5
AqtobeGMT+5
AshgabatGMT+5
AtyrauGMT+5
DushanbeGMT+5
KarachiGMT+5
KerguelenGMT+5
MaldivesGMT+5
MawsonGMT+5
OralGMT+5
QostanayGMT+5
QyzylordaGMT+5
SamarkandGMT+5
TashkentGMT+5
VostokGMT+5
YekaterinburgGMT+5
CalcuttaGMT+5:30
ColomboGMT+5:30
KatmanduGMT+5:45
BishkekGMT+6
ChagosGMT+6
DhakaGMT+6
OmskGMT+6
ThimphuGMT+6
UrumqiGMT+6
CocosGMT+6:30
RangoonGMT+6:30
BangkokGMT+7
BarnaulGMT+7
ChristmasGMT+7
DavisGMT+7
HovdGMT+7
JakartaGMT+7
KrasnoyarskGMT+7
NovokuznetskGMT+7
NovosibirskGMT+7
Phnom PenhGMT+7
PontianakGMT+7
SaigonGMT+7
TomskGMT+7
VientianeGMT+7
BruneiGMT+8
CaseyGMT+8
Hong KongGMT+8
IrkutskGMT+8
Kuala LumpurGMT+8
KuchingGMT+8
MacauGMT+8
MakassarGMT+8
ManilaGMT+8
PerthGMT+8
ShanghaiGMT+8
SingaporeGMT+8
TaipeiGMT+8
UlaanbaatarGMT+8
EuclaGMT+8:45
ChitaGMT+9
DiliGMT+9
JayapuraGMT+9
KhandygaGMT+9
PalauGMT+9
PyongyangGMT+9
SeoulGMT+9
TokyoGMT+9
YakutskGMT+9
AdelaideGMT+9:30
Broken HillGMT+9:30
DarwinGMT+9:30
BrisbaneGMT+10
DumontDUrvilleGMT+10
GuamGMT+10
HobartGMT+10
LindemanGMT+10
MacquarieGMT+10
MelbourneGMT+10
Port MoresbyGMT+10
SaipanGMT+10
SydneyGMT+10
TrukGMT+10
Ust-NeraGMT+10
VladivostokGMT+10
Lord HoweGMT+10:30
BougainvilleGMT+11
EfateGMT+11
GuadalcanalGMT+11
KosraeGMT+11
MagadanGMT+11
NorfolkGMT+11
NoumeaGMT+11
PonapeGMT+11
SakhalinGMT+11
SrednekolymskGMT+11
AnadyrGMT+12
AucklandGMT+12
FijiGMT+12
FunafutiGMT+12
KamchatkaGMT+12
KwajaleinGMT+12
MajuroGMT+12
McMurdoGMT+12
NauruGMT+12
TarawaGMT+12
WakeGMT+12
WallisGMT+12
ChathamGMT+12:45
ApiaGMT+13
EnderburyGMT+13
FakaofoGMT+13
TongatapuGMT+13
KiritimatiGMT+14