The shadcn header is built by composing shadcn/ui's NavigationMenu primitive with Tailwind CSS, and every layer — mobile menu, dark mode, sticky scroll, search — bolts on top of the same base. This guide builds one shadcn ui header from the ground up in 5 steps, each with a live preview and full TypeScript code you can copy straight into your project.
A header is just one building block — explore all shadcn UI components for the rest of the set.
How Do You Install the shadcn NavigationMenu?
Every shadcn header in this guide starts from the same two primitives. Run both commands before you begin — the rest of the guide assumes both are already in components/ui/.
This installs navigation-menu.tsx, sheet.tsx, and command.tsx to components/ui/. Both Next.js App Router and Pages Router use the identical component code below — see the router differences section near the end for where each one gets imported.
Step 1: Build the Base shadcn Header with NavigationMenu
Start with a desktop-only header: a brand mark, a NavigationMenu for the primary links, and a call-to-action button. Every later step in this guide builds on top of this file.
NavigationMenuLink wraps asChild around a Next.js Link so routing stays client-side while Radix UI still manages focus and keyboard behaviour underneath. At this stage the header has no responsive fallback — Step 2 fixes that.
🎨 Not sold on stone and emerald? Every color here is a Tailwind CSS variable under the hood. Preview a different palette across this exact header shape with the free shadcn theme generator before you touch a single class name.
Step 2: Add a Mobile Menu with the shadcn Sheet Component
A shadcn header menu needs a mobile fallback. Hide the desktop NavigationMenu below the md breakpoint and swap in a Sheet that slides in from the right.
The preview below defaults to the mobile layout so the difference from Step 1 is visible immediately — on an actual desktop-width browser, a header using real md: classes would show the desktop nav instead. Use the toggle to compare both; in your app the switch happens automatically at the md breakpoint, no toggle required.
Recent entries
Field Guide 12
Cold mornings on the Kerry coast
Film 08
Notes on shooting Portra 400 in fog
Slow Travel 05
Twelve hours in a town with one road
Close the sheet with onClick={() => setOpen(false)} on every link — without it, the drawer stays open after navigation and the user has to dismiss it manually. This is the single most common shadcn header menu bug in production.
🚀 Skip wiring this up by hand. Every ShadcnDeck template ships with a header like this one already built — sticky, mobile Sheet menu, and dark mode toggle included. Browse free shadcn/ui templates →
Step 3: Add a Dark Mode Toggle to the shadcn Header
Add a segmented pill toggle that flips dark on the header's own state. In a real app, wire this to next-themes instead of local state so the whole site — not just the header — switches together.
Note where the colours live. NavigationMenuLink ships its own hover:bg-accent hover:text-accent-foreground defaults, and asChild merges the two class strings by concatenation — not through tailwind-merge. Put your hover colour on the child <Link> and it doesn't reliably win: the component's --accent-foreground can take over instead, which in a dark header means a near-black label on a near-black bar. Set the colour on NavigationMenuLink itself — that className does go through cn(), so the defaults are properly replaced.
Swap the local useState for useTheme() from next-themes in production — install it with npm install next-themes and wrap your root layout in <ThemeProvider attribute="class">.
Step 4: Make the shadcn Header Sticky on Scroll
Track window.scrollY and shrink the header once the user scrolls past 12px. Add a backdrop blur and border so the header lifts off the page instead of blending into it.
A real sticky header watches the page's own scroll position, so if you tried that inside a long blog post it would already read as "scrolled" the moment you reached this section. The preview below scopes the same logic to its own scrollable frame instead, purely so you can see both the resting and shrunk states without losing your place on the page.
Scroll inside the frame below ↓
Keep scrolling this frame to watch the header shrink, gain a border, and pick up a blurred backdrop.
That's the same window.scrollY > 12 check from the code sample below — just scoped to this box instead of the real page, so you can see both states without leaving this section.
Recent entries
Field Guide 12
Cold mornings on the Kerry coast
Film 08
Notes on shooting Portra 400 in fog
Slow Travel 05
Twelve hours in a town with one road
Film 07
The case for the 35mm compact
Slow Travel 04
Where the last ferry still runs
Field Guide 11
Packing light for a week of rain
Scroll back up to reset the header to its resting height.
Use { passive: true } on the scroll listener — it tells the browser your handler never calls preventDefault(), which keeps scrolling smooth on mobile. Always clean up the listener in the useEffect return function to avoid a memory leak on unmount.
Step 5: Add a Command Palette (⌘K) to the shadcn Header
Wire a global keyboard listener for ⌘K / Ctrl+K that opens shadcn's CommandDialog. This pattern is standard on developer tools and SaaS dashboards, and it is the final layer this guide adds to the header.
Command Palette
Search for a command to run...
Always call e.preventDefault() before toggling the dialog — without it, browsers intercept ⌘K as a "search this page" shortcut and your palette never opens. For a dashboard layout that pairs a header like this with a sidebar, see the shadcn resizable sidebar component guide.
⌘ Building a dashboard, not a marketing site? The ChatDeck SaaS template pairs a command-palette header with a full app shell, so you don't have to assemble Steps 1 through 5 yourself.
How Do You Use This shadcn Header in App Router vs Pages Router?
Every component above works identically in both routers. Only the import location changes, and any header using useState, useEffect, or an event listener needs the "use client" directive already present at the top of each file above.
Which shadcn Header Features Does Your Project Actually Need?
Not every project needs all 5 layers. Match the feature to the project type before you add complexity you will not use.
| Project type | Recommended steps |
|---|---|
| Portfolio or personal site | Step 1 + Step 2 |
| SaaS marketing page | Step 1, 2, 4 |
| Product with theme switching | Step 1, 2, 3 |
| Developer tool or dashboard | Step 1, 2, 5 |
| Full-featured product site | All 5 steps |
Is a shadcn Header Accessible by Default?
Yes. NavigationMenu, Sheet, and Command all build on Radix UI, which implements WAI-ARIA roles, keyboard navigation, and focus management without extra code from you. Three rules keep a custom shadcn header accessible as you extend it:
- Never remove the semantic wrapper. Keep content inside
<header>and letNavigationMenuLink asChildpass through to your ownLink— don't replace the whole primitive with plaindivelements. - Label icon-only controls. The hamburger, dark mode toggle, and search buttons above all carry
aria-label— screen readers have nothing else to announce them by. - Close overlays on navigation. Always reset
openstate when a link inside aSheetorCommandDialogis selected, or keyboard and screen reader users get trapped in a closed-but-still-mounted overlay.
One honest caveat for 2026: Radix UI's NavigationMenu is still the default primitive shadcn/ui scaffolds when you run the CLI, but not every shadcn primitive stayed on Radix. See Radix vs Base UI for what changed elsewhere in the component tree and why it's worth checking before you lock in a header pattern long-term.
Related Posts
Explore more shadcn component and build guides:
- shadcn resizable sidebar component guide - pair this header with a resizable dashboard shell
- how to build a landing page with Next.js and shadcn/ui
- shadcn card component guide - the section that usually sits right below your header
- shadcn tabs component guide
- Radix vs Base UI - what powers NavigationMenu, Sheet, and Command under the hood
- shadcn component libraries guide
- shadcn vs Material UI
- best React UI libraries in 2026





