Typeahead
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.
Installation
npm install @astryxdesign/core@0.7.1 @astryxdesign/theme-neutral@0.7.1 @stylexjs/stylex@0.19.0
Requires React 19+, base CSS and a Theme provider. See Setup.
Base Typeahead
Composable combobox engine providing a bare input, search, keyboard navigation, and a styled result dropdown. It renders no input wrapper, border, or selected-value token. Typeahead and Tokenizer compose it for standard fields.
| Prop | Type | Default | Description |
|---|---|---|---|
| searchSource * | SearchSource<T> | Data source providing search and bootstrap methods. | |
| value * | T | null | Currently selected item. | |
| onChange * | (item: T | null) => void | Called when the selection changes. | |
| renderItem | (item: T) => ReactNode | Custom render function for dropdown items. | |
| placeholder | string | 'Search…' | Input placeholder text. |
| hasEntriesOnFocus | boolean | false | Show bootstrap results on focus before typing. |
| maxMenuItems | number | 10 | Maximum dropdown items to display. |
| menuWidth | number | Requested dropdown width in pixels before viewport clamping. | |
| minQueryLength | number | 1 | Minimum query length before the search source is queried. Below it no search runs and the menu stays closed. |
| emptySearchText | ReactNode | 'No results found' | Content shown when the query matched nothing. Takes a ReactNode, so the dead end can carry a link or a create row. Announced in a polite live region as the text it renders, read from the DOM; aria-hidden parts stay out of both, and content that renders no text announces nothing. null counts as not given, like undefined, and falls through to the default; pass an empty string to render nothing. |
| isDisabled | boolean | false | Whether the input is disabled. |
| isFocusableDisabled | boolean | false | Keep a disabled input focusable with aria-disabled and readOnly so a caller-owned disabled reason remains discoverable. It blocks text entry, but when applied after results are already open, Enter can still select the highlighted option. |
| hasAutoFocus | boolean | false | Auto-focus the input on mount. |
| debounceMs | number | 150 | Debounce delay in ms before triggering search. Set to 0 for synchronous sources. |
| size | 'sm' | 'md' | 'lg' | 'md' | Size used to scale dropdown option padding. |
| 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. | |
| xstyle | StyleXStyles | Standard BaseProps StyleX styles applied to the input. Must be a stylex.create() value, not an inline style object. | |
| inputTabIndex | number | Legacy input-specific alias for native tabIndex. When provided, it takes precedence; otherwise native tabIndex is preserved. | |
| 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 | Legacy input-specific alias for native id. When provided, it takes precedence; otherwise native id is preserved. | |
| ariaDescribedBy | string | Legacy input-specific alias for native aria-describedby. When provided, it takes precedence; otherwise the native attribute is preserved. | |
| ariaLabelledBy | string | Legacy input-specific alias for native aria-labelledby. When provided, it takes precedence; otherwise the native attribute is preserved. |
Usage guidance
- Use Typeahead or Tokenizer for standard fields; they wrap BaseTypeahead with input chrome and selected-value rendering it intentionally omits.
- Provide your own visible label or aria-label and custom input wrapper so the bare combobox has an accessible name, focus treatment, border, and layout.
- Pass anchorRef pointing to your wrapper so the dropdown positions against your custom input chrome, not just the bare input element.
- Avoid: Expect input chrome or selected-value rendering. BaseTypeahead is an engine; the caller owns those visible parts.
- Avoid: Use BaseTypeahead when Typeahead or Tokenizer would suffice; the extra wrapper and styling work is only justified for truly custom compositions.
- Avoid: Treat Escape as cancellation of pending source work. It hides the current popup, but a late response can reopen it.
Anatomy
- Input: Bare combobox input. The caller supplies its visible field chrome and accessible name.
- Loading status: Named Spinner shown beside the input while an asynchronous source is pending, unless a composed owner takes over the busy indicator lane.
- Dropdown: Anchored listbox surface containing current search or bootstrap results.
- Empty state: Disabled listbox option shown after a completed search returns no results.
- Result group heading: Visible label for a group of result options.
- Result row: Option wrapper that owns highlight, selection, pointer, and keyboard behavior.
- Default item content: TypeaheadItem label and optional supporting content rendered inside a result row.
- Caller-rendered item content: Caller content supplied through renderItem or item.element inside the stable result row.
- Selected result state: Selected row weight and trailing check shown when a result matches value.
Typeahead
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.
| Prop | Type | Default | Description |
|---|---|---|---|
| label * | string | Accessible label for the input. | |
| searchSource * | SearchSource<T> | Data source providing search and bootstrap methods for populating the dropdown. | |
| value * | T | null | Currently selected item, or null if nothing is selected. | |
| onChange * | (item: T | null) => void | Called when the selection changes. | |
| placeholder | string | Input placeholder text. | |
| hasEntriesOnFocus | boolean | false | Show bootstrap results on focus before typing. |
| hasClear | boolean | true | Show clear button to deselect the current value. |
| isDisabled | boolean | false | Disables the input. |
| disabledMessage | string | Explains why the input is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the field focusable via aria-disabled (activation stays blocked). Use this instead of wrapping a disabled Typeahead in Tooltip. Disabled controls swallow the hover events an external Tooltip needs. | |
| maxMenuItems | number | 10 | Maximum number of dropdown items to display. |
| minQueryLength | number | 1 | Minimum query length before the search source is queried. Below it no search runs and the menu stays closed. |
| status | {type: 'warning' | 'error' | 'success', message?: string} | Validation status object with type and message for error/warning/success states. | |
| statusVariant | 'attached' | 'detached' | 'tooltip' | 'attached' | How the status message is placed relative to the input. attached overlaps directly below the input (bordered treatment); detached floats below as a separate element with spacing; tooltip reveals the message in an info-tip button. |
| renderItem | (item: T) => ReactNode | Custom render function for dropdown items. Default renders TypeaheadItem. | |
| isLabelHidden | boolean | false | Visually hides the label while keeping it accessible. |
| description | string | Helper text displayed below the label. | |
| isRequired | boolean | false | Marks the field as required. |
| isOptional | boolean | false | Shows an optional indicator on the label. |
| labelTooltip | string | Tooltip text shown on the label. | |
| emptySearchText | ReactNode | 'No results found' | Content shown when the query matched nothing. Takes a ReactNode, so the dead end can carry a link or a create row. Announced in a polite live region as the text it renders, read from the DOM; aria-hidden parts stay out of both, and content that renders no text announces nothing. null counts as not given, like undefined, and falls through to the default; pass an empty string to render nothing. |
| hasAutoFocus | boolean | false | Auto-focus the input on mount. |
| size | 'sm' | 'md' | 'lg' | 'md' | Input and token size. |
| debounceMs | number | 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. | |
| width | SizeValue | Width of the field (number = pixels, string used as-is, e.g. "100%"). Sizes the whole field (label, control, and status) so they stay aligned. | |
| startIcon | IconType | ReactNode | SVG icon component displayed at the start of the input. | |
| containerXstyle | StyleXStyles | StyleX styles for the field container (label, control, and status together). No effect inside InputGroup (style the InputGroup instead) or in a horizontal-labels FormLayout. Root xstyle, className, and style are not accepted. | |
| containerClassName | string | Class name for the field container. Same targeting as containerXstyle. | |
| containerStyle | CSSProperties | Inline styles for the field container. Same targeting as containerXstyle. | |
| controlXstyle | StyleXStyles | StyleX styles for the visual control box: the element that paints the border and background and holds icons and end controls. Never the native input. | |
| controlClassName | string | Class name for the visual control box. | |
| controlStyle | CSSProperties | Inline styles for the visual control box. |
Usage guidance
- Provide descriptive placeholder text that hints at what users can search for.
- Show suggestions on focus when users benefit from seeing popular or recent options before typing.
- Add a search delay for remote data sources to avoid excessive network requests.
- Use inside InputGroup when the typeahead needs a single-line prefix or suffix addon.
- Avoid: Use for short, static option lists; use Selector for better discoverability.
- Avoid: Use for multi-selection; use Tokenizer instead.
- Avoid: Place multiple Typeaheads adjacent to each other without clear labels differentiating them.
- Avoid: Wrap a disabled Typeahead in Tooltip to explain why it is disabled; disabled triggers swallow the hover events the wrapper needs. Use the disabledMessage prop instead.
Theming API
{
"targets": [
{
"className": "astryx-typeahead",
"visualProps": [
"status",
"size"
]
},
{
"className": "astryx-typeahead-dropdown"
},
{
"className": "astryx-typeahead-empty-state"
},
{
"className": "astryx-typeahead-item"
}
]
}
BaseTypeahead
TypeaheadItem
Anatomy
- Field: Standalone Field shell that provides the label and optional supporting content; omitted inside InputGroup.
- Input surface: Painted control surface containing the editable input or the selected token.
- Icon-rendered start icon: Optional leading semantic icon or icon component rendered through Icon.
- Caller-rendered start content: Optional arbitrary React content rendered directly at the start of the input surface.
- Selected token: Token that presents the selected item while the control is not in edit mode.
- Spinner: Loading indicator shown at the end of the input surface while a search is in flight.
- Clear button: Shared clear action that removes the selected item when hasClear is enabled.
- Dropdown: Anchored listbox surface containing the current search results.
- Empty state: Message shown after a completed search returns no results.
- Result row: Stable option wrapper that owns highlight, selection, pointer, and keyboard behavior.
- Default item content: Standard TypeaheadItem label and supporting content rendered inside a result row when renderItem and item.element are absent.
- Caller-rendered item content: Caller-owned result content supplied through renderItem or item.element inside the stable result row.
- Result group heading: Visible heading for a labeled group of result rows.
- Selected result state: Selected styling and trailing check presented on the current result row.
Typeahead Item
Default dropdown item renderer for typeahead results. Shows label with optional icon, description, and avatar. Exported for use in custom renderItem implementations.
| Prop | Type | Default | Description |
|---|---|---|---|
| item * | SearchableItem | 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 | false | Whether this item is visually disabled. |
| group | string | Group label for grouping items visually. |
Examples
- BaseTypeahead — Custom Search Bar: BaseTypeahead embedded inside a custom-styled wrapper. The wrapper provides its own border and icon chrome; anchorRef positions the dropdown relative to it. Use this pattern when Typeahead's built-in field layout does not fit your composition.
- Typeahead Item: Typeahead with custom item rendering using TypeaheadItem for avatars and descriptions.
- Typeahead:
- Base Typeahead: A custom result renderer that adds supporting metadata while BaseTypeahead retains option semantics and keyboard behavior.
- TypeaheadItem — Basic: A typeahead whose results are rendered with TypeaheadItem, adding a secondary description below each label. Use inside renderItem to keep custom results visually consistent.
- Typeahead — Limited Results: Typeahead with a capped dropdown showing at most three results.
- Typeahead — Search Field: Search input with icon and suggestions on focus.
- Typeahead — With Helper Text: Typeahead with a description below the label.
- Typeahead — With Validation: Typeahead with an error validation message.
Complete example dependencies
Some examples import optional icons. The per-example install command in the examples list identifies exactly what that example needs. For all examples on this page:
npm install @heroicons/react@2.2.0
Source StyleX compilation
BaseTypeaheadCustomSearch, BaseTypeaheadShowcase contain stylex.create and require a StyleX source build pipeline. Precompiled Core CSS alone does not compile these examples. See Styling before copying them.