DocsBlog
  • 1.3.0

  • light

    dark

    system

    Switch mode
  • Cerberus

    Acheron

    Elysium

Get Started
Components
Data Grid
Signals
Styling
Theming

Concepts

OverviewCompositionCerberus ContextTesting

Layout

Aspect RatioBleedBoxCenterContainerDividerFlexFloatGridGroupLink OverlayScrollableStackWrap

Components

AccordionAdmonitionAvatarButtonNewCarouselCheckboxClipboardCollapsibleComboboxConfirm ModalCTAModalDate PickerDialogFieldFieldsetFile UploaderIcon ButtonInputLoading StatesMenuNotificationsNumber InputPaginationPin InputPopoverProgressPrompt ModalRadioRatingSelectSplit ButtonSwitchTableTabsTagTextTextareaToggleTooltip

Utilities

Client OnlyDownload TriggerEnvironmentFeature FlagsFocus TrapForFormat ByteFormat NumberFormat Relative TimeFrameHighlightJSON Tree ViewLocaleLocal StoragePortalPresenceShowsplitPropsTheme

Combobox

A filterable select component that allows users to search and select options.

  • npm
  • source
  • recipe
  • Ark
import {
  Combobox,
  ComboItemWithIndicator,
  ComboItemText,
} from '@cerberus/react'

Usage

The Combobox is an abracted API that combines multiple primitives into a single root component. You combine this with the useStatefulCollection hook to create a filterable select component.

With Start Icon

To add an icon to the start position of the input, use the startIcon prop to pass in your icon of choice.

Sizes

The Combobox component supports sizes: sm to lg. The default is md.

With Field

Wrap the Combobox component with the Field component to add additional functionality such as error and helper text when using it in a form.

Context

Access the combobox's state with ComboboxContext or the useComboboxContext hook—useful for displaying the selected value or building custom UI.

Note

Since combobox is an abstracted API, you'll need to build your own to obtain the context as we do in the example below.

Grouped Items

To group items, use the ComboItemGroup component (not ComboboxItemGroup).

Async Loading

Combine the collection with a query to load data asynchronously with signal-based speed.

Highlight Matching Text

Combine the ComboItemText component with the Highlight component to highlight matching text in the dropdown.

Creatable

Allow users to create new options when their search doesn't match any existing items. This is useful for tags, categories, or other custom values.

Customizing

Guides

Router Links

Customize the navigate prop on Combobox to integrate with your router. Using Tanstack Router:

import { Combobox } from '@cerberus/react'
import { useNavigate } from '@tanstack/react-router'

function Demo() {
  const navigate = useNavigate()
  return (
    <Combobox
      navigate={(e) => {
        navigate({ to: e.node.href })
      }}
    >
      {/* ... */}
    </Combobox>
  )
}

Custom Objects

By default, the combobox collection expects an array of objects with label and value properties. In some cases, you may need to deal with custom objects.

Use the itemToString and itemToValue props to map the custom object to the required interface.

const items = [
  { country: 'United States', code: 'US', flag: '🇺🇸' },
  { country: 'Canada', code: 'CA', flag: '🇨🇦' },
  { country: 'Australia', code: 'AU', flag: '🇦🇺' },
  // ...
]

const { collection } = useListCollection({
  initialItems: items,
  itemToString: (item) => item.country,
  itemToValue: (item) => item.code,
})

Type Safety

The ComboboxRootProps type enables you to create typed wrapper components that maintain full type safety for collection items.

const Combobox = (props: ComboboxRootProps) => {
  return <CerbyCombobox {...props}>{/* ... */}</CerbyCombobox>
}

Large Datasets

The recommended way of managing large lists is to use the limit property on the useListCollection hook. This will limit the number of rendered items in the DOM to improve performance.

const { collection } = useListCollection({
  initialItems: items,
  limit: 10,
})

Available Size

The following css variables are exposed to the ComboboxPositioner which you can use to style the ComboboxContent.

/* width of the combobox control */
--reference-width: <pixel-value>;
/* width of the available viewport */
--available-width: <pixel-value>;
/* height of the available viewport */
--available-height: <pixel-value>;

For example, if you want to make sure the maximum height doesn't exceed the available height, you can use the following:

[data-scope='combobox'][data-part='content'] {
  max-height: calc(var(--available-height) - 100px);
}

Primitives

You can utilize the primitive components or the css prop to customize the Combobox.

