FairGarden Design

Autocomplete

Free text with suggestions, where any value is valid and suggestions help, such as addresses: Base UI's Autocomplete, inside a Field. Site and collection search compose it in Search.

import { Autocomplete } from '@fairgarden-private/design/components/Autocomplete'

Suggestions are { value, label? } objects, or titled groups; a chosen suggestion only fills the input, and Enter submits the typed text. There is no ✓, because nothing stays selected. When the server filters the suggestions, pass filter={null} and the current items.

Suggestions and states

Suggestions and states

No row is highlighted until the arrow keys move; the highlighted row takes the --primary4 soft fill and the --ds-stroke-3 start bar, and matches are marked by weight. loading shows "Searching…" in a polite live region; error shows the danger glyph and a message instead of silently closing; the empty row keeps what the user typed.

Pick a suggestion or keep typing.

AutocompleteStates.tsx
'use client'

import * as React from 'react'
import {
  Autocomplete,
  type AutocompleteOption,
} from '@fairgarden-private/design/components/Autocomplete'
import { Field, FieldDescription, FieldLabel } from '@fairgarden-private/design/components/Field'
import styles from './states.module.css'

const streets: AutocompleteOption[] = [
  { value: '12 Marsh Lane' },
  { value: '14 Marsh Lane' },
  { value: '3 Heron Close' },
  { value: '27 Heron Way' },
  { value: '9 Osprey Road' },
  { value: '41 Tern Street' },
]

const places = [
  { label: 'Towns', items: [{ value: 'Ashby' }, { value: 'Bexley' }, { value: 'Carrow' }] },
  { label: 'Parks', items: [{ value: 'Ashby Common' }, { value: 'Bexley Woods' }] },
]

/**
 * Free text with suggestions: any value is valid. Then a loading field
 * (the "Searching…" row), a fetch failure and a disabled field.
 */
export function AutocompleteStates() {
  const [loading, setLoading] = React.useState(false)

  return (
    <div className={styles.stack}>
      <Field>
        <FieldLabel>Street Address</FieldLabel>
        <Autocomplete items={streets} placeholder="Start with the number…" />
        <FieldDescription>Pick a suggestion or keep typing.</FieldDescription>
      </Field>
      <Field>
        <FieldLabel>Meeting Place</FieldLabel>
        <Autocomplete items={places} icon="search" showTrigger placeholder="Town or park…" />
      </Field>
      <Field>
        <FieldLabel>Nearest Station</FieldLabel>
        <Autocomplete
          items={[]}
          filter={null}
          loading={loading}
          onValueChange={(value) => setLoading(value.trim() !== '')}
          placeholder="Type to search…"
        />
      </Field>
      <Field>
        <FieldLabel>Parish</FieldLabel>
        <Autocomplete
          items={[]}
          filter={null}
          error="Couldn't load suggestions. Keep typing or try again."
          placeholder="Type to search…"
        />
      </Field>
      <Field disabled>
        <FieldLabel>County</FieldLabel>
        <Autocomplete items={streets} defaultValue="Heron County" />
      </Field>
    </div>
  )
}

Primary and secondary

Primary and secondary

primary sets the box, value and icons; secondary is unused at rest and becomes the danger scale while invalid.

AutocompleteColor.tsx
'use client'

import {
  Autocomplete,
  type AutocompleteOption,
} from '@fairgarden-private/design/components/Autocomplete'
import { Field, FieldLabel } from '@fairgarden-private/design/components/Field'
import styles from './color.module.css'

const topics: AutocompleteOption[] = [
  { value: 'Birding' },
  { value: 'Botany' },
  { value: 'Bouldering' },
  { value: 'Butterflies' },
]

/** `primary` recolors the box and its icons; the popup keeps the white scope's defaults. */
export function AutocompleteColor() {
  return (
    <div className={styles.stack}>
      <Field>
        <FieldLabel>Primary Plum</FieldLabel>
        <Autocomplete primary="plum" items={topics} icon="search" placeholder="Topic…" />
      </Field>
      <Field>
        <FieldLabel>Primary Indigo</FieldLabel>
        <Autocomplete primary="indigo" items={topics} placeholder="Topic…" />
      </Field>
    </div>
  )
}

On grounds

On paper and forest

The box follows its ground; the popup renders in a portal as the white scope.

paper

forest

AutocompleteGrounds.tsx
'use client'

import {
  Autocomplete,
  type AutocompleteOption,
} from '@fairgarden-private/design/components/Autocomplete'
import { Field, FieldLabel } from '@fairgarden-private/design/components/Field'
import { PresetGround } from '@/components/PresetGround'
import styles from './grounds.module.css'

const rivers: AutocompleteOption[] = [
  { value: 'Avon' },
  { value: 'Derwent' },
  { value: 'Severn' },
  { value: 'Tweed' },
]

const presets = ['paper', 'forest'] as const

/** The box follows its ground; the suggestion popup is always the white scope. */
export function AutocompleteGrounds() {
  return (
    <div className={styles.row}>
      {presets.map((preset) => (
        <PresetGround key={preset} preset={preset} className={styles.face}>
          <p className={styles.name}>{preset}</p>
          <Field>
            <FieldLabel>River</FieldLabel>
            <Autocomplete items={rivers} icon="search" placeholder="River name…" />
          </Field>
        </PresetGround>
      ))}
    </div>
  )
}

