Site header
Responsive header with primary navigation and a mobile drawer.
Installation
npx shadcn@latest add http://localhost:3000/r/site-header.jsonMKT UI is not published yet, so this points at your local development server. Set NEXT_PUBLIC_REGISTRY_URL to the deployed origin to show the public command.
Also installs: button, sheet, container, phone-link.
Usage
import { Button } from "@/components/ui/button";
import { SiteHeader } from "@/components/site-header";
<SiteHeader
logo="Northgate Plumbing"
logoHref="/"
navigation={[
{ label: "Services", href: "/services", current: true },
{ label: "Service areas", href: "/areas" },
{ label: "Reviews", href: "/reviews" },
{ label: "FAQ", href: "/faq" },
]}
phone={{ number: "(512) 555-0142", label: "Call" }}
action={<Button asChild><a href="/quote">Request a quote</a></Button>}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| logo (required) | ReactNode | — | Business name or logo element. A text wordmark is a fine default. |
| logoHref | string | "/" | Where the logo links. |
| navigation | HeaderNavItem[] | [] | Primary links. Keep to about five — more and nothing stands out. |
| action | ReactNode | — | Primary CTA. Shown on desktop and at the bottom of the mobile drawer. |
| phone | { number, dial?, label? } | — | Tap-to-call number. Local businesses should set this. |
| sticky | boolean | true | Stick to the top of the viewport on scroll. |
| menuLabel | string | "Open menu" | Accessible name for the mobile menu button. |
HeaderNavItem
| Prop | Type | Default | Description |
|---|---|---|---|
| label (required) | string | — | Link text. |
| href (required) | string | — | Destination. |
| current | boolean | — | Marks the current page. Sets `aria-current="page"`. |
Behavior
Height is fixed at 4rem and matches the --mkt-header-height token that
scroll-padding-top uses. That is what stops an in-page anchor link from landing with
its heading hidden underneath the sticky header. Change the height and update the
token:
:root { --mkt-header-height: 5rem; }The mobile menu is a shadcn Sheet. Escape to close, focus trapped while open, and
focus returned to the trigger button on close — all of which is why it is a Sheet
rather than a hand-rolled useState panel. Tapping a link closes the drawer.
Navigation collapses below lg (1024px), not md. Four or five marketing links
plus a phone number and a CTA genuinely do not fit at tablet width.
Client component
This section carries "use client" for the drawer state. You can still place it
directly in a server component — the boundary is inside the section.
Limitations
- Single-level navigation only. Dropdowns and mega menus (
nav-dropdown,nav-mega-menu) are not implemented yet. - No announcement bar, breadcrumbs, or mobile action bar yet.
- Uses plain
<a>elements, so a Next.js app does not get client-side navigation from the header. Swapping innext/linkis a one-line edit in your installed copy — the section stays framework-neutral so it also works in Vite and Astro. - No transparent-over-hero variant yet.
Related
- Composition — anchor links and sticky headers