Navbar with Search Field and Category Bar

A two-row store header. The top row has a wordmark, a rounded search field that suggests matching products as you type, a sign in link and a basket with a count. The second row lists categories that scroll sideways on phones, with an underline on the selected one.

Code

// Adapted from Meraki UI (https://merakiui.com/components)
// Copyright (c) 2021 Khatab Wedaa. MIT License: https://we.inc/components/licenses
import { useState } from "react";

const cats = ["All", "Kitchen", "Lighting", "Textiles", "Storage", "Outdoor", "Sale"];
const catalogue = ["Linen apron", "Oak spice rack", "Brass desk lamp", "Wool throw", "Stackable crate", "Garden trowel set", "Ceramic pour-over", "Cane pendant light"];

export default function NavbarSearchCategories() {
  const [cat, setCat] = useState("All");
  const [q, setQ] = useState("");
  const [focus, setFocus] = useState(false);
  const hits = q ? catalogue.filter((c) => c.toLowerCase().includes(q.toLowerCase())).slice(0, 4) : [];
  return (
    <div className="min-h-[360px] bg-white">
      <header className="border-b border-slate-200">
        <div className="mx-auto flex max-w-6xl flex-wrap items-center gap-3 px-4 py-3 sm:px-6">
          <a href="#" className="mr-2 text-lg font-bold tracking-tight text-slate-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-orange-600">Hearth<span className="text-orange-600">&amp;</span>Co</a>
          <div className="relative order-3 w-full sm:order-none sm:flex-1 sm:max-w-xl">
            <label htmlFor="nbs-q" className="sr-only">Search the shop</label>
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true" className="pointer-events-none absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-500"><path d="m21 21-4.3-4.3M17 10a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z" /></svg>
            <input id="nbs-q" value={q} onChange={(e) => setQ(e.target.value)} onFocus={() => setFocus(true)} onBlur={() => setTimeout(() => setFocus(false), 120)} placeholder="Search for lamps, linen, crates" className="h-11 w-full rounded-full border border-slate-300 bg-slate-50 pl-10 pr-4 text-sm text-slate-900 placeholder:text-slate-500 focus:border-orange-500 focus:bg-white focus:outline-none focus:ring-2 focus:ring-orange-500/25" />
            {focus && hits.length > 0 && (
              <ul className="absolute left-0 right-0 z-10 mt-2 overflow-hidden rounded-xl bg-white py-1 shadow-xl ring-1 ring-slate-900/10">
                {hits.map((h) => (
                  <li key={h}><a href="#" className="block px-4 py-2.5 text-sm text-slate-800 hover:bg-orange-50">{h}</a></li>
                ))}
              </ul>
            )}
          </div>
          <div className="ml-auto flex items-center gap-1">
            <a href="#" className="rounded-lg px-3 py-2 text-sm font-medium text-slate-700 hover:bg-slate-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-orange-600">Sign in</a>
            <a href="#" aria-label="Basket, 2 items" className="relative inline-flex h-10 w-10 items-center justify-center rounded-full text-slate-700 hover:bg-slate-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-orange-600">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="h-5 w-5"><path d="M5 8h14l-1 12H6L5 8Zm4 0V6a3 3 0 0 1 6 0v2" /></svg>
              <span className="absolute -right-0.5 -top-0.5 inline-flex h-5 min-w-[1.25rem] items-center justify-center rounded-full bg-orange-600 px-1 text-xs font-bold text-white">2</span>
            </a>
          </div>
        </div>
        <nav aria-label="Categories" className="mx-auto max-w-6xl overflow-x-auto px-4 sm:px-6">
          <ul className="flex gap-1 whitespace-nowrap">
            {cats.map((c) => (
              <li key={c}>
                <button type="button" aria-pressed={cat === c} onClick={() => setCat(c)} className={`relative min-h-[44px] px-3 text-sm font-medium transition focus:outline-none focus-visible:ring-2 focus-visible:ring-orange-600 ${cat === c ? "text-orange-700" : c === "Sale" ? "text-rose-700 hover:text-rose-800" : "text-slate-700 hover:text-slate-900"}`}>
                  {c}
                  {cat === c && <span className="absolute inset-x-3 bottom-0 h-0.5 rounded-full bg-orange-600" />}
                </button>
              </li>
            ))}
          </ul>
        </nav>
      </header>
      <div className="mx-auto grid max-w-6xl grid-cols-2 gap-4 px-6 py-8 sm:grid-cols-4">
        {catalogue.slice(0, 4).map((c, i) => (
          <div key={c}><div className={`h-24 rounded-xl ${["bg-orange-100", "bg-amber-100", "bg-stone-200", "bg-rose-100"][i]}`} /><p className="mt-2 text-sm font-medium text-slate-800">{c}</p><p className="text-sm text-slate-600">Showing {cat.toLowerCase()}</p></div>
        ))}
      </div>
    </div>
  );
}

Install with one command: npx shadcn@latest add https://we.inc/r/navbar-with-search-field-and-category-bar.json

When to use it

How to customize it

Accessibility

Questions

How do I add live search suggestions?

Filter your data on each change of the input and render the first few matches in an absolutely positioned list under the field. Hide it when the field loses focus.

How do I make a category bar scroll on mobile?

Wrap the list in an element with `overflow-x-auto` and keep the items on one line with `whitespace-nowrap`.

Should categories be links or buttons?

If each category is its own page, use links. If they filter the current page, buttons with a pressed state are the better fit.

How do I show the basket count accessibly?

Put the count inside the visible badge and repeat it in the link's `aria-label` so it is announced.

Adapted from Navbar: Search and Categories in Meraki UI, used under its open-source licence. Licences and credits

Related: Black Glass Pill Navbar with a Mercury Drop That Stretches to the Active Link, Navbar with Centered Links, CTA Button and Mobile Menu, Navbar with Liquid Stretch Highlight and Inverting Labels.

More navbars or all free UI components. Start for free to have a full site built around it.

More in Navbars

We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.

Product

Who It's For

Features

Resources

Company

View Sitemap