Combobox
Multi-purpose combobox widget to allow selection from a dynamic set of options.
#
Documentation
#
#
Usage
#
App.tsx
import {
Combobox,
ComboboxCheckboxItem,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxScrollArea,
ComboboxTrigger,
ComboboxValue,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
import { colors } from "./data";
export function App() {
const [items, setItems] = useState(colors);
const [value, setValue] = useState<string[]>([]);
return (
<Combobox
items={items}
onInputValueChange={(inputValue) => {
setItems(
inputValue
? colors.filter((color) => new RegExp(inputValue, "i").test(color))
: colors,
);
}}
onItemSelect={(value) =>
setValue((prev) =>
prev.includes(value)
? prev.filter((v) => v !== value)
: [...prev, value],
)
}
value={value}
>
<ComboboxTrigger w="224">
<ComboboxValue placeholder="Select colors..." />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput />
<ComboboxScrollArea>
{items.map((item) => (
<ComboboxCheckboxItem item={item} key={item}>
{item}
</ComboboxCheckboxItem>
))}
{items.length === 0 && <ComboboxEmpty>No result found</ComboboxEmpty>}
</ComboboxScrollArea>
</ComboboxContent>
</Combobox>
);
}
#
Anatomy
#
import {
Combobox,
ComboboxCheckboxItem,
ComboboxContent,
ComboboxEmpty,
ComboboxFooter,
ComboboxGroup,
ComboboxInput,
ComboboxItem,
ComboboxLabel,
ComboboxRadioItem,
ComboboxScrollArea,
ComboboxSeparator,
ComboboxTrigger,
ComboboxValue,
ComboboxVirtualized,
} from "@optiaxiom/react";
export default () => (
<Combobox>
<ComboboxTrigger>
<ComboboxValue />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput />
<ComboboxScrollArea>
<ComboboxItem />
<ComboboxCheckboxItem />
<ComboboxRadioItem />
<ComboboxGroup>
<ComboboxLabel />
<ComboboxItem />
<ComboboxCheckboxItem />
<ComboboxRadioItem />
</ComboboxGroup>
<ComboboxVirtualized>
<ComboboxItem />
<ComboboxCheckboxItem />
<ComboboxRadioItem />
</ComboboxVirtualized>
<ComboboxEmpty />
<ComboboxSeparator />
</ComboboxScrollArea>
<ComboboxFooter />
</ComboboxContent>
</Combobox>
);
#
Data
#
Use the items
prop to pass the collection of data to Combobox
. Items can be an array of primitive types such as strings or it can be an array of objects.
In case item is an object you need to pass an additional itemToString
prop for Combobox
to work.
App.tsx
import { Box } from "@optiaxiom/react";
import {
Combobox,
ComboboxCheckboxItem,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxScrollArea,
ComboboxTrigger,
ComboboxValue,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
import { type Color, colors } from "./data";
export function App() {
const [items, setItems] = useState(colors);
const [value, setValue] = useState<Color[]>([]);
return (
<Combobox
isItemDisabled={(item) => Boolean(item.isDisabled)}
items={items}
itemToString={(item) => (item ? item.label : "")}
onInputValueChange={(inputValue) => {
setItems(
inputValue
? colors.filter((color) =>
new RegExp(inputValue, "i").test(color.label),
)
: colors,
);
}}
onItemSelect={(value) =>
setValue((prev) =>
prev.includes(value)
? prev.filter((v) => v !== value)
: [...prev, value],
)
}
value={value}
>
<ComboboxTrigger w="224">
<ComboboxValue placeholder="Search a color..." />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput />
<ComboboxScrollArea>
{items.map((item) => (
<ComboboxCheckboxItem
icon={
<Box
rounded="sm"
style={{ aspectRatio: 1, backgroundColor: item.color }}
/>
}
item={item}
key={item.value}
>
{item.label}
</ComboboxCheckboxItem>
))}
{items.length === 0 && <ComboboxEmpty>No result found</ComboboxEmpty>}
</ComboboxScrollArea>
</ComboboxContent>
</Combobox>
);
}
#
Filtering
#
Use the onInputValueChange
handler to handle filtering your list of items as the user is typing.
App.tsx
import { Box } from "@optiaxiom/react";
import {
Combobox,
ComboboxCheckboxItem,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxScrollArea,
ComboboxTrigger,
ComboboxValue,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
import { type Color, colors } from "./data";
export function App() {
const [items, setItems] = useState(colors);
const [value, setValue] = useState<Color[]>([]);
return (
<Combobox
isItemDisabled={(item) => Boolean(item.isDisabled)}
items={items}
itemToString={(item) => (item ? item.label : "")}
onInputValueChange={(inputValue) => {
setItems(
inputValue
? colors.filter((color) =>
new RegExp(inputValue, "i").test(color.label),
)
: colors,
);
}}
onItemSelect={(value) =>
setValue((prev) =>
prev.includes(value)
? prev.filter((v) => v !== value)
: [...prev, value],
)
}
value={value}
>
<ComboboxTrigger w="224">
<ComboboxValue placeholder="Search a color..." />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput />
<ComboboxScrollArea>
{items.map((item) => (
<ComboboxCheckboxItem
icon={
<Box
rounded="sm"
style={{ aspectRatio: 1, backgroundColor: item.color }}
/>
}
item={item}
key={item.value}
>
{item.label}
</ComboboxCheckboxItem>
))}
{items.length === 0 && <ComboboxEmpty>No result found</ComboboxEmpty>}
</ComboboxScrollArea>
</ComboboxContent>
</Combobox>
);
}
#
Controlled
#
We can only use Combobox
in a controlled mode and using the following props:
value
: Represents an array of currently selected elements from theitems
array.onItemSelect
: Handler invoked when user selects an item with the item as the only parameter.
Developers are expected to manually track the selection on their side since items inside a combobox can represent values or actions.
App.tsx
import {
Combobox,
ComboboxCheckboxItem,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxScrollArea,
ComboboxTrigger,
ComboboxValue,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
import { colors } from "./data";
import { useSet } from "./useSet";
export function App() {
const [items, setItems] = useState(colors);
const [value, { toggle }] = useSet([colors[1]]);
return (
<Combobox
items={items}
itemToString={(item) => item?.label || ""}
onInputValueChange={(inputValue) => {
setItems(
inputValue
? colors.filter((color) =>
new RegExp(inputValue, "i").test(color.label),
)
: colors,
);
}}
onItemSelect={(value) => toggle(value)}
value={value}
>
<ComboboxTrigger w="224">
<ComboboxValue placeholder="Search a color..." />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput />
<ComboboxScrollArea>
{items.map((item) => (
<ComboboxCheckboxItem item={item} key={item.label}>
{item.label}
</ComboboxCheckboxItem>
))}
{items.length === 0 && <ComboboxEmpty>No result found</ComboboxEmpty>}
</ComboboxScrollArea>
</ComboboxContent>
</Combobox>
);
}
#
Disabled
#
Use the optional isItemDisabled
prop to configure which items should be disabled. The callback is passed the item and the index number as arguments and expects a boolean result.
App.tsx
import { Box } from "@optiaxiom/react";
import {
Combobox,
ComboboxCheckboxItem,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxScrollArea,
ComboboxTrigger,
ComboboxValue,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
import { type Color, colors } from "./data";
export function App() {
const [items, setItems] = useState(colors);
const [value, setValue] = useState<Color[]>([]);
return (
<Combobox
isItemDisabled={(item) => Boolean(item.isDisabled)}
items={items}
itemToString={(item) => (item ? item.label : "")}
onInputValueChange={(inputValue) => {
setItems(
inputValue
? colors.filter((color) =>
new RegExp(inputValue, "i").test(color.label),
)
: colors,
);
}}
onItemSelect={(value) =>
setValue((prev) =>
prev.includes(value)
? prev.filter((v) => v !== value)
: [...prev, value],
)
}
value={value}
>
<ComboboxTrigger w="224">
<ComboboxValue placeholder="Search a color..." />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput />
<ComboboxScrollArea>
{items.map((item) => (
<ComboboxCheckboxItem
icon={
<Box
rounded="sm"
style={{ aspectRatio: 1, backgroundColor: item.color }}
/>
}
item={item}
key={item.value}
>
{item.label}
</ComboboxCheckboxItem>
))}
{items.length === 0 && <ComboboxEmpty>No result found</ComboboxEmpty>}
</ComboboxScrollArea>
</ComboboxContent>
</Combobox>
);
}
#
Creatable
#
We can combine filtering and controlled usage to build creatable comboboxes by allowing the user to add new entries on the fly.
App.tsx
import {
Combobox,
ComboboxCheckboxItem,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxScrollArea,
ComboboxTrigger,
ComboboxValue,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
import { type Color, colors } from "./data";
import { useSet } from "./useSet";
export function App() {
const [items, setItems] = useState(colors);
const [value, { toggle }] = useSet<Color>([]);
const [inputValue, setInputValue] = useState("");
const filteredItems = inputValue
? [
...items.filter((color) =>
new RegExp(inputValue, "i").test(color.label),
),
...(items.find(
(color) => color.label.toLowerCase() === inputValue.toLowerCase(),
)
? []
: [{ label: inputValue, new: true }]),
]
: items;
return (
<Combobox
inputValue={inputValue}
items={filteredItems}
itemToString={(item) => item?.label || ""}
onInputValueChange={setInputValue}
onItemSelect={(value) => {
if (value.new) {
const newItem = { label: value.label };
setItems((items) => [...items, newItem]);
toggle(newItem);
setInputValue("");
} else {
toggle(value);
}
}}
value={value}
>
<ComboboxTrigger w="224">
<ComboboxValue placeholder="Search a color..." />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput />
<ComboboxScrollArea>
{filteredItems.map((item) => (
<ComboboxCheckboxItem item={item} key={item.label}>
{item.new ? `Create "${item.label}"` : item.label}
</ComboboxCheckboxItem>
))}
{filteredItems.length === 0 && (
<ComboboxEmpty>No result found</ComboboxEmpty>
)}
</ComboboxScrollArea>
</ComboboxContent>
</Combobox>
);
}
#
Trigger
#
By default we use the Button
component for the combobox trigger which accepts all of the existing button props.
#
Item
#
The ComboboxCheckboxItem
component accepts icon
, addonBefore
, addonAfter
, and description
props for additional content inside the items.
App.tsx
import { Box } from "@optiaxiom/react";
import {
Combobox,
ComboboxCheckboxItem,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxScrollArea,
ComboboxTrigger,
ComboboxValue,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
import { type Color, colors } from "./data";
export function App() {
const [items, setItems] = useState(colors);
const [value, setValue] = useState<Color[]>([]);
return (
<Combobox
isItemDisabled={(item) => Boolean(item.isDisabled)}
items={items}
itemToString={(item) => (item ? item.label : "")}
onInputValueChange={(inputValue) => {
setItems(
inputValue
? colors.filter((color) =>
new RegExp(inputValue, "i").test(color.label),
)
: colors,
);
}}
onItemSelect={(value) =>
setValue((prev) =>
prev.includes(value)
? prev.filter((v) => v !== value)
: [...prev, value],
)
}
value={value}
>
<ComboboxTrigger w="224">
<ComboboxValue placeholder="Search a color..." />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput />
<ComboboxScrollArea>
{items.map((item) => (
<ComboboxCheckboxItem
icon={
<Box
rounded="sm"
style={{ aspectRatio: 1, backgroundColor: item.color }}
/>
}
item={item}
key={item.value}
>
{item.label}
</ComboboxCheckboxItem>
))}
{items.length === 0 && <ComboboxEmpty>No result found</ComboboxEmpty>}
</ComboboxScrollArea>
</ComboboxContent>
</Combobox>
);
}
#
Empty
#
Use the ComboboxEmpty
component when there are no available items or suggestions.
App.tsx
import { Box } from "@optiaxiom/react";
import {
Combobox,
ComboboxCheckboxItem,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxScrollArea,
ComboboxTrigger,
ComboboxValue,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
import { type Color, colors } from "./data";
export function App() {
const [items, setItems] = useState(colors);
const [value, setValue] = useState<Color[]>([]);
return (
<Combobox
isItemDisabled={(item) => Boolean(item.isDisabled)}
items={items}
itemToString={(item) => (item ? item.label : "")}
onInputValueChange={(inputValue) => {
setItems(
inputValue
? colors.filter((color) =>
new RegExp(inputValue, "i").test(color.label),
)
: colors,
);
}}
onItemSelect={(value) =>
setValue((prev) =>
prev.includes(value)
? prev.filter((v) => v !== value)
: [...prev, value],
)
}
value={value}
>
<ComboboxTrigger w="224">
<ComboboxValue placeholder="Search a color..." />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput />
<ComboboxScrollArea>
{items.map((item) => (
<ComboboxCheckboxItem
icon={
<Box
rounded="sm"
style={{ aspectRatio: 1, backgroundColor: item.color }}
/>
}
item={item}
key={item.value}
>
{item.label}
</ComboboxCheckboxItem>
))}
{items.length === 0 && <ComboboxEmpty>No result found</ComboboxEmpty>}
</ComboboxScrollArea>
</ComboboxContent>
</Combobox>
);
}
#
Loading
#
Use the loading
prop on ComboboxScrollArea
to show a loading spinner if the initial data for the combobox has not loaded yet.
App.tsx
import {
Combobox,
ComboboxCheckboxItem,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxScrollArea,
ComboboxTrigger,
ComboboxValue,
} from "@optiaxiom/react/unstable";
import { useEffect, useState } from "react";
import { colors } from "./data";
export function App() {
const [items, setItems] = useState<string[]>([]);
const [inputValue, setInputValue] = useState("");
const filteredItems = inputValue
? items.filter((color) => new RegExp(inputValue, "i").test(color))
: items;
const [open, setOpen] = useState(false);
useEffect(() => {
const timer = setTimeout(() => setItems(open ? colors : []), 3000);
return () => clearTimeout(timer);
}, [open]);
return (
<Combobox
items={filteredItems}
onInputValueChange={setInputValue}
onOpenChange={setOpen}
open={open}
>
<ComboboxTrigger w="224">
<ComboboxValue placeholder="Search a color..." />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput />
<ComboboxScrollArea loading={items.length === 0}>
{filteredItems.map((item) => (
<ComboboxCheckboxItem item={item} key={item}>
{item}
</ComboboxCheckboxItem>
))}
{filteredItems.length === 0 && (
<ComboboxEmpty>No result found</ComboboxEmpty>
)}
</ComboboxScrollArea>
</ComboboxContent>
</Combobox>
);
}
#
Virtualized
#
We can use ComboboxVirtualized
component to improve performance when rendering a large number of items inside the combobox.
import {
Combobox,
ComboboxCheckboxItem,
ComboboxContent,
ComboboxEmpty,
ComboboxInput,
ComboboxScrollArea,
ComboboxTrigger,
ComboboxValue,
ComboboxVirtualized,
} from "@optiaxiom/react/unstable";
import { useState } from "react";
const colors = Array.from({ length: 2000 }).map(
(_, index) => `Color ${index + 1}`,
);
export function App() {
const [items, setItems] = useState(colors);
const [value, setValue] = useState([colors[0]]);
return (
<Combobox
items={items}
onInputValueChange={(inputValue) => {
setItems(
inputValue
? colors.filter((color) => new RegExp(inputValue, "i").test(color))
: colors,
);
}}
onItemSelect={(value) =>
setValue((prev) =>
prev.includes(value)
? prev.filter((v) => v !== value)
: [...prev, value],
)
}
value={value}
>
<ComboboxTrigger w="224">
<ComboboxValue placeholder="Search a color..." />
</ComboboxTrigger>
<ComboboxContent>
<ComboboxInput />
<ComboboxScrollArea>
<ComboboxVirtualized items={items}>
{(item) => (
<ComboboxCheckboxItem item={item} key={item}>
{item}
</ComboboxCheckboxItem>
)}
</ComboboxVirtualized>
{items.length === 0 && <ComboboxEmpty>No result found</ComboboxEmpty>}
</ComboboxScrollArea>
</ComboboxContent>
</Combobox>
);
}
#
Related
#
DropdownMenu
Display a dropdown menu.
Select
Single select combobox widget to allow selection from a fixed set of options.
#
Props
#
#
Combobox
#
Doesn't render its own HTML element.
Prop |
---|
defaultOpen The initial open state in uncontrolled mode.
Default: |
inputValue The input value in controlled mode.
|
isItemDisabled Return true if items need to be marked as disabled and skipped from keyboard navigation.
Default: |
items* The items we want to render.
|
itemToKey Return a unique key for each item when the object reference will change during renders.
Default: |
itemToString Return a string representation of items if they are objects. Needed to show selected values inside triggers.
Default: |
onInputValueChange Handler that is called when input value changes.
|
onItemSelect Handler that is called when an item is selected either via keyboard or mouse.
|
onOpenChange Handler that is called when the open state changes.
|
open The open state in controlled mode.
|
value
|
#
ComboboxTrigger
#
Supports all Button props in addition to its own. Renders a <button>
element.
Prop |
---|
addonAfter Display content inside the button after
|
addonBefore Display content inside the button before
|
appearance Control the appearance by selecting between the different button types.
|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
disabled Whether the button is disabled.
|
hasCustomAnchor
|
icon Display an icon before or after the button content or omit
|
iconPosition Control whether to show the icon before or after the button content.
|
loading Whether to show loading spinner inside the button.
|
size Control the size of the button.
|
square Whether button should have square shape.
|
#
ComboboxValue
#
Supports all Box props in addition to its own. Renders a <div>
element.
Prop |
---|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
placeholder
|
#
ComboboxContent
#
Supports all Box props in addition to its own. Renders a <div>
element.
Prop |
---|
align
|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
minW Whether to set the min-width to the width of the trigger.
|
onCloseAutoFocus Event handler called when auto-focusing on close. Can be prevented.
|
onEscapeKeyDown Event handler called when the escape key is down. Can be prevented.
|
onFocusOutside Event handler called when the focus moves outside of the
|
onInteractOutside Event handler called when an interaction happens outside the
|
onOpenAutoFocus Event handler called when auto-focusing on open. Can be prevented.
|
onPointerDownOutside Event handler called when the a
|
side
|
sideOffset
|
transitionType
|
withArrow Whether to show an arrow.
|
#
ComboboxInput
#
Supports all Input props in addition to its own. Renders a <div>
element but forwards all props to an inner <input>
element.
Prop |
---|
addonAfter Display content inside the input at the end.
|
addonBefore Display content inside the input at the start.
|
addonPointerEvents When this prop is set to
|
appearance Control the appearance of the input.
|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
disabled Whether the input is disabled.
|
error Whether to show the input error state.
|
htmlSize Control the native input
|
size Control the size of the input.
|
#
ComboboxScrollArea
#
Supports all Box props in addition to its own. Renders a <div>
element.
Prop |
---|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
loading Whether to show loading spinner inside the menu.
|
#
ComboboxVirtualized
#
Supports all Box props in addition to its own. Renders a <div>
element.
Prop |
---|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
items*
|
#
ComboboxItem
#
Supports all Box props in addition to its own. Renders a <div>
element.
Prop |
---|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
item* The exact item object from the collection.
|
selected Whether to override the default selected state.
|
#
ComboboxCheckboxItem
#
Supports all Box props in addition to its own. Renders a <div>
element.
Prop |
---|
addonAfter Display content inside the item after
|
addonBefore Display content inside the item before
|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
description Add secondary text after the primary label.
|
icon Display an icon before the item content.
|
intent Control the appearance by selecting between the different item types.
|
item* The exact item object from the collection.
|
selected Whether to override the default selected state.
|
#
ComboboxRadioItem
#
Supports all Box props in addition to its own. Renders a <div>
element.
Prop |
---|
addonAfter Display content inside the item after
|
addonBefore Display content inside the item before
|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
description Add secondary text after the primary label.
|
icon Display an icon before the item content.
|
intent Control the appearance by selecting between the different item types.
|
item* The exact item object from the collection.
|
selected Whether to override the default selected state.
|
#
ComboboxGroup
#
Supports all Box props in addition to its own. Renders a <div>
element.
Prop |
---|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
#
ComboboxLabel
#
Supports all Box props in addition to its own. Renders a <div>
element.
Prop |
---|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
#
ComboboxSeparator
#
Supports all Separator props in addition to its own. Renders a <div>
element.
Prop |
---|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
decorative Whether or not the component is purely decorative. When true, accessibility-related attributes are updated so that that the rendered element is removed from the accessibility tree.
|
orientation
|
#
ComboboxEmpty
#
Supports all Box props in addition to its own. Renders a <div>
element.
Prop |
---|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
#
ComboboxFooter
#
Supports all Box props in addition to its own. Renders a <div>
element.
Prop |
---|
asChild Change the default rendered element for the one passed as a child, merging their props and behavior. Read the Composition guide for more details.
|
className
|
#
Changelog
#
#
0.4.0
#
- Added component