Autocomplete

Intelligent search input with suggestions for locations, properties, and more.

Basic Autocomplete

Location Search

Munich Bavaria, Germany
Munster North Rhine-Westphalia, Germany
Munich Airport Freising, Bavaria

Empty State

No results found for "xyz"

Loading State

Grouped Results

Property Search with Categories

Properties
Villa Monaco €2,500,000 • 5 bedrooms
Villa Sunset €1,800,000 • 4 bedrooms
Locations
Villach Austria
Agents
John Villanueva Senior Agent

Multi-Select Autocomplete

Property Features

Pool
Garden
Garage

Implementation Guide

HTML Structure

<!-- Autocomplete -->
<div class="lyd-autocomplete">
    <input type="text" class="lyd-autocomplete-input" 
           placeholder="Type to search...">
    <svg class="lyd-autocomplete-icon">...</svg>
    
    <div class="lyd-autocomplete-dropdown">
        <!-- Grouped Results -->
        <div class="lyd-autocomplete-group">Properties</div>
        
        <div class="lyd-autocomplete-item">
            <svg class="lyd-autocomplete-item-icon">...</svg>
            <div class="lyd-autocomplete-item-text">
                <span class="lyd-autocomplete-item-primary">
                    <span class="lyd-autocomplete-highlight">Mun</span>ich
                </span>
                <span class="lyd-autocomplete-item-secondary">
                    Bavaria, Germany
                </span>
            </div>
        </div>
    </div>
</div>

React/Next.js Component

// components/Autocomplete.tsx
import { useState, useRef, useEffect } from 'react';

interface AutocompleteOption {
  id: string;
  label: string;
  sublabel?: string;
  group?: string;
  icon?: React.ReactNode;
}

interface AutocompleteProps {
  options: AutocompleteOption[];
  placeholder?: string;
  value?: string;
  onChange?: (value: string) => void;
  onSelect?: (option: AutocompleteOption) => void;
  loading?: boolean;
  multiSelect?: boolean;
}

export const Autocomplete = ({
  options,
  placeholder = 'Type to search...',
  value = '',
  onChange,
  onSelect,
  loading = false,
  multiSelect = false
}: AutocompleteProps) => {
  const [isOpen, setIsOpen] = useState(false);
  const [searchValue, setSearchValue] = useState(value);
  const [selectedOptions, setSelectedOptions] = useState([]);
  const [activeIndex, setActiveIndex] = useState(0);
  const ref = useRef(null);
  
  const filteredOptions = options.filter(option =>
    option.label.toLowerCase().includes(searchValue.toLowerCase())
  );
  
  const groupedOptions = filteredOptions.reduce((acc, option) => {
    const group = option.group || 'Results';
    if (!acc[group]) acc[group] = [];
    acc[group].push(option);
    return acc;
  }, {} as Record);
  
  const handleSelect = (option: AutocompleteOption) => {
    if (multiSelect) {
      setSelectedOptions([...selectedOptions, option]);
      setSearchValue('');
    } else {
      setSearchValue(option.label);
      onSelect?.(option);
    }
    setIsOpen(false);
  };
  
  const highlightMatch = (text: string) => {
    const parts = text.split(new RegExp(`(${searchValue})`, 'gi'));
    return parts.map((part, i) => 
      part.toLowerCase() === searchValue.toLowerCase() ? 
        <span key={i} className="lyd-autocomplete-highlight">{part}</span> : 
        part
    );
  };
  
  return (
    <div className="lyd-autocomplete" ref={ref}>
      {multiSelect && selectedOptions.length > 0 && (
        <div className="lyd-autocomplete-chips">
          {selectedOptions.map(option => (
            <div key={option.id} className="lyd-autocomplete-chip">
              {option.label}
              <svg 
                className="lyd-autocomplete-chip-remove"
                onClick={() => /* remove chip */}
              >...</svg>
            </div>
          ))}
        </div>
      )}
      
      <input
        type="text"
        className="lyd-autocomplete-input"
        placeholder={placeholder}
        value={searchValue}
        onChange={(e) => {
          setSearchValue(e.target.value);
          onChange?.(e.target.value);
          setIsOpen(true);
        }}
        onFocus={() => setIsOpen(true)}
      />
      
      {isOpen && (
        <div className="lyd-autocomplete-dropdown open">
          {loading ? (
            <div className="lyd-autocomplete-loading">
              <div className="lyd-autocomplete-spinner" />
            </div>
          ) : filteredOptions.length === 0 ? (
            <div className="lyd-autocomplete-empty">
              No results found
            </div>
          ) : (
            Object.entries(groupedOptions).map(([group, items]) => (
              <div key={group}>
                {Object.keys(groupedOptions).length > 1 && (
                  <div className="lyd-autocomplete-group">{group}</div>
                )}
                {items.map((option, index) => (
                  <div
                    key={option.id}
                    className={`lyd-autocomplete-item ${
                      index === activeIndex ? 'active' : ''
                    }`}
                    onClick={() => handleSelect(option)}
                  >
                    {option.icon}
                    <div className="lyd-autocomplete-item-text">
                      <span className="lyd-autocomplete-item-primary">
                        {highlightMatch(option.label)}
                      </span>
                      {option.sublabel && (
                        <span className="lyd-autocomplete-item-secondary">
                          {option.sublabel}
                        </span>
                      )}
                    </div>
                  </div>
                ))}
              </div>
            ))
          )}
        </div>
      )}
    </div>
  );
};

API Reference

Class Description
.lyd-autocomplete Container for autocomplete
.lyd-autocomplete-input Search input field
.lyd-autocomplete-dropdown Dropdown container
.lyd-autocomplete-item Individual result item
.lyd-autocomplete-group Group header
.lyd-autocomplete-highlight Highlighted search match
.lyd-autocomplete-chips Multi-select chips container
.lyd-autocomplete-chip Selected item chip
.active Active/focused item
.selected Selected item