XDSTypeahead@xds/core · Typeahead
Usage
A searchable input for selecting a single item from a large or dynamic dataset. Results appear as the user types, with support for async data sources, debounced search, and custom item rendering. Use it when the option list is too large for a Selector dropdown.Best practices
| Guidance | Practices |
|---|---|
| Do | Provide descriptive placeholder text that hints at what users can search for. |
| Do | Show suggestions on focus when users benefit from seeing popular or recent options before typing. |
| Do | Add a search delay for remote data sources to avoid excessive network requests. |
| Don't | Use for short, static option lists — use Selector for better discoverability. |
| Don't | Use for multi-selection — use Tokenizer instead. |
| Don't | Place multiple Typeaheads adjacent to each other without clear labels differentiating them. |
Import
tsimport {XDSTypeahead} from '@xds/core/Typeahead'
Props
| Prop | Type | Description |
|---|---|---|
labelrequired | string | Accessible label for the input. |
searchSourcerequired | XDSSearchSource<T> | Data source providing search and bootstrap methods for populating the dropdown. |
valuerequired | T | null | Currently selected item, or null if nothing is selected. |
onChangerequired | (item: T | null) => void | Called when the selection changes. |
placeholder | string | Input placeholder text. |
hasEntriesOnFocus | boolean (default: false) | Show bootstrap results on focus before typing. |
hasClear | boolean (default: true) | Show clear button to deselect the current value. |
isDisabled | boolean (default: false) | Disables the input. |
maxMenuItems | number (default: 10) | Maximum number of dropdown items to display. |
status | XDSInputStatus | Validation status object with type and message for error/warning/success states. |
renderItem | (item: T) => ReactNode | Custom render function for dropdown items. Default renders XDSTypeaheadItem. |
isLabelHidden | boolean (default: false) | Visually hides the label while keeping it accessible. |
description | string | Helper text displayed below the label. |
isRequired | boolean (default: false) | Marks the field as required. |
isOptional | boolean (default: false) | Shows an optional indicator on the label. |
labelTooltip | string | Tooltip text shown on the label. |
emptySearchResultsText | string (default: 'No results found') | Text shown when search returns no results. |
hasAutoFocus | boolean (default: false) | Auto-focus the input on mount. |
size | 'sm' | 'md' (default: 'md') | Input and token size. |
debounceMs | number (default: 150) | Debounce delay in ms before triggering search. Set to 0 for synchronous sources. |
onChangeQuery | (query: string) => void | Callback fired when the search query text changes. |
onOpenChange | (isOpen: boolean) => void | Callback when the dropdown opens or closes. |
xstyle | StyleXStyles | StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value — not an inline style object like style={{}}. |
Sub-components
Typeahead is a compound component with 3 sub-components.XDSBaseTypeahead
Unstyled combobox engine providing input, search, keyboard navigation, and dropdown. No wrapper div, no border styling, no token rendering. Used by XDSTypeahead and XDSTokenizer for custom compositions.| Prop | Type | Description |
|---|---|---|
searchSourcerequired | XDSSearchSource<T> | Data source providing search and bootstrap methods. |
valuerequired | T | null | Currently selected item. |
onChangerequired | (item: T | null) => void | Called when the selection changes. |
renderItem | (item: T) => ReactNode | Custom render function for dropdown items. |
placeholder | string (default: 'Search...') | Input placeholder text. |
hasEntriesOnFocus | boolean (default: false) | Show bootstrap results on focus before typing. |
maxMenuItems | number (default: 10) | Maximum dropdown items to display. |
emptySearchResultsText | string (default: 'No results found') | Text shown when search returns no results. |
isDisabled | boolean (default: false) | Whether the input is disabled. |
hasAutoFocus | boolean (default: false) | Auto-focus the input on mount. |
debounceMs | number (default: 150) | Debounce delay in ms before triggering search. Set to 0 for synchronous sources. |
anchorRef | RefObject<HTMLElement | null> | Ref to the anchor element for dropdown positioning. If not provided, the input itself is used. |
inputXStyle | StyleXStyles | Additional StyleX styles for the input element. |
onKeyDown | (e: React.KeyboardEvent<HTMLInputElement>) => void | Additional keydown handler called before internal keyboard navigation. Call e.preventDefault() to skip internal handling. |
onChangeQuery | (query: string) => void | Callback fired when the search query text changes. |
onOpenChange | (isOpen: boolean) => void | Callback when the dropdown opens or closes. |
inputId | string | ID for the input element (for label association). |
ariaDescribedBy | string | Additional aria-describedby IDs. |
XDSTypeahead
Styled typeahead with label, description, validation, and all field features. Wraps XDSBaseTypeahead with XDSField for the primary use case.| Prop | Type | Description |
|---|---|---|
labelrequired | string | Accessible label for the input. |
searchSourcerequired | XDSSearchSource<T> | Data source providing search and bootstrap methods for populating the dropdown. |
valuerequired | T | null | Currently selected item, or null if nothing is selected. |
onChangerequired | (item: T | null) => void | Called when the selection changes. |
placeholder | string | Input placeholder text. |
hasEntriesOnFocus | boolean (default: false) | Show bootstrap results on focus before typing. |
hasClear | boolean (default: true) | Show clear button to deselect the current value. |
isDisabled | boolean (default: false) | Disables the input. |
maxMenuItems | number (default: 10) | Maximum number of dropdown items to display. |
status | XDSInputStatus | Validation status object with type and message for error/warning/success states. |
renderItem | (item: T) => ReactNode | Custom render function for dropdown items. Default renders XDSTypeaheadItem. |
isLabelHidden | boolean (default: false) | Visually hides the label while keeping it accessible. |
description | string | Helper text displayed below the label. |
isRequired | boolean (default: false) | Marks the field as required. |
isOptional | boolean (default: false) | Shows an optional indicator on the label. |
labelTooltip | string | Tooltip text shown on the label. |
emptySearchResultsText | string (default: 'No results found') | Text shown when search returns no results. |
hasAutoFocus | boolean (default: false) | Auto-focus the input on mount. |
size | 'sm' | 'md' (default: 'md') | Input and token size. |
debounceMs | number (default: 150) | Debounce delay in ms before triggering search. Set to 0 for synchronous sources. |
onChangeQuery | (query: string) => void | Callback fired when the search query text changes. |
onOpenChange | (isOpen: boolean) => void | Callback when the dropdown opens or closes. |
xstyle | StyleXStyles | StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value — not an inline style object like style={{}}. |
XDSTypeaheadItem
Default dropdown item renderer for typeahead results. Shows label with optional icon, description, and avatar. Exported for use in custom renderItem implementations.| Prop | Type | Description |
|---|---|---|
itemrequired | XDSSearchableItem | The search result item to render. |
icon | ReactNode | Icon or avatar to display before the label. |
description | string | Description text displayed below the label. |
isDisabled | boolean (default: false) | Whether this item is visually disabled. |
group | string | Group label for grouping items visually. |
Examples
Common configurations, variations, and states.Typeahead — Limited ResultsTypeahead with a capped dropdown showing at most three results.
tsx'use client';import {useState} from 'react';import {XDSTypeahead} from '@xds/core/Typeahead';import type {XDSSearchableItem, XDSSearchSource} from '@xds/core/Typeahead';import {XDSCenter} from '@xds/core/Center';const items: XDSSearchableItem[] = [{id: '1', label: 'United States'},{id: '2', label: 'United Kingdom'},{id: '3', label: 'Canada'},{id: '4', label: 'Australia'},{id: '5', label: 'Germany'},{id: '6', label: 'France'},{id: '7', label: 'Japan'},{id: '8', label: 'Brazil'},];const searchSource: XDSSearchSource = {search: (query: string) =>items.filter(i => i.label.toLowerCase().includes(query.toLowerCase())),bootstrap: () => items.slice(0, 5),};export default function TypeaheadLimitedResults() {const [value, setValue] = useState<XDSSearchableItem | null>(null);return (<XDSCenter width={320}><XDSTypeaheadlabel="Country"placeholder="Search countries..."searchSource={searchSource}value={value}onChange={setValue}hasEntriesOnFocusmaxMenuItems={3}/></XDSCenter>);}
Typeahead — Search FieldSearch input with icon and suggestions on focus.
tsx'use client';import {useState} from 'react';import {XDSTypeahead} from '@xds/core/Typeahead';import type {XDSSearchableItem, XDSSearchSource} from '@xds/core/Typeahead';import {XDSCenter} from '@xds/core/Center';import {MagnifyingGlassIcon} from '@heroicons/react/24/outline';const items: XDSSearchableItem[] = [{id: '1', label: 'Olivia Martin'},{id: '2', label: 'Jackson Lee'},{id: '3', label: 'Isabella Nguyen'},{id: '4', label: 'William Kim'},{id: '5', label: 'Sofia Davis'},{id: '6', label: 'Lucas Brown'},{id: '7', label: 'Mia Wilson'},{id: '8', label: 'Ethan Jones'},];const searchSource: XDSSearchSource = {search: (query: string) =>items.filter(i => i.label.toLowerCase().includes(query.toLowerCase())),bootstrap: () => items.slice(0, 5),};export default function TypeaheadSearchField() {const [value, setValue] = useState<XDSSearchableItem | null>(null);return (<XDSCenter width={320}><XDSTypeaheadlabel="Team member"placeholder="Search people..."searchSource={searchSource}value={value}onChange={setValue}startIcon={MagnifyingGlassIcon}hasEntriesOnFocus/></XDSCenter>);}
Typeahead — With Helper TextTypeahead with a description below the label.
tsx'use client';import {useState} from 'react';import {XDSTypeahead} from '@xds/core/Typeahead';import type {XDSSearchableItem, XDSSearchSource} from '@xds/core/Typeahead';import {XDSCenter} from '@xds/core/Center';const items: XDSSearchableItem[] = [{id: '1', label: 'Engineering'},{id: '2', label: 'Design'},{id: '3', label: 'Marketing'},{id: '4', label: 'Sales'},{id: '5', label: 'Product'},{id: '6', label: 'Finance'},{id: '7', label: 'Legal'},{id: '8', label: 'Operations'},];const searchSource: XDSSearchSource = {search: (query: string) =>items.filter(i => i.label.toLowerCase().includes(query.toLowerCase())),bootstrap: () => items.slice(0, 5),};export default function TypeaheadWithHelperText() {const [value, setValue] = useState<XDSSearchableItem | null>(null);return (<XDSCenter width={320}><XDSTypeaheadlabel="Department"placeholder="Search departments..."searchSource={searchSource}value={value}onChange={setValue}description="Select the department this request should be routed to"/></XDSCenter>);}
Typeahead — With ValidationTypeahead with an error validation message.
tsx'use client';import {useState} from 'react';import {XDSTypeahead} from '@xds/core/Typeahead';import type {XDSSearchableItem, XDSSearchSource} from '@xds/core/Typeahead';import {XDSCenter} from '@xds/core/Center';const items: XDSSearchableItem[] = [{id: '1', label: 'New York'},{id: '2', label: 'San Francisco'},{id: '3', label: 'London'},{id: '4', label: 'Berlin'},{id: '5', label: 'Tokyo'},{id: '6', label: 'Singapore'},{id: '7', label: 'Sydney'},{id: '8', label: 'Toronto'},];const searchSource: XDSSearchSource = {search: (query: string) =>items.filter(i => i.label.toLowerCase().includes(query.toLowerCase())),bootstrap: () => items.slice(0, 5),};export default function TypeaheadWithValidation() {const [value, setValue] = useState<XDSSearchableItem | null>(null);return (<XDSCenter width={320}><XDSTypeaheadlabel="Office"placeholder="Search offices..."searchSource={searchSource}value={value}onChange={setValue}status={{type: 'error', message: 'Please select an office location'}}/></XDSCenter>);}
Showcase source
tsximport {XDSTypeahead} from '@xds/core/Typeahead';import type {XDSSearchableItem, XDSSearchSource} from '@xds/core/Typeahead';const fruits: XDSSearchableItem[] = [{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'},];const fruitSource: XDSSearchSource = {search: (query: string) =>fruits.filter(f => f.label.toLowerCase().includes(query.toLowerCase())),bootstrap: () => fruits.slice(0, 5),};export default function TypeaheadShowcase() {return (<div style={{width: 320}}><XDSTypeaheadlabel="Fruit"placeholder="Search fruits..."searchSource={fruitSource}value={null}onChange={() => {}}/></div>);}