ComponentDescription
ComboboxRootThe context provider for the combobox family
ComboboxLabelThe label that appears above the combobox input
ComboboxControlThe wrapper to the combobox trigger that opens the dropdown
ComboboxInputThe input field of the combobox
ComboboxTriggerhe trigger that opens the dropdown
ComboboxClearTriggerThe trigger that clears the selected value
ComboboxPositionerThe wrapper that positions the dropdown
ComboboxContentThe content of the dropdown (i.e. the container itself)
ComboboxItemGroupThe group of options in the dropdown
ComboboxItemGroupLabelThe label for the group of options
ComboboxItemThe option in the dropdown
ComboboxItemTextThe text label of the option
ComboboxItemIndicatorThe indicator shown when the option is selected

API

Combobox

The Combobox component is an abstraction of our primitives and accepts the following props:

NameDefaultDescription
sizemdThe size of the combobox.
startIconThe icon to display at the start of the input.

The Combobox component also accepts all the props of the ComboboxRoot primitive props

ItemWithIndicator

The ItemWithIndicator component is an abstraction of our primitives and accepts all the props of the ComboboxItem primitive props

ComboItemGroup

The ComboItemGroup component is an abstraction of our primitives and accepts the following props:

NameDefaultDescription
labelThe label of the group.

The ComboItemGroup component is an abstraction of our primitives and accepts all the props of the ComboboxItemGroup primitive props

Primitive API

Root Props

PropTypeRequiredDescription
collectionListCollection<T>YesThe collection of items
allowCustomValuebooleanNoWhether to allow typing custom values in the input
alwaysSubmitOnEnterbooleanNoWhether to always submit on Enter key press, even if popup is open.
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.
autoFocusbooleanNoWhether to autofocus the input on mount
closeOnSelectbooleanNoWhether to close the combobox when an item is selected.
autoFocusbooleanNoWhether to autofocus the input on mount
closeOnSelectbooleanNoWhether to close the combobox when an item is selected.
compositebooleanNoWhether the combobox is a composed with other composite widgets like tabs
defaultHighlightedValuestringNoThe initial highlighted value of the combobox when rendered. Use when you don't need to control the highlighted value of the combobox.
defaultInputValuestringNoThe initial value of the combobox's input when rendered. Use when you don't need to control the value of the combobox's input.
defaultOpenbooleanNoThe initial open state of the combobox when rendered. Use when you don't need to control the open state of the combobox.
defaultValuestring[]NoThe initial value of the combobox's selected items when rendered. Use when you don't need to control the value of the combobox's selected items.
disabledbooleanNoWhether the combobox is disabled
disableLayerbooleanNoWhether to disable registering this a dismissable layer
formstringNoThe associate form of the combobox.
highlightedValuestringNoThe controlled highlighted value of the combobox
idstringNoThe unique identifier of the machine.
idsPartial<Parts>NoThe ids of the elements in the combobox. Useful for composition.
immediatebooleanNoWhether to synchronize the present change immediately or defer it to the next frame
inputBehavior'none' | 'autohighlight' | 'autocomplete'NoDefines the auto-completion behavior of the combobox. autohighlight: The first focused item is highlighted as the user types, autocomplete: Navigating the listbox with the arrow keys selects the item and the input is updated
inputValuestringNoThe controlled value of the combobox's input
invalidbooleanNoWhether the combobox is invalid
lazyMountbooleanNoWhether to enable lazy mounting
loopFocusbooleanNoWhether to loop the keyboard navigation through the items
multiplebooleanNoWhether to allow multiple selection. Good to know: When multiple is true, the selectionBehavior is automatically set to clear. It is recommended to render the selected items in a separate container.
namestringNoThe name attribute of the combobox's input. Useful for form submission
navigate(details: NavigateDetails) => voidNoFunction to navigate to the selected item
onExitCompleteVoidFunctionNoFunction called when the animation ends in the closed state
onFocusOutside(event: FocusOutsideEvent) => voidNoFunction called when the focus is moved outside the component
onHighlightChange(details: HighlightChangeDetails<T>) => voidNoFunction called when an item is highlighted using the pointer or keyboard navigation.
onInputValueChange(details: InputValueChangeDetails) => voidNoFunction called when the input's value changes
onInteractOutside(event: InteractOutsideEvent) => voidNoFunction called when an interaction happens outside the component
onOpenChange(details: OpenChangeDetails) => voidNoFunction called when the popup is opened
onPointerDownOutside(event: PointerDownOutsideEvent) => voidNoFunction called when the pointer is pressed down outside the component
onSelect(details: SelectionDetails) => voidNoFunction called when an item is selected
onValueChange(details: ValueChangeDetails<T>) => voidNoFunction called when a new item is selected
openbooleanNoThe controlled open state of the combobox
openOnChangebooleanNoWhether to show the combobox when the input value changes
openOnClickbooleanNoWhether to open the combobox popup on initial click on the input
openOnKeyPressbooleanNoWhether to open the combobox on arrow key press
placeholderstringNoThe placeholder text of the combobox's input
positioningPositioningOptionsNoThe positioning options to dynamically position the menu
presentbooleanNoWhether the node is present (controlled by the user)
readOnlybooleanNoWhether the combobox is readonly. This puts the combobox in a "non-editable" mode but the user can still interact with it
requiredbooleanNoWhether the combobox is required
scrollToIndexFn(details: ScrollToIndexDetails) => voidNoFunction to scroll to a specific index
selectionBehavior'clear' | 'replace' | 'preserve'NoThe behavior of the combobox input when an item is selected. replace: The selected item string is set as the input value, clear: The input value is cleared, preserve: The input value is preserved
skipAnimationOnMountbooleanNoWhether to allow the initial presence animation.
translationsIntlTranslationsNoSpecifies the localized strings that identifies the accessibility elements and their states
unmountOnExitbooleanNoWhether to unmount on exit.
value | string[]NoNoThe controlled value of the combobox's selected items