API Reference

A Base UI Autocomplete inside a Field. The input keeps any text: a suggestion only fills it, and Enter submits the typed text. No row is highlighted until the arrow keys move.

PropTypeDescription
aria-label
string | undefined

Names the input when no visible label exists. Prefer a FieldLabel.

clearLabel
string | undefined

The clear בs accessible name. Default “Clear”.

emptyText
((query: string) => React.ReactNode) | undefined

The empty row. Default “No matches for ‘[query]'". Keep what the user typed.

error
React.ReactNode | undefined

A fetch failure, shown with the danger glyph instead of silently closing.

icon
| 'search'
| 'arrow_forward'
| 'arrow_upward'
| 'expand_more'
| 'close'
| 'remove'
| 'add'
| 'check'
| 'circle'
| 'chevron_right'
| 'chevron_left'
| 'menu'
| 'more_horiz'
| 'play_arrow'
| 'pause'
| 'download'
| 'zoom_in'
| 'zoom_out'
| 'recenter'
| 'help'
| 'mail'
| undefined

A leading icon in the box, such as search (inline tier).

items
AutocompleteItems | undefined

The suggestions, or titled groups. Pass filter={null} when the server filters them.

loading
boolean | undefined

Shows the loading row (“Searching…") while suggestions load.

loadingText
React.ReactNode | undefined

The loading row’s words. Default “Searching…".

placeholder
string | undefined

The input’s placeholder, ending in “…"; never the label.

plate
boolean | undefined

The box face becomes a nested white scope; patterned grounds only (§10.1).

primary
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale: edge, value, icons and focus ring. Never defaulted [D133].

secondary
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'ruby'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale. Unused at rest; the danger scale while invalid.

showTrigger
boolean | undefined

Shows the chevron that opens the full list. Default false.

triggerLabel
string | undefined

The chevron’s accessible name. Default “Show suggestions”.

className
string | undefined

Class names for the box, added after the module’s own.

autocomplete
AutocompleteOption

One suggestion: the text it puts in the input, and optional display words.

type AutocompleteOption = {
  /** The text the suggestion fills in. */
  value: string;
  /** The words shown in the list. Default: `value`. */
  label?: string;
  disabled?: boolean;
}
AutocompleteOptionGroup

A titled group of suggestions.

type AutocompleteOptionGroup = {
  /** The group label, in `type-label` caps. */
  label: string;
  items: AutocompleteOption[];
}
AutocompleteProps

Props for Autocomplete: Base UI Autocomplete.Root props plus the suggestions, rows and color axes.

type AutocompleteProps = {
  /** The suggestions, or titled groups. Pass `filter={null}` when the server filters them. */
  items?: AutocompleteItems;
  /** The input's placeholder, ending in "…"; never the label. */
  placeholder?: string;
  /** A leading icon in the box, such as `search` (inline tier). */
  icon?:
    | 'search'
    | 'arrow_forward'
    | 'arrow_upward'
    | 'expand_more'
    | 'close'
    | 'remove'
    | 'add'
    | 'check'
    | 'circle'
    | 'chevron_right'
    | 'chevron_left'
    | 'menu'
    | 'more_horiz'
    | 'play_arrow'
    | 'pause'
    | 'download'
    | 'zoom_in'
    | 'zoom_out'
    | 'recenter'
    | 'help'
    | 'mail';
  /** Shows the chevron that opens the full list. Default `false`. */
  showTrigger?: boolean;
  /** The empty row. Default "No matches for '[query]'". Keep what the user typed. */
  emptyText?: (query: string) => React.ReactNode;
  /** Shows the loading row ("Searching…") while suggestions load. */
  loading?: boolean;
  /** The loading row's words. Default "Searching…". */
  loadingText?: React.ReactNode;
  /** A fetch failure, shown with the danger glyph instead of silently closing. */
  error?: React.ReactNode;
  /** The clear ×'s accessible name. Default "Clear". */
  clearLabel?: string;
  /** The chevron's accessible name. Default "Show suggestions". */
  triggerLabel?: string;
  /** Names the input when no visible label exists. Prefer a `FieldLabel`. */
  'aria-label'?: string;
  /** Class names for the box, added after the module's own. */
  className?: string;
  /** The box face becomes a nested `white` scope; patterned grounds only (§10.1). */
  plate?: boolean;
  /** Primary Radix scale: edge, value, icons and focus ring. Never defaulted [D133]. */
  primary?:
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'ruby'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | null;
  /** Secondary Radix scale. Unused at rest; the danger scale while invalid. */
  secondary?:
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'ruby'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | 'amber'
    | 'blue'
    | 'cyan'
    | 'grass'
    | 'green'
    | 'jade'
    | 'lime'
    | 'mint'
    | 'orange'
    | 'sky'
    | 'teal'
    | 'yellow'
    | null;
}

Specification: DESIGN-SYSTEM.md §10.6 (combobox and autocomplete).