# Empty Search

> A search page showing live suggestions, a no-results state, and suggested queries.

A shadcn/ui block from the [@esuiss](https://suiss.app/ui?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/esuiss/empty-search).

- Page, with a live preview: https://shadcn.exchange/r/esuiss/empty-search
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/esuiss/empty-search-light-bcf1ef6fda3564fc.png
- Registry item: https://suiss.app/ui/r/new-york-v4/empty-search.json
- Categories: blocks, empty
- Listed: 2026-10-20, source last changed 2026-10-20

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/esuiss/empty-search.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `@esuiss/button`, `@esuiss/empty`

## Files

### `components/blocks/empty-search.tsx`

```tsx
"use client"

import * as React from "react"

import { IconPlaceholder } from "@/app/(create)/components/icon-placeholder"
import { Button } from "@/components/ui/button"
import {
  Empty,
  EmptyContent,
  EmptyDescription,
  EmptyHeader,
  EmptyTitle,
} from "@/components/ui/empty"

const suggestions = ["Book Air", "Book Pro", "Book mini", "Compare Book models"]

export function EmptySearch() {
  const [query, setQuery] = React.useState("book neo")
  const inputRef = React.useRef<HTMLInputElement>(null)
  const trimmed = query.trim().toLowerCase()
  const matches =
    trimmed.length > 0 && trimmed.startsWith("book")
      ? suggestions.filter((item) => item.toLowerCase().startsWith(trimmed))
      : []

  return (
    <section className="px-5 py-10 md:px-12">
      <div className="mx-auto flex max-w-170 flex-col gap-6.5">
        <div className="flex h-13 items-center gap-2.5 border-b border-input text-muted-foreground focus-within:border-primary">
          <IconPlaceholder
            lucide="SearchIcon"
            tabler="IconSearch"
            hugeicons="Search01Icon"
            phosphor="MagnifyingGlassIcon"
            remixicon="RiSearchLine"
            className="size-5 shrink-0"
          />
          <input
            ref={inputRef}
            type="search"
            value={query}
            onChange={(event) => setQuery(event.target.value)}
            aria-label="Search suiss"
            placeholder="Search suiss"
            className="min-w-0 flex-1 bg-transparent text-2xl font-semibold text-foreground outline-none placeholder:text-muted-foreground [&::-webkit-search-cancel-button]:hidden"
          />
          {query && (
            <Button
              size="icon-xs"
              aria-label="Clear search"
              onClick={() => {
                setQuery("")
                inputRef.current?.focus()
              }}
              className="relative size-5.5 bg-muted-foreground text-background after:absolute after:-inset-2.5 after:content-[''] hover:bg-muted-foreground"
            >
              <IconPlaceholder
                lucide="XIcon"
                tabler="IconX"
                hugeicons="Cancel01Icon"
                phosphor="XIcon"
                remixicon="RiCloseLine"
                className="size-2.75"
              />
            </Button>
          )}
        </div>
        {matches.length > 0 ? (
          <nav aria-label="Suggested" className="flex flex-col gap-1">
            <p className="mb-1.5 text-xs text-muted-foreground">Suggested</p>
            {matches.map((item) => (
              <button
                key={item}
                type="button"
                onClick={() => setQuery(item)}
                className="flex items-center gap-2.5 rounded-md py-1.5 text-start text-lg outline-none hover:underline focus-visible:ring-3 focus-visible:ring-ring/50"
              >
                <IconPlaceholder
                  lucide="SearchIcon"
                  tabler="IconSearch"
                  hugeicons="Search01Icon"
                  phosphor="MagnifyingGlassIcon"
                  remixicon="RiSearchLine"
                  className="size-3.5 text-muted-foreground"
                />
                {item}
              </button>
            ))}
          </nav>
        ) : (
          <Empty
            key={query ? "none" : "idle"}
            className="items-start gap-3 rounded-none p-0 text-start transition-[opacity,translate] duration-800 ease-[cubic-bezier(0.32,0.72,0,1)] starting:translate-y-2 starting:opacity-0 motion-reduce:transition-none"
          >
            <EmptyHeader
              className="max-w-none items-start gap-3"
              aria-live="polite"
            >
              <EmptyTitle className="text-3xl tracking-tight">
                {query
                  ? `No results for “${query}”.`
                  : "What are you looking for?"}
              </EmptyTitle>
              <EmptyDescription className="text-lg/normal">
                {query
                  ? "Check the spelling or try one of these:"
                  : "Search for products, support articles or stores."}
              </EmptyDescription>
            </EmptyHeader>
            <EmptyContent className="mt-1.5 max-w-none flex-row flex-wrap gap-2">
              {suggestions.map((item) => (
                <Button
                  key={item}
                  variant="secondary"
                  onClick={() => setQuery(item)}
                  className="h-8.5 px-3.5"
                >
                  {item}
                </Button>
              ))}
            </EmptyContent>
          </Empty>
        )}
      </div>
    </section>
  )
}
```