Root Data Attributes:

AttributeValue
[data-scope]combobox
[data-part]root
[data-invalid]Present when invalid
[data-readonly]Present when read-only

ClearTrigger Props:

PropTypeRequiredDescription
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.

ClearTrigger Data Attributes:

AttributeValue
[data-scope]combobox
[data-part]clear-trigger
[data-invalid]Present when invalid

Content Props:

PropTypeRequiredDescription
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.

Content Data Attributes:

AttributeValue
[data-scope]combobox
[data-part]content
[data-state]"open" | "closed"
[data-nested]listbox
[data-has-nested]listbox
[data-placement]The placement of the content
[data-empty]Present when the content is empty

Content CSS Variables:

VariableDescription
--layer-indexThe index of the dismissable in the layer stack
--nested-layer-countThe number of nested comboboxs

Control Props:

PropTypeRequiredDescription
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.

Control Data Attributes:

AttributeValue
[data-scope]combobox
[data-part]control
[data-state]"open" | "closed"
[data-focus]Present when focused
[data-disabled]Present when disabled
[data-invalid]Present when invalid

Input Props:

PropTypeRequiredDescription
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.

Input Data Attributes:

AttributeValue
[data-scope]combobox
[data-part]input
[data-invalid]Present when invalid
[data-autofocus]
[data-state]"open" | "closed"

ItemGroupLabel Props:

PropTypeRequiredDescription
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.

ItemGroup Props:

PropTypeRequiredDescription
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.

ItemGroup Data Attributes:

AttributeValue
[data-scope]combobox
[data-part]item-group
[data-empty]Present when the content is empty

ItemIndicator Props:

PropTypeRequiredDescription
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.

ItemIndicator Data Attributes:

AttributeValue
[data-scope]combobox
[data-part]item-indicator
[data-state]"checked" | "unchecked"

Item Props:

PropTypeRequiredDescription
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.
itemanyNoThe item to render
persistFocusbooleanNoWhether hovering outside should clear the highlighted state

Item Data Attributes:

AttributeValue
[data-scope]combobox
[data-part]item
[data-highlighted]Present when highlighted
[data-state]"checked" | "unchecked"
[data-disabled]Present when disabled
[data-value]The value of the item

ItemText Props:

PropTypeRequiredDescription
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.

ItemText Data Attributes:

AttributeValue
[data-scope]combobox
[data-part]item-text
[data-state]"checked" | "unchecked"
[data-disabled]Present when disabled
[data-highlighted]Present when highlighted

Label Props:

PropTypeRequiredDescription
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.

Label Data Attributes:

AttributeValue
[data-scope]combobox
[data-part]label
[data-readonly]Present when read-only
[data-disabled]Present when disabled
[data-invalid]Present when invalid
[data-required]Present when required
[data-focus]Present when focused

List Props:

PropTypeRequiredDescription
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.

List Data Attributes:

