Autocomplete is one of those UI elements that feels simple until you try to build one that looks great, feels responsive, and is flexible enough to extend. That’s where shadcn/ui comes in.

Check out the full project here (Still a WIP)

What is shadcn/ui?

shadcn/ui is a component library that brings together the flexibility of headless UI, the utility of Tailwind CSS, and the aesthetics of pre-styled components—all while keeping your codebase in control.

Why Should You Use It?

Here’s what makes it stand out:

  • Open Code: You own the top layer. Easily customize anything without hacking around abstraction.
  • Composition: Components are built with predictable, composable patterns.
  • Distribution: Simple CLI and flat-file system make setup and updates seamless.
  • Beautiful Defaults: Clean, modern styles out of the box.
  • AI-Ready: Because the code is open and modular, it’s easy for LLMs to analyze and suggest improvements.

Installation (Next.js)

pnpm dlx shadcn@latest init

This sets up the base config and connects the CLI to your project.

The Command Component

This is the core of the autocomplete feature. It’s a dynamic, accessible component designed for fuzzy search and filtering.

Install it:

pnpm dlx shadcn@latest add command

Import the component and structure your layout.

"use client";
import { useState, useCallback } from "react";
import {
   Command,
   CommandEmpty,
   CommandGroup,
   CommandInput,
   CommandItem,
   CommandList,
   CommandSeparator,
} from "@/components/ui/command";

Create the component

Neighborhood Rating

<>
         
            
               
                   handleUserInput(v)}
                     className="block w-[480px] h-[48px] -mr-16 text-base text-gray-900"
                  />

                  
                     No results found.
                     
                        {predictions.map((prediction) => (
                            handleSelectedPlace(value)}
                           >
                              {prediction.placePrediction?.text.text}
                           
                        ))}
                     
                     
                  
               
               
                  Calculate rating
               
            
         
      >

I used the CommandInput to handle user input and the CommandList to render results from the API call. It is important to not that Shadcn\ui handles state changes for you.

Tip: Use the shouldFilter prop to control search behavior—great for more advanced use cases like server-side filtering or custom ranking.

Create event handlers to handle user input and rendering the results.

const [predictions, setPredictions] = useState<
      google.maps.places.AutocompleteSuggestion[]
   >([]);

   const handleUserInput = useCallback(
      debounce((input: string) => {
         setUserInput(input);
         fetchPredictions(input);
      }, 1000),
      []
   );

   async function fetchPredictions(input: string) {
      const queryBody = {
         input: input,
         includedRegionCodes: ["uk"],
         includeQueryPredictions: true,
      };
      try {
         const res = await PlacesApi.post("places:autocomplete", queryBody);
         const data = await res.json();
         if (!res.ok) throw new Error("Failed to fetch predictions");
         console.log("received suggestings ->", data.suggestions);
         setPredictions(data.suggestions ?? []);
      } catch (error) {
         console.log(error);
      }
   }

   const handleSelectedPlace = (placeId: string) => {
      const selectedInput = predictions.filter(
         (prediction) => prediction.placePrediction?.placeId === placeId
      );
      setUserInput(String(selectedInput[0].placePrediction?.text.text));
      handleGetSelectedPlaceRating(placeId);
   };

Here's a simple explanation of my code.

  • The prediction state stores the response from the Google Maps API
  • handleUserInput helps rate limit the user input by using the debounce helper function.
  • fetchPredictions takes a string and uses the PlacesApi helper function to GET the predictions and sets the array of predictions into state.
  • handleSelectedPlace uses the CommandItem value which is the place Id to get the text the user selected and then calls a function prop from the parent component.

Key challenged faced: Google Autocomplete Types

I initially found it tricky to properly type Google Maps results but after pair programming with my friend @opeadeyomoye, we were able to find the DefintelyTyped Google Maps Types and it was a live saver!