A dark header for a software product. The Products button opens a two-column panel of four items, each with an icon, a name and a one-line description, and a footer strip for a changelog link. A search field with a slash key hint, a log-in link and a primary button sit on the right. Under 1024 pixels everything moves into a menu panel: search first, then Products as an expandable group, then the other links and both buttons.
import { useState } from "react";
const products = [
{ name: "Pipelines", text: "Build, test and release from one file.", icon: "M4 6h5v5H4zM15 13h5v5h-5zM9 8.5h3.5a2.5 2.5 0 0 1 2.5 2.5v2" },
{ name: "Preview links", text: "A shareable address for every branch.", icon: "M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1" },
{ name: "Monitors", text: "Uptime checks with alerts that page you.", icon: "M3 12h4l2.5-6 4 12 2.5-6h5" },
{ name: "Secrets", text: "Encrypted keys, scoped per environment.", icon: "M7 11V8a5 5 0 0 1 10 0v3M5.5 11h13v9h-13zM12 15v2" },
];
const links = ["Solutions", "Docs", "Pricing"];
export default function DarkNavbarWithDropdownAndSearch() {
// Both start open so the preview shows the dropdown. Set to false on a real site.
const [productsOpen, setProductsOpen] = useState(true);
const [menuOpen, setMenuOpen] = useState(true);
const focus = "focus:outline-none focus-visible:ring-2 focus-visible:ring-violet-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950";
const searchField = (id: string) => (
<div className="relative">
<label htmlFor={id} className="sr-only">Search</label>
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400">
<circle cx="9" cy="9" r="5.5" stroke="currentColor" strokeWidth="1.6" />
<path d="m13.5 13.5 3.5 3.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" />
</svg>
<input
id={id}
type="search"
placeholder="Search docs and projects"
className="h-10 w-full rounded-md border border-white/10 bg-white/5 pl-9 pr-10 text-sm text-white placeholder:text-slate-400 focus:border-violet-400 focus:outline-none focus:ring-1 focus:ring-violet-400"
/>
<kbd aria-hidden="true" className="pointer-events-none absolute right-2 top-1/2 hidden h-6 w-6 -translate-y-1/2 items-center justify-center rounded border border-white/10 font-sans text-xs text-slate-400 lg:flex">/</kbd>
</div>
);
const productList = (
<ul className="grid gap-1 sm:grid-cols-2">
{products.map((p) => (
<li key={p.name}>
<a href="#" className={`flex items-start gap-3 rounded-md p-3 transition hover:bg-white/5 ${focus}`}>
<span className="flex h-9 w-9 flex-none items-center justify-center rounded-md bg-violet-500/15 text-violet-300">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-5 w-5">
<path d={p.icon} stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
<span>
<span className="block text-sm font-semibold text-white">{p.name}</span>
<span className="mt-0.5 block text-sm leading-snug text-slate-400">{p.text}</span>
</span>
</a>
</li>
))}
</ul>
);
return (
<div className="relative bg-slate-950 lg:h-[440px]" onKeyDown={(e) => e.key === "Escape" && setProductsOpen(false)}>
<header className="relative z-10 border-b border-white/10">
<div className="mx-auto flex h-16 max-w-6xl items-center gap-8 px-4 sm:px-6">
<a href="#" className={`flex flex-none items-center gap-2.5 rounded-md ${focus}`}>
<svg viewBox="0 0 24 24" aria-hidden="true" className="h-6 w-6">
<path d="M3 5h18v4H3z" className="fill-violet-400" />
<path d="M3 11h12v4H3z" className="fill-violet-500" />
<path d="M3 17h6v3H3z" className="fill-violet-700" />
</svg>
<span className="text-base font-semibold tracking-tight text-white">Quillstack</span>
</a>
<nav aria-label="Main" className="hidden lg:block">
<ul className="flex items-center gap-1">
<li className="relative">
<button
type="button"
aria-expanded={productsOpen}
aria-controls="quillstack-products"
onClick={() => setProductsOpen(!productsOpen)}
className={`inline-flex h-10 items-center gap-1.5 rounded-md px-3 text-sm font-medium transition ${focus} ${productsOpen ? "bg-white/10 text-white" : "text-slate-300 hover:text-white"}`}
>
Products
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" className={`h-4 w-4 transition-transform ${productsOpen ? "rotate-180" : ""}`}>
<path d="m5.5 8 4.5 4.5L14.5 8" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
{productsOpen && (
<div id="quillstack-products" className="absolute left-0 top-full mt-3 w-[600px] overflow-hidden rounded-xl border border-white/10 bg-slate-900 shadow-2xl shadow-black/60">
<div className="p-2">{productList}</div>
<a href="#" className={`flex items-center justify-between border-t border-white/10 bg-white/5 px-5 py-3.5 text-sm ${focus}`}>
<span className="text-slate-300">
<span className="mr-2 rounded bg-violet-500/20 px-1.5 py-0.5 text-xs font-semibold text-violet-200">New</span>
Rollbacks now finish in under ten seconds
</span>
<span className="font-medium text-violet-300">Read the changelog</span>
</a>
</div>
)}
</li>
{links.map((l) => (
<li key={l}>
<a href="#" className={`inline-flex h-10 items-center rounded-md px-3 text-sm font-medium text-slate-300 transition hover:text-white ${focus}`}>{l}</a>
</li>
))}
</ul>
</nav>
<div className="ml-auto flex items-center gap-3">
<div className="hidden w-64 lg:block">{searchField("quillstack-search")}</div>
<a href="#" className={`hidden h-10 items-center rounded-md px-3 text-sm font-medium text-slate-300 hover:text-white lg:inline-flex ${focus}`}>Log in</a>
<a href="#" className={`hidden h-10 items-center rounded-md bg-violet-600 px-4 text-sm font-semibold text-white transition hover:bg-violet-500 sm:inline-flex ${focus}`}>Deploy now</a>
<button
type="button"
aria-expanded={menuOpen}
aria-controls="quillstack-mobile"
aria-label={menuOpen ? "Close menu" : "Open menu"}
onClick={() => setMenuOpen(!menuOpen)}
className={`inline-flex h-10 w-10 items-center justify-center rounded-md border border-white/10 text-slate-200 hover:bg-white/5 lg:hidden ${focus}`}
>
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" className="h-5 w-5">
<path d={menuOpen ? "M6 6l12 12M18 6 6 18" : "M4 7h16M4 12h16M4 17h16"} stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
</svg>
</button>
</div>
</div>
{menuOpen && (
<div id="quillstack-mobile" className="border-t border-white/10 px-4 pb-5 pt-4 sm:px-6 lg:hidden">
{searchField("quillstack-search-mobile")}
<nav aria-label="Mobile" className="mt-3">
<button
type="button"
aria-expanded={productsOpen}
onClick={() => setProductsOpen(!productsOpen)}
className={`flex h-12 w-full items-center justify-between rounded-md px-1 text-base font-medium text-white ${focus}`}
>
Products
<svg viewBox="0 0 20 20" fill="none" aria-hidden="true" className={`h-5 w-5 text-slate-400 transition-transform ${productsOpen ? "rotate-180" : ""}`}>
<path d="m5.5 8 4.5 4.5L14.5 8" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</button>
{productsOpen && <div className="-mx-2 mb-2 border-b border-white/10 pb-2">{productList}</div>}
<ul>
{links.map((l) => (
<li key={l}>
<a href="#" className={`flex h-12 items-center rounded-md px-1 text-base font-medium text-slate-200 ${focus}`}>{l}</a>
</li>
))}
</ul>
</nav>
<div className="mt-4 grid grid-cols-2 gap-3">
<a href="#" className={`inline-flex h-11 items-center justify-center rounded-md border border-white/15 text-sm font-semibold text-white ${focus}`}>Log in</a>
<a href="#" className={`inline-flex h-11 items-center justify-center rounded-md bg-violet-600 text-sm font-semibold text-white ${focus}`}>Deploy now</a>
</div>
</div>
)}
</header>
{/* Stand-in for the page under the menu, so the open dropdown has something to sit on. */}
<div aria-hidden="true" className="mx-auto hidden max-w-6xl px-6 pt-14 lg:block">
<div className="ml-auto w-[340px] space-y-4">
<div className="h-8 w-72 rounded bg-white/5" />
<div className="h-8 w-56 rounded bg-white/5" />
<div className="h-3 w-full rounded bg-white/5" />
<div className="h-3 w-4/5 rounded bg-white/5" />
<div className="flex gap-3 pt-3">
<div className="h-10 w-32 rounded-md bg-white/10" />
<div className="h-10 w-28 rounded-md bg-white/5" />
</div>
</div>
</div>
</div>
);
}
Install with one command: npx shadcn@latest add https://we.inc/r/dark-navbar-with-dropdown-and-search.json
Give the list item `relative`, put a button inside it that toggles a state value, and render the panel with `absolute left-0 top-full` when the value is true. The button needs `aria-expanded` so assistive technology knows whether the panel is showing.
Click is the safer default. It works the same on touch screens and keyboards, and it does not open by accident when the pointer crosses the bar. This component opens on click and closes on Escape or a second click.
Add a `useEffect` that listens for `pointerdown` on the document and closes the menu when the event target is not inside the list item. Attach a `ref` to the list item to make that check.
Add a `keydown` listener in a `useEffect`. When the key is `/` and the focused element is not already an input, call `preventDefault()` and focus the search input through a `ref`.
The header needs to sit above the content that follows it. This component puts `relative z-10` on the header. If your hero creates its own stacking layer, raise the header to `z-40` or higher.
Related: Navbar with Centered Links, CTA Button and Mobile Menu, Store Navbar with Announcement Bar and Category Row, Dark Footer with Large Call to Action, Button Set: Primary, Secondary, Ghost, Icon and Loading.
More navbars or all free UI components. Start for free to have a full site built around it.
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.