AttributeValue
[data-scope]combobox
[data-part]list
[data-empty]Present when the content is empty

Positioner Props:

PropTypeRequiredDescription
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.

Positioner CSS Variables:

VariableDescription
--reference-widthThe width of the reference element
--reference-heightThe height of the root
--available-widthThe available width in viewport
--available-heightThe available height in viewport
--xThe x position for transform
--yThe y position for transform
--z-indexThe z-index value
--transform-originThe transform origin for animations

RootProvider Props:

PropTypeRequiredDescription
valueUseComboboxReturn<T>Yes
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.
immediatebooleanNoWhether to synchronize the present change immediately or defer it to the next frame
lazyMountbooleanNoWhether to enable lazy mounting
onExitCompleteVoidFunctionNoFunction called when the animation ends in the closed state
presentbooleanNoWhether the node is present (controlled by the user)
skipAnimationOnMountbooleanNoWhether to allow the initial presence animation.
unmountOnExitbooleanNoWhether to unmount on exit.

Trigger Props:

PropTypeRequiredDescription
asChildbooleanNoUse the provided child element as the default rendered element, combining their props and behavior.
focusablebooleanNoWhether the trigger is focusable

Trigger Data Attributes:

AttributeValue
[data-scope]combobox
[data-part]trigger
[data-state]"open" | "closed"
[data-invalid]Present when invalid
[data-focusable]
[data-readonly]Present when read-only
[data-disabled]Present when disabled

Hooks

useStatefulCollection

The useStatefulCollection function is a utility hook that creates a collection of options and filters the list based on the user input.

Returns

NameDescription
collectionThe collection of options.
filterCharsThe filter value split into an Array of chars.
handleInputChangeThe function to pass to onInputValueChange.

Parts

The ComboboxParts API is an Object containing the full family of components.

Note

It is best to only use the ComboboxParts if you are building a custom solution. Importing Object based components will ship every property it includes into your bundle, regardless if you use it or not.

NameDescription
RootThe ComboboxRoot component which is the Provider for the family.
LabelThe ComboboxLabel component which displays the label.
ControlThe ComboboxControl component which is the container for the visual field.
InputThe ComboboxInput component which is the visual field.
TriggerThe ComboboxTrigger component which is the trigger for the dropdown.
ClearTriggerThe ComboboxClearTrigger component which is the trigger to clear the value.
PositionerThe ComboboxPositioner component which is controls the positioning for the dropdown.
ContentThe ComboboxContent component which is the dropdown itself.
ItemGroupThe ComboboxItemGroup component which is the group of options in the dropdown.
ItemGroupLabelThe ComboboxItemGroupLabel component which is the label for the group of options.
ItemThe ComboboxItem component which is the option in the dropdown.
ItemTextThe ComboboxItemText component which is the text label of the option.
ItemIndicatorThe ComboboxItemIndicator component which displays based on the checked state.

On this page

Loading...

Loading...

Loading...

Loading...

Hades
Persephone
Zeus
Poseidon
Hera
Copy
'use client'

import { Box, VStack } from '@/styled-system/jsx'
import {
  Combobox,
  ComboItemText,
  ComboItemWithIndicator,
  For,
  Text,
  useStatefulCollection,
} from '@cerberus/react'
import { items } from './items'

export function BasicDemo() {
  const { collection, handleInputChange } = useStatefulCollection(items)

  return (
    <Box w="1/2">
      <Combobox
        collection={collection}
        label="Select Relative"
        onInputValueChange={handleInputChange}
        placeholder="Choose option"
      >
        <For
          each={collection.items}
          fallback={
            <VStack paddingBlock="6" w="full">
              <Text textAlign="center" textStyle="label-sm">
                No results found
              </Text>
            </VStack>
          }
        >
          {(item) => (
            <ComboItemWithIndicator key={item.value} item={item}>
              <ComboItemText>{item.label}</ComboItemText>
            </ComboItemWithIndicator>
          )}
        </For>
      </Combobox>
    </Box>
  )
}
Hades
Persephone
Zeus
Poseidon
Hera
Copy
'use client'

import { Box, VStack } from '@/styled-system/jsx'
import { Search } from '@carbon/icons-react'
import {
  Combobox,
  ComboItemText,
  ComboItemWithIndicator,
  For,
  Text,
  useStatefulCollection,
} from '@cerberus/react'
import { items } from './items'

