Skip to content

Combobox

An input that behaves similarly to a select, with the addition of a free text input to filter options.

Combobox is a custom input that behaves similarly to a select, with the addition of a free text input to filter options. It it used to improve user experience by helping the user select a value without having to type in the complete value and, optionally depending whether supported values are limited, preventing the user from entering invalid or otherwise unsupported values.

To use the Combobox with the keyboard you can use the following keys:

  • ArrowDown to navigate to the next option
  • ArrowUp to navigate to the previous option
  • Backspace to remove the last selected option
  • Enter to select an option
  • Escape to close the Combobox

By default the Combobox supports selecing multiple options. Each option is visually represented as a tag.

  • id - The id of the Combobox, used for internal association between elements and for accessibility. Must be unique.
  • selectedItems - The selected items.
  • onChange - Function that will be called when the selected items change.
  • items - An array of objects with the following properties:
    • id - A unique identifier for the item.
    • label - The text that will be displayed and used for filtering.
import { Combobox, Flex, Text, ThemeSpace } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItems, setSelectedItems] = useState([{ id: "3", label: "Cat" }]);
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="animals-label">Pick some animals</Text>
<Combobox
id="animals-combobox"
aria-labelledby="animals-label"
selectedItems={selectedItems}
onChange={setSelectedItems}
items={[
{
id: "1",
label: "Ant",
},
{
id: "2",
label: "Bear",
},
{
id: "3",
label: "Cat",
},
{
id: "4",
label: "Dog",
},
{
id: "5",
label: "Elephant",
},
{
id: "6",
label: "Fox",
},
{
id: "7",
label: "Giraffe",
},
{
id: "8",
label: "Horse",
},
{
id: "9",
label: "Iguana",
},
{
id: "10",
label: "Jaguar",
},
{
id: "11",
label: "Kangaroo",
},
{
id: "12",
label: "Lion",
},
{
id: "13",
label: "Monkey",
},
]}
/>
</Flex>
);
}

To collapse selected items, you can set collapsed on the Combobox.

import { Combobox, Flex, Text, ThemeSpace } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItem, setSelectedItem] = useState([
{
id: "1",
label: "Michael Scott",
},
{
id: "2",
label: "Dwight Schrute",
},
{
id: "3",
label: "Jim Halpert",
},
]);
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="people-label">Select people</Text>
<Combobox
id="collapsed-combobox"
collapsed
aria-labelledby="people-label"
selectedItems={selectedItem}
onChange={setSelectedItem}
items={[
{
id: "1",
label: "Michael Scott",
},
{
id: "2",
label: "Dwight Schrute",
},
{
id: "3",
label: "Jim Halpert",
},
{
id: "4",
label: "Toby Flenderson",
},
]}
/>
</Flex>
);
}

You can disable the Combobox by setting the isDisabled prop to true. This will prevent the user from interacting with the Combobox.

import { Combobox, Flex, Text, ThemeSpace } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItem, setSelectedItem] = useState([{ id: "2", label: "CEO" }]);
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="role-label">Role</Text>
<Combobox
id="disabled-combobox"
collapsed
isDisabled
aria-labelledby="role-label"
selectedItems={selectedItem}
onChange={setSelectedItem}
items={[
{
id: "1",
label: "Founder",
},
{
id: "2",
label: "CEO",
},
{
id: "3",
label: "CTO",
},
{
id: "4",
label: "CFO",
},
]}
/>
</Flex>
);
}

You can also set the isReadOnly prop to true to make the Combobox read-only, which will allow the user to see the selected items but not change them.

import { Combobox, Flex, Text, ThemeSpace } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItem, setSelectedItem] = useState([
{ id: "1", label: "Dairy" },
{ id: "2", label: "Seafood" },
]);
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="allergy-label">Allergies</Text>
<Combobox
id="readonly-combobox"
collapsed
isReadOnly
aria-labelledby="allergy-label"
selectedItems={selectedItem}
onChange={setSelectedItem}
items={[
{
id: "1",
label: "Dairy",
},
{
id: "2",
label: "Seafood",
},
{
id: "3",
label: "Nuts",
},
{
id: "4",
label: "Gluten",
},
]}
/>
</Flex>
);
}

