Skip to main content

Combobox

A fully WCAG-compliant combobox component for selecting options with support for icons, keyboard navigation, and controlled/uncontrolled usage.


Usage

Basic usage

import { Combobox } from '@nofinite/nui';

<Combobox
options={[
{ label: 'React', value: 'react' },
{ label: 'Next.js', value: 'next.js' },
{ label: 'TypeScript', value: 'ts' },
]}
/>;

With controlled value and change handler

const [value, setValue] = React.useState('');

<Combobox
value={value}
onChange={setValue}
options={[{ label: 'Apple', value: 'apple' }]}
/>;

With custom filtering

<Combobox
options={options}
filter={(input, opt) =>
opt.label.toLowerCase().startsWith(input.toLowerCase())
}
/>

With custom option rendering

<Combobox
options={options}
renderOption={(opt, active) => (
<div style={active ? { fontWeight: 700 } : {}}>{opt.label}</div>
)}
/>

With icons

import { FaSearch } from 'react-icons/fa';

<Combobox
options={options}
leftIcon={<FaSearch />}
placeholder="Search tech..."
/>;

Variants

This component does not have built-in visual variants. Styling can be customized via className or inline styles.


Sizes

The component size is defined via CSS and can be adjusted using custom styles:

<Combobox style={{ width: '300px' }} />

States

<Combobox disabled />
<Combobox options={[]} emptyMessage="Nothing found" />
  • disabled Prevents user interaction and visually disables the input.

  • emptyMessage Displayed when no filtered options are available.


Native Props / Composition

Supports forwarding native HTML input attributes:

<Combobox
aria-label="Choose a framework"
onFocus={() => console.log('focused')}
className="my-combobox"
/>

Props

PropTypeDefaultDescription
optionsComboboxOption[]List of selectable options
valuestringControlled selected value
defaultValuestringInitial value for uncontrolled usage
onChange(value: string) => voidCallback when value changes
placeholderstring"Select..."Placeholder text for input
disabledbooleanfalseDisable user interaction
classNamestring""Custom CSS class
emptyMessagestring"No results found"Message when no options match filter
filter(input: string, option: ComboboxOption) => booleandefault filterCustom filter function for options
leftIconReact.ReactNodeIcon displayed inside input on the left
rightIconReact.ReactNodeIcon displayed inside input on the right
renderOption(option: ComboboxOption, active: boolean) => ReactNodeCustom render function for options
renderOptionIcon(option: ComboboxOption) => ReactNodeCustom icon renderer for listbox options

Behavior Notes

  • Controlled vs uncontrolled:

    • If value is provided, the component behaves as controlled.
    • Otherwise, it manages its own internal state.
  • Keyboard interaction:

    • ArrowDown / ArrowUp to navigate options
    • Enter to select an option
    • Escape to close the dropdown
  • Clicking outside closes the dropdown automatically.

  • Options are filtered using the filter prop or a default label match.


Accessibility

  • Input:

    • Uses role="combobox"
    • Reflects open state via aria-expanded
  • Listbox:

    • Uses role="listbox"
  • Options:

    • Use role="option"
    • Reflect selection via aria-selected
  • Fully keyboard accessible:

    • Tab navigation
    • Arrow keys for traversal
    • Enter and Escape for interaction

Layout

  • Renders as a block-level element (width: 100%) by default.
  • Input wrapper supports left and right icons with automatic padding.
  • Listbox is positioned below the input with a scrollable max height.
<Combobox style={{ width: '100%' }} />

Best Practices

Do

  • Use when a searchable or filterable dropdown is required.
  • Provide meaningful label and value pairs.
  • Use icons sparingly to improve visual clarity.

Don’t

  • Render large datasets without filtering.
  • Omit onChange when using controlled mode.
  • Use without accessible labels or placeholders.