export function StartIconDemo() {
  const { collection, handleInputChange } = useStatefulCollection(items)

  return (
    <Box w="1/2">
      <Combobox
        collection={collection}
        label="Select Relative"
        onInputValueChange={handleInputChange}
        openOnClick
        placeholder="Choose option"
        startIcon={<Search />}
      >
        <For
          each={collection.items}
          fallback={
            <VStack paddingBlock="6" w="full">
              <Text textAlign="center" textStyle="label-sm">
                No results found
              </Text>
            </VStack>
          }
        >
          {(item) => (
            <ComboItemWithIndicator key={item.value} item={item}>
              <ComboItemText>{item.label}</ComboItemText>
            </ComboItemWithIndicator>
          )}
        </For>
      </Combobox>
    </Box>
  )
}
Hades
Persephone
Zeus
Poseidon
Hera
Hades
Persephone
Zeus
Poseidon
Hera
Hades
Persephone
Zeus
Poseidon
Hera
Copy
'use client'

import { Stack, VStack } from '@/styled-system/jsx'
import {
  Combobox,
  ComboboxProps,
  ComboItemText,
  ComboItemWithIndicator,
  For,
  Text,
  useStatefulCollection,
} from '@cerberus/react'
import { items } from './items'
import { sizes } from './meta'

export function SizeDemo() {
  return (
    <Stack gap="md" w="3/4">
      <For each={sizes}>{(size) => <SizeBox key={String(size)} size={size} />}</For>
    </Stack>
  )
}

interface SizeBoxProps<T> {
  size: ComboboxProps<T>['size']
}

export function SizeBox<T>(props: SizeBoxProps<T>) {
  const { collection, handleInputChange } = useStatefulCollection(items)

  return (
    <Combobox
      collection={collection}
      label={`Size: ${props.size}`}
      onInputValueChange={handleInputChange}
      placeholder="Type to search"
      size={props.size}
    >
      <For
        each={collection.items}
        fallback={
          <VStack paddingBlock="6" w="full">
            <Text textAlign="center" textStyle="label-sm">
              No results found
            </Text>
          </VStack>
        }
      >
        {(item) => (
          <ComboItemWithIndicator key={item.value} item={item}>
            <ComboItemText>{item.label}</ComboItemText>
          </ComboItemWithIndicator>
        )}
      </For>
    </Combobox>
  )
}
Hades
Persephone
Zeus
Poseidon
Hera
This is saved to your profile
Copy
'use client'

import { Box, VStack } from '@/styled-system/jsx'
import {
  Combobox,
  ComboItemText,
  ComboItemWithIndicator,
  Field,
  For,
  Text,
  useStatefulCollection,
} from '@cerberus/react'
import { items } from './items'

export function FieldDemo() {
  const { collection, handleInputChange } = useStatefulCollection(items)

  return (
    <Box w="1/2">
      <Field
        label="Select Relative"
        helperText="This is saved to your profile"
        errorText="You must select something."
        required
      >
        <Combobox
          collection={collection}
          onInputValueChange={handleInputChange}
          placeholder="Choose option"
        >
          <For
            each={collection.items}
            fallback={
              <VStack paddingBlock="6" w="full">
                <Text textAlign="center" textStyle="label-sm">
                  No results found
                </Text>
              </VStack>
            }
          >
            {(item) => (
              <ComboItemWithIndicator key={item.value} item={item}>
                <ComboItemText>{item.label}</ComboItemText>
              </ComboItemWithIndicator>
            )}
          </For>
        </Combobox>
      </Field>
    </Box>
  )
}

Selected: None

Hades
Persephone
Zeus
Poseidon
Hera
Copy
'use client'

import { Stack, VStack } from '@/styled-system/jsx'
import { ChevronDown, Close } from '@carbon/icons-react'
import {
  ComboboxParts,
  ComboItemText,
  ComboItemWithIndicator,
  For,
  Text,
  UseComboboxContext,
  useStatefulCollection,
} from '@cerberus/react'
import { items } from './items'