You can mark the Combobox as invalid by setting the isInvalid prop to true. This is commonly used with form validation to indicate that the user’s selection is not valid. The Combobox should be used together with FormControl and FormErrorMessage to provide proper validation feedback.

import {
Combobox,
Flex,
FormControl,
FormErrorMessage,
FormLabel,
ThemeSpace,
type ComboboxItem,
} from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItems, setSelectedItems] = useState<ComboboxItem[]>([]);
// Example validation: at least one item must be selected
const isInvalid = selectedItems.length === 0;
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<FormControl isInvalid={isInvalid} isRequired id="fruits-combobox">
<FormLabel>Required: Pick at least one fruit</FormLabel>
<Combobox
id="fruits-combobox"
selectedItems={selectedItems}
onChange={setSelectedItems}
placeholder="Choose fruits..."
items={[
{
id: "1",
label: "Apple",
},
{
id: "2",
label: "Banana",
},
{
id: "3",
label: "Cherry",
},
{
id: "4",
label: "Date",
},
{
id: "5",
label: "Elderberry",
},
{
id: "6",
label: "Fig",
},
{
id: "7",
label: "Grape",
},
{
id: "8",
label: "Honeydew",
},
]}
/>
<FormErrorMessage>Please select at least one fruit</FormErrorMessage>
</FormControl>
</Flex>
);
}

To limit the Combobox to a single selection, you can set the isSingleSelect prop to true.

import { Combobox, Flex, Text, ThemeSpace, type ComboboxItem } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItem, setSelectedItem] = useState<ComboboxItem[]>([]);
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="animal-label">Pick a single animal</Text>
<Combobox
id="animal-combobox"
aria-labelledby="animal-label"
isSingleSelect
selectedItems={selectedItem}
onChange={setSelectedItem}
items={[
{
id: "1",
label: "Ant",
},
{
id: "2",
label: "Bear",
},
{
id: "3",
label: "Cat",
},
{
id: "4",
label: "Dog",
},
{
id: "5",
label: "Elephant",
},
{
id: "6",
label: "Fox",
},
{
id: "7",
label: "Giraffe",
},
]}
/>
</Flex>
);
}

By default the search string is cleared when an item is selected. In a multiselect Combobox you can set keepSearchAfterSelect to keep the search string after selecting an item, allowing the user to select several items from the same search result. When folderStyle is set to "stacked" the search string is kept by default. Try searching for “bea” in the example below and selecting an item.

import { Combobox, Flex, Text, ThemeSpace, type ComboboxItem } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItems, setSelectedItems] = useState<ComboboxItem[]>([]);
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="keep-search-label">Pick some animals</Text>
<Combobox
id="keep-search-combobox"
aria-labelledby="keep-search-label"
keepSearchAfterSelect
selectedItems={selectedItems}
onChange={setSelectedItems}
items={[
{
id: "1",
label: "Bear",
},
{
id: "2",
label: "Bearded dragon",
},
{
id: "3",
label: "Beaver",
},
{
id: "4",
label: "Cat",
},
{
id: "5",
label: "Dog",
},
{
id: "6",
label: "Elephant",
},
]}
/>
</Flex>
);
}

The Combobox should always be associated with a label. Either use the aria-labelledby property to associate the Combobox with a label (see examples above), or use the label property on the Combobox itself if you want to show a label inline (see example below). Use aria-label if you don’t have a visually displayed label.

import { Combobox, Flex, ThemeSpace } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [size, setSize] = useState([{ id: "2", label: "S" }]);
const [brand, setBrand] = useState([{ id: "1", label: "Nike" }]);
return (
<Flex gap={ThemeSpace.Medium}>
<Combobox
id="size-combobox"
collapsed
label="Size"
isSingleSelect
selectedItems={size}
onChange={setSize}
items={[
{
id: "1",
label: "XS",
},
{
id: "2",
label: "S",
},
{
id: "3",
label: "M",
},
]}
/>
<Combobox
id="brand-combobox"
collapsed
label="Brand"
isSingleSelect
selectedItems={brand}
onChange={setBrand}
items={[
{
id: "1",
label: "Nike",
},
{
id: "2",
label: "Puma",
},
{
id: "3",
label: "Adidas",
},
]}
/>
</Flex>
);
}

