My Own Headless Autocomplete Component

Autocomplete components are essential in modern user interfaces, enabling users to search, filter, and select data with ease. However, most libraries either provide tightly coupled solutions that limit customization or headless solutions without built-in accessibility or keyboard support. I decided to address this gap by building my own headless UI-only autocomplete component, focusing on three core principles:

  1. Customizability – Developers retain full control over styling, data fetching, and filtering.
  2. Accessibility – The component follows WAI-ARIA guidelines for compatibility with assistive technologies.
  3. Keyboard Support – Full navigation and interaction support using the keyboard.

This article walks you through the design, implementation, and key features of this flexible autocomplete component.

Goals and Scope

What the Component Does

  • Provides the UI structure for an autocomplete, including:
    • Input (AutocompleteInput).
    • Dropdown container (AutocompleteOptions).
    • Individual selectable options (AutocompleteOption).
  • Handles essential keyboard interactions and accessibility features.

What the Component Does Not Do

  • The component does not handle data fetching or filtering. Developers are free to implement these aspects as per their needs, ensuring maximum flexibility:
    • Use static datasets (e.g., local arrays).
    • Fetch data dynamically from third-party APIs.
    • Implement custom caching or filtering logic.

Examples of Use Cases

We aim to document several use cases to demonstrate how the component can be effectively used in various contexts:

  1. Static Data: Simple datasets directly embedded in the frontend.
  2. Remote Data: Real-time querying of external APIs (e.g., Google Places or GitHub APIs).
  3. Complex Filtering: Advanced use cases such as integrating with a search engine or applying business-specific filters.

Interface definition (API)

This component leverages the compound component pattern, allowing developers to compose different pieces of the autocomplete UI in a flexible and reusable way. It is powered by React Context, which facilitates state sharing across the compound components.

Autocomplete

  • The root component that provides the overall context (e.g., selected value, open state, active option) to its children.
  • Manages basic interaction states like opening/closing the dropdown.

AutocompleteInput

  • Represents the input field where users type their queries.
  • Updates the query state based on user input and communicates changes to the parent.

AutocompleteOptions

  • A container for rendering the dropdown options.
  • Displays options only when the dropdown is open.

AutocompleteOption

  • Represents an individual selectable option.
  • Supports aria-selected and triggers events like selection when clicked or navigated via the keyboard.

By separating concerns across these components, developers can replace, extend, or re-style any part of the autocomplete as needed.

Key Features

Customizability

Developers can:

  • Style each component with their preferred CSS framework (e.g., Tailwind CSS, Emotion).
  • Pass custom rendering logic for options or input fields.
  • Fully control the data layer, whether using static lists or complex APIs.

Accessibility

The component strictly adheres to WAI-ARIA guidelines to ensure full compatibility with assistive technologies like screen readers.

Keyboard Support

Users can navigate and interact with the autocomplete entirely via the keyboard:

  • ArrowUp and ArrowDown to navigate options.
  • Enter to select an option.
  • Escape to close the dropdown.

Why UI-Only?

By leaving out the data-fetching logic, this component provides unmatched flexibility:

  • Developers can implement their own caching strategies (e.g., TanStack Query or Redux).
  • They are free to choose how to handle filtering or API requests.
  • This approach reduces unnecessary complexity and ensures that the component remains lightweight.

Future Work

  1. Advanced Documentation:
  • Include detailed guides for integrating the component with various data sources and state management libraries.
  • Provide examples for common use cases, such as integrating with REST APIs or GraphQL.
  1. Virtualization Support:
  • Add support for rendering long lists efficiently using techniques like windowing.
  1. TypeScript Improvements:
  • Enhance type inference for value and onChange to improve developer experience.

Example Usage

import {
  Autocomplete,
  AutocompleteInput,
  AutocompleteOption,
  AutocompleteOptions,
} from 'furet'
import { useState } from 'react'
import './Example.css'

const destinations = [
  { id: 1, name: 'Paris' },
  { id: 2, name: 'Tokyo' },
  { id: 3, name: 'Milan' },
  { id: 4, name: 'Bangkok' },
  { id: 5, name: 'Los Angeles' },
]

export default function Example() {
  const [query, setQuery] = useState('')
  const [selectedDestination, setSelectedDestination] = useState(
    destinations[0],
  )

  const filteredDestinations =
    query === ''
      ? destinations
      : destinations.filter((destination) =>
          destination.name.toLowerCase().includes(query.toLowerCase()),
        )

  return (
    <div className="autocomplete-container">
      <Autocomplete
        value={selectedDestination}
        onChange={(value) => setSelectedDestination(value)}
        onClose={() => setQuery('')}
      >
        <div className="autocomplete-input-wrapper">
          <AutocompleteInput
            className="autocomplete-input"
            displayValue={(destination) => destination?.name || ''}
            onChange={(event) => setQuery(event.target.value)}
            placeholder="Search destinations..."
          />
        </div>

        <AutocompleteOptions className="autocomplete-options">
          {filteredDestinations.map((destination) => (
            <AutocompleteOption
              key={destination.id}
              value={destination}
              className="autocomplete-option"
            >
              {destination.name}
            </AutocompleteOption>
          ))}
        </AutocompleteOptions>
      </Autocomplete>
    </div>
  )
}

Conclusion

This headless autocomplete component is a powerful UI tool that combines accessibility, customizability, and keyboard support. By decoupling the data layer from the UI, it adapts to a wide range of use cases, making it a versatile addition to any React project. Stay tuned for future updates and examples demonstrating its real-world usage! 🎉

Curious to try it out? Check out the live demo or visit the GitHub repository for more details, examples, and installation instructions.