export function ContextDemo() {
  const { collection, handleInputChange } = useStatefulCollection(items)

  return (
    <Stack w="1/2">
      <ComboboxParts.Root
        collection={collection}
        onInputValueChange={handleInputChange}
        placeholder="Choose option"
      >
        <ComboboxParts.Context>
          {(context: UseComboboxContext<(typeof collection.items)[number]>) => (
            <Text mb="md">Selected: {context.valueAsString || 'None'}</Text>
          )}
        </ComboboxParts.Context>

        <ComboboxParts.Label>Select Relative</ComboboxParts.Label>

        <ComboboxParts.Control>
          <ComboboxParts.Input />

          <ComboboxParts.ClearTrigger>
            <Close />
          </ComboboxParts.ClearTrigger>
          <ComboboxParts.Trigger>
            <ChevronDown />
          </ComboboxParts.Trigger>
        </ComboboxParts.Control>

        <ComboboxParts.Positioner>
          <ComboboxParts.Content>
            <For
              each={collection.items}
              fallback={
                <VStack paddingBlock="6" w="full">
                  <Text textAlign="center" textStyle="label-sm">
                    No results found
                  </Text>
                </VStack>
              }
            >
              {(item) => (
                <ComboItemWithIndicator key={item.value} item={item}>
                  <ComboItemText>{item.label}</ComboItemText>
                </ComboItemWithIndicator>
              )}
            </For>
          </ComboboxParts.Content>
        </ComboboxParts.Positioner>
      </ComboboxParts.Root>
    </Stack>
  )
}
The fam
Hades
Persephone
Zeus
Poseidon
Hera
Copy
'use client'

import { Box, VStack } from '@/styled-system/jsx'
import { Search } from '@carbon/icons-react'
import {
  Combobox,
  ComboItemGroup,
  ComboItemText,
  ComboItemWithIndicator,
  For,
  Text,
  useStatefulCollection,
} from '@cerberus/react'
import { items } from './items'

export function GroupedItemsDemo() {
  const { collection, handleInputChange } = useStatefulCollection(items)

  return (
    <Box w="1/2">
      <Combobox
        collection={collection}
        label="Select Relative"
        onInputValueChange={handleInputChange}
        placeholder="Choose option"
        startIcon={<Search />}
      >
        <ComboItemGroup label="The fam">
          <For
            each={collection.items}
            fallback={
              <VStack paddingBlock="6" w="full">
                <Text textAlign="center" textStyle="label-sm">
                  No results found
                </Text>
              </VStack>
            }
          >
            {(item) => (
              <ComboItemWithIndicator key={item.value} item={item}>
                <ComboItemText>{item.label}</ComboItemText>
              </ComboItemWithIndicator>
            )}
          </For>
        </ComboItemGroup>
      </Combobox>
    </Box>
  )
}
...loading
Copy
Hades
Persephone
Zeus
Poseidon
Hera
Copy
'use client'

import { Box, VStack } from '@/styled-system/jsx'
import {
  Combobox,
  ComboboxContext,
  ComboItemText,
  ComboItemWithIndicator,
  For,
  Highlight,
  Text,
  useStatefulCollection,
} from '@cerberus/react'
import { items } from './items'

export function HighlightDemo() {
  const { collection, handleInputChange } = useStatefulCollection(items)

  return (
    <Box w="1/2">
      <Combobox
        collection={collection}
        label="Select Relative"
        onInputValueChange={handleInputChange}
        placeholder="Choose option"
      >
        <For
          each={collection.items}
          fallback={
            <VStack paddingBlock="6" w="full">
              <Text textAlign="center" textStyle="label-sm">
                No results found
              </Text>
            </VStack>
          }
        >
          {(item) => (
            <ComboItemWithIndicator key={item.value} item={item}>
              <ComboItemText>
                <ComboboxContext>
                  {(context) => (
                    <Highlight
                      text={item.label}
                      query={context.inputValue}
                      ignoreCase
                    />
                  )}
                </ComboboxContext>
              </ComboItemText>
            </ComboItemWithIndicator>
          )}
        </For>
      </Combobox>
    </Box>
  )
}
Bug
Feature
Enhancement
Documentation
Copy
Hades
🔥
Persephone
🔥
Zeus
🔥
Poseidon
🔥
Hera
🔥
Copy
'use client'

import { Box, VStack } from '@/styled-system/jsx'
import { ChevronDownOutline } from '@carbon/icons-react'
import {
  ComboboxParts,
  For,
  Portal,
  Text,
  useStatefulCollection,
} from '@cerberus/react'
import { items } from './items'