A selected item is always visually represented in a tag component, and therefore is limited in the way you can customize the appearance. Tge Combobox supports the following additional properties on each item:

  • prepend - A component that will be displayed before the label.
  • variant - Tag color variant to use for the item.
import { Combobox, Flex, Text, ThemeSpace, type ComboboxItem } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItem, setSelectedItem] = useState<ComboboxItem[]>([]);
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="status-label">Pick a status</Text>
<Combobox
id="status-combobox"
aria-labelledby="status-label"
isSingleSelect
selectedItems={selectedItem}
onChange={setSelectedItem}
items={[
{
id: "1",
label: "Out of stock",
variant: "error",
},
{
id: "2",
label: "In stock",
variant: "success",
},
{
id: "3",
label: "Low stock",
variant: "warning",
},
]}
/>
</Flex>
);
}
import {
Combobox,
Flex,
SlightlySadIcon,
SmileIcon,
SweatIcon,
Text,
ThemeSpace,
type ComboboxItem,
} from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItem, setSelectedItem] = useState<ComboboxItem[]>([]);
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="mood-label">Pick a mood</Text>
<Combobox
id="mood-combobox"
aria-labelledby="mood-label"
isSingleSelect
selectedItems={selectedItem}
onChange={setSelectedItem}
items={[
{
id: "1",
label: "Happy",
prepend: <SmileIcon mx={ThemeSpace.XSmall} />,
},
{
id: "2",
label: "Slightly Sad",
prepend: <SlightlySadIcon mx={ThemeSpace.XSmall} />,
},
{
id: "3",
label: "Anxious",
prepend: <SweatIcon mx={ThemeSpace.XSmall} />,
},
]}
/>
</Flex>
);
}
import { Combobox, Flex, Text, ThemeSpace, type ComboboxItem } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItems, setSelectedItems] = useState<ComboboxItem[]>([
{
id: "2",
label: "Banana",
variant: "warning",
prepend: <>🍌</>,
},
{
id: "3",
label: "Cherry",
variant: "error",
prepend: <>🍒</>,
},
]);
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="controlled-label">Pick some fruits</Text>
<Combobox
id="controlled-combobox"
aria-labelledby="controlled-label"
onChange={setSelectedItems}
selectedItems={selectedItems}
items={[
{
id: "1",
label: "Apple",
variant: "success",
prepend: <>🍏</>,
},
{
id: "2",
label: "Banana",
variant: "warning",
prepend: <>🍌</>,
},
{
id: "3",
label: "Cherry",
variant: "error",
prepend: <>🍒</>,
},
{
id: "4",
label: "Grapes",
variant: "info",
prepend: <>🍇</>,
},
]}
/>
</Flex>
);
}
import { Combobox, Flex, Text, ThemeSpace, type ComboboxItem } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItem, setSelectedItem] = useState<ComboboxItem[]>([]);
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="append-combobox-label">Pick an animal</Text>
<Combobox
id="append-combobox"
aria-labelledby="append-combobox-label"
isSingleSelect
selectedItems={selectedItem}
onChange={setSelectedItem}
items={[
{
id: "1",
label: "Ant",
append: <>🐜</>,
},
{
id: "2",
label: "Bear",
append: <>🐻</>,
},
{
id: "3",
label: "Cat but the text is very long on this one",
append: <>🐱</>,
},
{
id: "4",
label: "Dog",
append: <>🐶</>,
},
{
id: "5",
label: "Elephant",
append: <>🐘</>,
},
]}
/>
</Flex>
);
}

Items can be displayed as only text with asPlainText. This is only applicable together with isSingleSelect.

import { Combobox, Flex, SmileIcon, Text, ThemeSpace, type ComboboxItem } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItems, setSelectedItems] = useState<ComboboxItem[]>([]);
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="plain-text-label">Pick an animal</Text>
<Combobox
asPlainText
isSingleSelect
id="plain-text-combobox"
aria-labelledby="plain-text-label"
onChange={setSelectedItems}
selectedItems={selectedItems}
items={[
{
id: "1",
label: "Giraffe",
},
{
id: "2",
prepend: <SmileIcon />,
label: "Cat",
},
{
id: "3",
label: "Dolphin",
},
{
id: "4",
label: "Capybara",
},
]}
/>
</Flex>
);
}

