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:
ArrowDownto navigate to the next optionArrowUpto navigate to the previous optionBackspaceto remove the last selected optionEnterto select an optionEscapeto close the Combobox
Base example
Section titled “Base example”By default the Combobox supports selecing multiple options. Each option is visually represented as a tag.
Required props
Section titled “Required props”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> );}Collapsed
Section titled “Collapsed”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> );}Disabled and read-only
Section titled “Disabled and read-only”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> );}Invalid state
Section titled “Invalid state”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> );}Single selection
Section titled “Single selection”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> );}Keep search after select
Section titled “Keep search after select”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> );}Labels
Section titled “Labels”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> );}Item appearance
Section titled “Item appearance”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.
Items with variant example
Section titled “Items with variant example”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> );}Items with prepend example
Section titled “Items with prepend example”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> );}Items with prepend and variant example
Section titled “Items with prepend and variant example”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> );}Items with append example
Section titled “Items with append example”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> );}As plain text
Section titled “As plain text”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> );}Footer
Section titled “Footer”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> );}Popover width
Section titled “Popover width”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> );}Limiting rendered items
Section titled “Limiting rendered items”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> );}Folders
Section titled “Folders”Grouped
Section titled “Grouped”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> );}Stacked
Section titled “Stacked”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> );}Parent selects child items
Section titled “Parent selects child items”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> );}