export function CustomDemo() {
  const { collection, handleInputChange } = useStatefulCollection(items)

  return (
    <Box w="1/2">
      <ComboboxParts.Root
        collection={collection}
        onInputValueChange={handleInputChange}
        transform="skewX(-10deg)"
      >
        <ComboboxParts.Label textTransform="uppercase">
          Custom label
        </ComboboxParts.Label>

        <ComboboxParts.Control>
          <ComboboxParts.Input />

          <ComboboxParts.Trigger>
            <ChevronDownOutline />
          </ComboboxParts.Trigger>
        </ComboboxParts.Control>

        <Portal>
          <ComboboxParts.Positioner>
            <ComboboxParts.Content>
              <For
                each={collection.items}
                fallback={
                  <VStack paddingBlock="6" w="full">
                    <Text textAlign="center" textStyle="label-sm">
                      No results found
                    </Text>
                  </VStack>
                }
              >
                {(item) => (
                  <ComboboxParts.Item key={item.value} item={item}>
                    <ComboboxParts.ItemText fontSize="xl">
                      {item.label}
                    </ComboboxParts.ItemText>
                    <ComboboxParts.ItemIndicator>🔥</ComboboxParts.ItemIndicator>
                  </ComboboxParts.Item>
                )}
              </For>
            </ComboboxParts.Content>
          </ComboboxParts.Positioner>
        </Portal>
      </ComboboxParts.Root>
    </Box>
  )
}
'use client'

import { Box, Square, VStack } from '@/styled-system/jsx'
import { Search } from '@carbon/icons-react'
import {
  ComboItemText,
  ComboItemWithIndicator,
  Combobox,
  ComboboxInputValueChangeDetails,
  For,
  Spinner,
  Text,
  createListCollection,
} from '@cerberus/react'
import { createQuery, useQuery } from '@cerberus/signals'
import { Suspense, useState, useTransition } from 'react'

function useDeferredValue() {
  // Use native React state and transitions for loading state to override Suspsense
  const [inputValue, setInputValue] = useState<string>('')
  const [pending, startTransition] = useTransition()
  return {
    inputValue,
    setInputValue,
    pending,
    startTransition,
  }
}

function AsyncCombobox() {
  const { inputValue, setInputValue, pending, startTransition } = useDeferredValue()
  const data = useQuery(queryList(inputValue))

  const collection = createListCollection<Character>({
    items: data,
    itemToString: (item) => item.name,
    itemToValue: (item) => item.url,
  })

  const handleInputChange = (details: ComboboxInputValueChangeDetails) => {
    if (details.reason === 'input-change') {
      startTransition(() => {
        setInputValue(details.inputValue)
      })
    }
  }

  return (
    <Combobox
      collection={collection}
      label="Select Star Wars Character"
      onInputValueChange={handleInputChange}
      placeholder="Type to search or choose an option"
      startIcon={
        pending ? (
          <Square size="4">
            <Spinner />
          </Square>
        ) : (
          <Search />
        )
      }
    >
      <For
        each={collection.items}
        fallback={
          <VStack paddingBlock="6" w="full">
            <Text textAlign="center" textStyle="label-sm">
              No results found
            </Text>
          </VStack>
        }
      >
        {(item) => (
          <ComboItemWithIndicator key={item.name} item={item}>
            <ComboItemText>{item.name}</ComboItemText>
          </ComboItemWithIndicator>
        )}
      </For>
    </Combobox>
  )
}

export function LoadingDemo() {
  return (
    <Box w="1/2">
      <Suspense fallback="...loading">
        <AsyncCombobox />
      </Suspense>
    </Box>
  )
}

// Factories

interface Character {
  name: string
  height: string
  mass: string
  created: string
  edited: string
  url: string
}

const queryList = createQuery(async (inputValue: string) => {
  try {
    const response = await db.searchCharacters(inputValue)
    return response.results
  } catch (error) {
    console.error(error)
    return []
  }
}, 'queryList')

// API

const db = {
  searchCharacters: async (inputValue: string) => {
    const response = await fetch(
      `https://swapi.py4e.com/api/people/?search=${inputValue ?? ''}`,
    )
    return await response.json()
  },
}
'use client'

import { Box, VStack } from '@/styled-system/jsx'
import { Corn } from '@carbon/icons-react'
import {
  Combobox,
  ComboboxInputValueChangeDetails,
  ComboboxOpenChangeDetails,
  ComboboxValueChangeDetails,
  ComboItemText,
  ComboItemWithIndicator,
  For,
  Show,
  Tag,
  Text,
  useFilter,
  useListCollection,
} from '@cerberus/react'
import { useSignal } from '@cerberus/signals'
import { flushSync } from 'react-dom'

interface Item {
  label: string
  value: string
  __new__?: boolean
}