The Combobox has a footer-slot that can be used to display information about the list items, or be used for custom actions that relates to your Combobox. To use it, provide content to the footerContent prop.

import {
Button,
Combobox,
Flex,
HistoryIcon,
Text,
ThemeSpace,
type ComboboxItem,
} from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItems, setSelectedItems] = useState<ComboboxItem[]>([]);
const onReset = () => {
setSelectedItems([
{
id: "1",
label: "Dragon",
variant: "success",
prepend: <>🐉</>,
},
{
id: "3",
label: "Unicorn",
variant: "primary",
prepend: <>🦄</>,
},
]);
};
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="footer-label">Pick some mythical creatures</Text>
<Combobox
id="footer-combobox"
aria-labelledby="footer-label"
onChange={setSelectedItems}
selectedItems={selectedItems}
items={[
{
id: "1",
label: "Dragon",
variant: "success",
prepend: <>🐉</>,
},
{
id: "2",
label: "Phoenix",
variant: "warning",
prepend: <>🔥</>,
},
{
id: "3",
label: "Unicorn",
variant: "primary",
prepend: <>🦄</>,
},
{
id: "4",
label: "Mermaid",
variant: "info",
prepend: <>🧜‍♀️</>,
},
]}
footerContent={
<Button onClick={onReset} variant="ghost" size="sm" leftIcon={HistoryIcon}>
Reset to the coolest choice
</Button>
}
/>
</Flex>
);
}

This example demonstrates a combobox configured with a small input field while using the popoverWidth prop to display a wider dropdown.

import { Combobox, Flex, Text, ThemeSpace, type ComboboxItem } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItem, setSelectedItem] = useState<ComboboxItem[]>([]);
const commits = [
{
id: "1",
label: "8030ad7766ed0853d4b3dfa8f09f19988b2f79ed",
},
{
id: "2",
label: "2eb08e4d31b07de6852af42a1a59d370dc0d2b9f",
},
{
id: "3",
label: "d9f4928eca262b2b94ec8109889bafe4b32b360b",
},
];
return (
<Flex w="105px" gap={ThemeSpace.Medium} direction="column">
<Text id="commit-label">Commit hash</Text>
<Combobox
asPlainText
id="commit-combobox"
aria-labelledby="commit-label"
isSingleSelect
selectedItems={selectedItem}
onChange={setSelectedItem}
items={commits}
popoverWidth={400}
popoverPlacement="bottom-start"
/>
</Flex>
);
}

This example demonstrates a combobox configured to only display a limited number of items in the dropdown.

import { Combobox, Flex, Text, ThemeSpace, type ComboboxItem } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItem, setSelectedItem] = useState<ComboboxItem[]>([]);
const animals = [
{
id: "1",
label: "Ant",
},
{
id: "2",
label: "Bear",
},
{
id: "3",
label: "Cat",
},
{
id: "4",
label: "Dog",
},
{
id: "5",
label: "Elephant",
},
{
id: "6",
label: "Fox",
},
{
id: "7",
label: "Giraffe",
},
{
id: "8",
label: "Horse",
},
{
id: "9",
label: "Iguana",
},
{
id: "10",
label: "Jaguar",
},
{
id: "11",
label: "Kangaroo",
},
{
id: "12",
label: "Lion",
},
{
id: "13",
label: "Monkey",
},
];
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="limited-label">Animal</Text>
<Combobox
asPlainText
id="limited-combobox"
aria-labelledby="limited-label"
isSingleSelect
selectedItems={selectedItem}
onChange={setSelectedItem}
items={animals}
maxVisibleItems={4}
/>
</Flex>
);
}

This example demonstrates a combobox configured to show folders in a grouped configuration.