const NEW_OPTION_VALUE = '[[new]]'

const api = {
  create: (value: string): Item => ({ label: value, value: NEW_OPTION_VALUE }),
  isNew: (value: string) => value === NEW_OPTION_VALUE,
  replaceValue: (values: string[], value: string) => {
    return values.map((v) => (v === NEW_OPTION_VALUE ? value : v))
  },
  getNewOptionData: (inputValue: string): Item => ({
    label: inputValue,
    value: inputValue,
    __new__: true,
  }),
}

export function CreatableDemo() {
  const [selectedValue, setSelectedValue] = useSignal<string[]>([])
  const [inputValue, setInputValue] = useSignal('')

  const { contains } = useFilter({ sensitivity: 'base' })
  const { collection, filter, upsert, update, remove } = useListCollection<Item>({
    initialItems: [
      { label: 'Bug', value: 'bug' },
      { label: 'Feature', value: 'feature' },
      { label: 'Enhancement', value: 'enhancement' },
      { label: 'Documentation', value: 'docs' },
    ],
    filter: contains,
  })

  const isValidNewOption = (inputValue: string) => {
    const exactOptionMatch =
      collection.filter((item) => item.toLowerCase() === inputValue.toLowerCase())
        .size > 0
    return !exactOptionMatch && inputValue.trim().length > 0
  }

  const handleInputChange = ({
    inputValue,
    reason,
  }: ComboboxInputValueChangeDetails) => {
    if (reason === 'input-change' || reason === 'item-select') {
      flushSync(() => {
        if (isValidNewOption(inputValue)) {
          upsert(NEW_OPTION_VALUE, api.create(inputValue))
        } else if (inputValue.trim().length === 0) {
          remove(NEW_OPTION_VALUE)
        }
      })
      filter(inputValue)
    }
    setInputValue(inputValue)
  }

  const handleOpenChange = ({ reason }: ComboboxOpenChangeDetails) => {
    if (reason === 'trigger-click') {
      filter('')
    }
  }

  const handleValueChange = ({ value }: ComboboxValueChangeDetails) => {
    setSelectedValue(api.replaceValue(value, inputValue))
    if (value.includes(NEW_OPTION_VALUE)) {
      console.log('New Option Created', inputValue)
      update(NEW_OPTION_VALUE, api.getNewOptionData(inputValue))
    }
  }

  return (
    <Box w="1/2">
      <Combobox
        allowCustomValue
        collection={collection}
        label="Select Relative"
        onOpenChange={handleOpenChange}
        onInputValueChange={handleInputChange}
        onValueChange={handleValueChange}
        placeholder="Choose option"
        value={selectedValue}
      >
        <For
          each={collection.items}
          fallback={
            <VStack paddingBlock="6" w="full">
              <Text textAlign="center" textStyle="label-sm">
                No results found
              </Text>
            </VStack>
          }
        >
          {(item) => (
            <ComboItemWithIndicator key={item.value} item={item}>
              <Show
                when={api.isNew(item.value)}
                fallback={<ComboItemText>{item.label}</ComboItemText>}
              >
                {() => (
                  <ComboItemText textStyle="label-sm">
                    + Create &quot;{item.label}&quot;
                  </ComboItemText>
                )}
              </Show>

              <Show when={item.__new__}>
                {() => (
                  <Tag palette="success" usage="outlined">
                    New <Corn />
                  </Tag>
                )}
              </Show>
            </ComboItemWithIndicator>
          )}
        </For>
      </Combobox>
    </Box>
  )
}

On this page

  • Usage
  • With Start Icon
  • Sizes
  • With Field
  • Context
  • Grouped Items
  • Async Loading
  • Highlight Matching Text
  • Creatable
  • Customizing
  • Guides
  • Router Links
  • Custom Objects
  • Type Safety
  • Large Datasets
  • Available Size
  • Primitives
  • API
  • Combobox
  • ItemWithIndicator
  • ComboItemGroup
  • Primitive API
  • Root Props
  • Root Data Attributes:
  • ClearTrigger Props:
  • Content Props:
  • Control Props:
  • Input Props:
  • ItemGroup Props:
  • ItemIndicator Props:
  • Item Props:
  • ItemText Props:
  • Label Props:
  • List Props:
  • Positioner Props:
  • RootProvider Props:
  • Trigger Props:
  • Hooks
  • useStatefulCollection
  • Parts
  • Edit this page on Github

No results found