import { Combobox, Flex, Text, ThemeSpace, type ComboboxItem } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItems, setSelectedItems] = useState<ComboboxItem[]>([]);
const numbers = [
{
id: "no number",
label: "No number",
children: [
{
id: "dislike",
label: "I don't like numbers",
},
{
id: "like",
label: "I like them all equally",
},
],
isSelectableParent: true,
},
{
id: "Single digit numbers",
label: "Single digit numbers",
children: [
{ id: "0", label: "0" },
{ id: "1", label: "1" },
{ id: "2", label: "2" },
{ id: "3", label: "3" },
{ id: "4", label: "4" },
{ id: "5", label: "5" },
{ id: "6", label: "6" },
{ id: "7", label: "7" },
{ id: "8", label: "8" },
{ id: "9", label: "9" },
],
},
{
id: "Special Numbers",
label: "Special Numbers",
children: [
{
id: "pi",
label: "Pi",
},
{
id: "euler",
label: "Euler's number",
},
{
id: "golden",
label: "Golden ratio",
},
],
},
{
id: "other",
label: "I like another number",
},
];
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="numbers">Select your favourite number</Text>
<Combobox
id="numbers-single-select"
placeholder="Number"
isSingleSelect
asPlainText
items={numbers}
selectedItems={selectedItems}
onChange={setSelectedItems}
/>
</Flex>
);
}

This example demonstrates a combobox configured to show folders in a stacked configuration.

import { Combobox, Flex, Text, ThemeSpace, type ComboboxItem } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItems, setSelectedItems] = useState<ComboboxItem[]>([]);
const locations = [
{
id: "uppsala",
label: "Uppsala",
children: [
{
id: "restaurant1",
label: "Pasta Place",
children: [
{
id: "kitchen1",
label: "Kitchen",
},
{
id: "dining1",
label: "Dining Room",
},
],
},
{
id: "hotel1",
label: "Hotel UpAndAway",
children: [
{
id: "reception1",
label: "Reception",
},
{
id: "room-service1",
label: "Room Service",
},
],
},
],
},
{
id: "stockholm",
label: "Stockholm",
children: [
{
id: "restaurant2",
label: "Sushi Spot",
children: [
{
id: "kitchen2",
label: "Kitchen",
},
{
id: "dining2",
label: "Dining Room",
},
],
},
{
id: "hotel2",
label: "Grand Hotel",
children: [
{
id: "reception2",
label: "Reception",
},
{
id: "room-service2",
label: "Room Service",
},
{
id: "spa",
label: "Spa",
children: [
{
id: "massage",
label: "Massage",
},
{
id: "pool",
label: "Pool",
},
],
},
],
},
],
},
];
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="numbers">Select your location</Text>
<Combobox
id="numbers-single-select"
placeholder="Locations"
items={locations}
selectedItems={selectedItems}
onChange={setSelectedItems}
folderStyle="stacked"
isSingleSelect
asPlainText
/>
</Flex>
);
}

This example demonstrates using parentSelectsChildren with a stacked folder style to automatically select or deselect all child items when clicking a parent item.

import { Combobox, Flex, Text, ThemeSpace, type ComboboxItem } from "@caspeco/casper-ui-library";
import { useState } from "react";
export default function Demo() {
const [selectedItems, setSelectedItems] = useState<ComboboxItem[]>([]);
const locations = [
{
id: "uppsala",
label: "Uppsala",
children: [
{
id: "restaurant1",
label: "Pasta Place",
children: [
{
id: "kitchen1",
label: "Kitchen",
},
{
id: "dining1",
label: "Dining Room",
},
],
},
{
id: "hotel1",
label: "Hotel UpAndAway",
children: [
{
id: "reception1",
label: "Reception",
},
{
id: "room-service1",
label: "Room Service",
},
],
},
],
},
{
id: "stockholm",
label: "Stockholm",
children: [
{
id: "restaurant2",
label: "Sushi Spot",
children: [
{
id: "kitchen2",
label: "Kitchen",
},
{
id: "dining2",
label: "Dining Room",
},
],
},
{
id: "hotel2",
label: "Grand Hotel",
children: [
{
id: "reception2",
label: "Reception",
},
{
id: "room-service2",
label: "Room Service",
},
{
id: "spa",
label: "Spa",
children: [
{
id: "massage",
label: "Massage",
},
{
id: "pool",
label: "Pool",
},
],
},
],
},
],
},
];
return (
<Flex gap={ThemeSpace.Medium} direction="column" w="300px">
<Text id="numbers">Select your location</Text>
<Combobox
id="parent-select-children"
placeholder="Locations"
items={locations}
selectedItems={selectedItems}
onChange={setSelectedItems}
folderStyle="stacked"
parentSelectsChildren
/>
</Flex>
);
}