Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
  1. Home
  2. Blog
  3. Build it: screens and apps
  4. Shadcn Navbar: Mega Menu, Mobile Menu, Sticky Header
Auf Deutsch lesen
  1. Blog
  2. Build it: screens and apps
  3. Shadcn Navbar: Mega Menu, Mobile Menu, Sticky Header
Build it: screens and apps
Build it: screens and apps

Shadcn Navbar: Mega Menu, Mobile Menu, Sticky Header

SR
Serban Rusu · Founder of Wingo UI
Nov 5, 2026 · 9 min read

On this page

0%
  1. What does a shadcn navbar need on a SaaS site?
  2. How do you build a shadcn navbar with a mega menu?
    1. Which props matter most?
  3. How do you make the shadcn navigation menu mobile friendly?
    1. When is a bottom drawer the better phone menu?
  4. How do you keep a sticky navbar free of layout shift?
  5. What about keyboard and screen reader support?
  6. Should you use the Marketing Header instead?
  7. Where does the navbar go next?
  8. FAQ
    1. Does shadcn/ui have a responsive navbar?
    2. How do I make a shadcn navigation menu work on mobile?
    3. How do I build a mega menu in Next.js?
    4. Why does my sticky navbar hide section headings when I jump to an anchor?
    5. Is the Wingo UI navbar free?

TL;DR

A good shadcn navbar has three parts: a sticky bar of fixed height, dropdown or mega menu panels that open on hover and from the keyboard, and a separate phone menu with every link in it. shadcn/ui ships the Navigation Menu primitive but no phone menu, so you pair it with a Sheet yourself. The free Wingo UI Nav Menu handles the panels and the phone menu in one component: one morphing panel on desktop, a full-screen accordion menu below 768px, chosen by CSS container queries so the server HTML is already right.

Published Nov 5, 2026

You want a shadcn navbar for a SaaS site: a logo, Product and Resources dropdowns with a mega menu, Pricing, Sign in, Get started, a bar that sticks while the page scrolls, and a phone menu that does not feel like an afterthought. shadcn/ui gives you the Navigation Menu (opens in a new tab) for the desktop part. Its docs page (checked November 5, 2026) covers the menu parts and wrapping the Next.js Link, and says nothing about phones. The phone menu, the sticky bar and the layout shift are left to you.

This guide builds that navbar with two Wingo UI components: the Nav Menu for a custom header and the Marketing Header when you want the whole thing ready. We build that library, so we are not neutral. Every snippet uses the real props, and both components are free.

What does a shadcn navbar need on a SaaS site?

Five things, and the last two take the most work:

  1. A fixed-height bar. 56 or 64px, with the logo at the start and one or two actions at the end. The height never changes after the page loads.
  2. Dropdown panels. A short list for Resources, a mega menu for Product: icons, one-line descriptions, two columns, maybe a featured card for the latest launch.
  3. Keyboard support. Tab reaches every trigger and link, arrows move between them, Escape closes a panel and returns focus.
  4. A real phone menu. Every link, the panels as collapsible groups, Sign in and Get started as full-width buttons, rows tall enough for a thumb.
  5. No layout shift. The server HTML must already show the right layout for the screen, and opening a panel or the phone menu must not move the page.

Navbar galleries sell finished layouts. shadcnblocks (opens in a new tab) lists 21+ navbar blocks next to its All Access offer, and Aceternity's Resizable Navbar (opens in a new tab) shrinks and blurs once the page scrolls, has its own mobile menu and installs with the shadcn CLI (both checked November 5, 2026). The Aceternity page documents props but says nothing about keyboard or focus handling. If you want a striking visual layout to copy, those galleries are a fine place to start. If you want the behavior in items 3 to 5 done for you, read on.

How do you build a shadcn navbar with a mega menu?

Describe the links as data and pass them to the Nav Menu: an item with items becomes a dropdown, and columns plus featured turn it into a mega menu. Install it first:

bash
npx wingo-ui@latest add nav-menu

The CLI copies components/ui/nav-menu.tsx and the Wingo UI files it imports: the Sheet and Accordion for the phone menu, Button, Badge, the motion helpers and the shared navigation helpers in lib/navigation.tsx. It also installs the npm packages they need, among them @radix-ui/react-navigation-menu, motion and lucide-react.

Plain data keeps a Next.js mega menu readable. An item with only an href is a plain link in the bar.

tsx
// components/site-header.tsx
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { BookOpen, ChartColumn, FileText, LifeBuoy, Newspaper, Send, Wallet } from "lucide-react"
import { Button } from "@/components/ui/button"
import { NavMenu, type NavMenuItem } from "@/components/ui/nav-menu"
import { NavLink } from "@/lib/navigation"
const items: NavMenuItem[] = [
{
label: "Product",
href: "/product",
columns: 2,
items: [
{ label: "Invoicing", href: "/product/invoicing", icon: <FileText />, description: "Invoices and estimates in 30 seconds" },
{ label: "E-invoicing", href: "/product/e-invoicing", icon: <Send />, description: "Sent and validated automatically" },
{ label: "Payments", href: "/product/payments", icon: <Wallet />, description: "Card payments and bank matching" },
{ label: "Reports", href: "/product/reports", icon: <ChartColumn />, description: "Sales tax, overdue and cash flow" },
],
featured: {
title: "New: e-invoices sent automatically",
description: "Every invoice reaches your client with zero extra clicks.",
image: "/images/e-invoicing.jpg",
href: "/changelog/e-invoicing",
},
},
{
label: "Resources",
items: [
{ label: "Guides", href: "/guides", icon: <BookOpen />, description: "Step by step, with screenshots" },
{ label: "Blog", href: "/blog", icon: <Newspaper />, description: "Product news and tips" },
{ label: "Help center", href: "/help", icon: <LifeBuoy />, description: "Answers in 2 minutes" },
],
},
{ label: "Pricing", href: "/pricing" },
{ label: "Contact", href: "/contact" },
]
export function SiteHeader() {
return (
<header className="sticky top-0 z-sticky border-b border-foreground/10 bg-background px-4 md:px-6">
<NavMenu
items={items}
currentPath={usePathname()}
linkComponent={Link}
logo={
<NavLink href="/" component={Link} className="text-[15px] font-semibold">
SimpleInvoice
</NavLink>
}
actions={
<>
<Button variant="ghost" size="sm" asChild>
<NavLink href="/sign-in" component={Link}>Sign in</NavLink>
</Button>
<Button size="sm" asChild>
<NavLink href="/sign-up" component={Link}>Get started</NavLink>
</Button>
</>
}
/>
</header>
)
}

Render <SiteHeader /> at the top of your root layout. Because Product has its own href, its panel starts with a "View all" row to /product. currentPath marks the active link (the longest matching href) and puts the active look on the top-level item above it. On Next.js you can set the link component and path once for every navigation component with a LinkProvider from @/lib/navigation, and drop linkComponent and currentPath from the call.

Hover Product, then slide to Resources: one panel follows the pointer, resizes and swaps its content. In a narrow column the links fold into the menu button.
$ npx wingo-ui@latest add nav-menu
FreeNav Menu docs

Which props matter most?

  • viewport (default true): one shared panel that glides between triggers and resizes to the new content, the Stripe-style header. Set it to false to open each panel as its own surface under its trigger.
  • indicator: "pill" (default), "underline" or "none", the highlight that follows the pointer and rests on the active section.
  • size: "md" gives a 64px bar with 36px triggers, "sm" a 56px bar with 32px triggers. Triggers grow to 44px on touch screens.
  • mobileBreakpoint: "md" (768px, default) or "lg" (1024px) for menus that do not fit a tablet.
  • delayDuration (150ms) and skipDelayDuration (300ms): how long a pointer rests before a panel opens, and the window in which the next one opens at once, so a cursor passing over the bar does not flash menus.

How do you make the shadcn navigation menu mobile friendly?

Below the breakpoint, replace the links with a menu button that opens a full-screen sheet holding every link, with each dropdown as an accordion section. shadcn/ui leaves this half of a responsive shadcn navbar to you; the Nav Menu does it in the same component, and its menu button also replaces the actions. Tap it and a full-screen Sheet slides in from the right:

  • the logo at the top, so the menu still feels like your site;
  • one accordion section per dropdown, with the section you are in already open;
  • 48px rows, and the featured card as a compact row;
  • your actions stacked full width at the bottom (pass mobileActions to use different ones there).

Choosing a link, the close button, a swipe to the right or Escape closes it. The menu traps focus and locks the page scroll while it is open; set modal={false} only for inline previews. One detail that is easy to miss: if the phone menu is open and the screen grows past the breakpoint (a tablet rotated to landscape), the Nav Menu closes it, so nobody is left with a full-screen overlay over a desktop layout.

When is a bottom drawer the better phone menu?

When the site has four or five plain links and no dropdowns. A full-screen menu is more than that needs, and a bottom sheet keeps the links near the thumb. Build it with the Drawer and close it from each link with DrawerClose:

tsx
"use client"
import Link from "next/link"
import { Menu } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Drawer, DrawerClose } from "@/components/ui/drawer"
import { NavLink } from "@/lib/navigation"
const links = [
{ label: "Features", href: "/features" },
{ label: "Pricing", href: "/pricing" },
{ label: "Blog", href: "/blog" },
]
export function PhoneMenu() {
return (
<Drawer
title="Menu"
hideTitle
trigger={
<Button variant="ghost" size="icon" radius="full" aria-label="Open menu" className="md:hidden">
<Menu />
</Button>
}
footer={
<Button size="lg" fullWidth asChild>
<NavLink href="/sign-up" component={Link}>Get started</NavLink>
</Button>
}
>
<nav aria-label="Main" className="flex flex-col">
{links.map((link) => (
<DrawerClose asChild key={link.href}>
<NavLink href={link.href} component={Link} className="flex min-h-12 items-center rounded-[12px] px-3 text-[15px] font-medium hover:bg-foreground/5">
{link.label}
</NavLink>
</DrawerClose>
))}
</nav>
</Drawer>
)
}

The trigger hides itself from 768px with md:hidden; the desktop links in your bar take hidden md:flex. Both are CSS, which matters for the next question.

How do you keep a sticky navbar free of layout shift?

Decide the layout with CSS so the server HTML is already right, keep panels and scroll locks out of the page flow, pad anchor jumps by the bar's height, and keep that height constant. These are the four usual causes of shift in a Next.js responsive navbar, with the fix for each.

The server renders the wrong layout. A useIsMobile() hook is false on the server, so a phone first gets the desktop links, then swaps to the hamburger after hydration. The Nav Menu decides with CSS container queries on the bar instead, so the HTML from the server is already the phone layout on a phone. A side effect: the breakpoint follows the bar's width, so a header inside a narrow column folds even on a wide screen.

The panel pushes content. Panels render in an absolutely positioned viewport under the bar, so opening one never moves the page below it.

Scroll lock moves the page. Hiding the scrollbar when a menu opens widens the page by the scrollbar's width. The Sheet is built on Radix Dialog and the Drawer on vaul, which wraps Radix Dialog; its scroll lock adds that width back as spacing, so the page stays put.

Anchors land under the bar. Jumping to #pricing scrolls the heading to the top of the viewport, behind your sticky bar. One line of CSS fixes it:

css
/* app/globals.css: the bar is 64px */
html {
scroll-padding-top: 4rem;
}

Keep the bar's height constant too. If you want it to look different after scrolling, change its background or shadow, which do not move anything.

What about keyboard and screen reader support?

The Nav Menu is fully keyboard operable and renders a labeled nav landmark, not an application menu. It sits on the Radix navigation menu primitive, and the Radix docs (opens in a new tab) (checked November 5, 2026) explain why it deliberately avoids the WAI-ARIA menu role: menus carry composite focus management and first-character navigation that website navigation does not need. In the Nav Menu, Tab reaches every trigger and link, ArrowLeft and ArrowRight move between triggers, Enter or Space opens a panel, ArrowDown opens it and lands on its first link, and Escape closes it and returns focus to the trigger. The nav carries an aria-label ("Main" by default, in labels.navigation), the menu button switches between "Open menu" and "Close menu", and under reduced motion panels fade without sliding.

The Marketing Header adds a "Skip to content" link as the first Tab stop, pointed at #main by default through skipTo.

Should you use the Marketing Header instead?

Use it when you want a finished SaaS header rather than parts. It wraps the Nav Menu with a logo, Sign in and Get started, an optional announcement strip, the skip link and sample navigation, and adds three layouts (default, centered, pill) and three looks (glass blurs the page under the sticky bar, solid, transparent for a hero).

bash
npx wingo-ui@latest add marketing-header
tsx
import { MarketingHeader } from "@/components/blocks/marketing-header"
<MarketingHeader
items={items}
linkComponent={Link}
currentPath={usePathname()}
announcement="SimpleInvoice 3.0 is here: e-invoicing and reports"
announcementHref="/changelog"
ctaTone="brand"
/>

One difference to know: the Marketing Header folds into the phone menu below 1024px, because a full SaaS bar with two actions rarely fits a tablet. If you need the 768px switch, use the Nav Menu directly as above.

Scroll inside the preview: the bar sticks and blurs what passes under it. Tap the menu button for the phone menu, then press Escape or swipe it away.
$ npx wingo-ui@latest add marketing-header
FreeMarketing Header docs

Where does the navbar go next?

The navbar is the first thing on every page, so it pays to get it right once. The Next.js SaaS landing page guide walks through the sections under it, the SaaS website template guide covers the pages the navbar links to, and the Landing Page template ships with this Marketing Header already in place. The Build it category and its pillar, the Next.js admin dashboard tutorial, cover the screens behind the Get started button.

The Marketing Header, Nav Menu, Sheet and Drawer are all free. Run npx wingo-ui@latest add marketing-header, swap in your logo and links, and check it on a real phone.

Components in this post

  • Marketing Header

    A React marketing header for a SaaS site: a sticky navbar with logo, dropdown panels, Sign in, Get started, a news strip and a phone menu.

    Free
  • Nav Menu

    A React navigation menu for websites: links and dropdown panels, lists or mega menus with a featured card, morphing through one panel.

    Free
  • Sheet

    A React sheet that slides in from any edge on a spring for filters, a cart, an inspector or phone navigation, swipeable to close on touch.

    Free
  • Drawer

    A React drawer and bottom sheet built on vaul: drag to dismiss, snap points, nesting, and a side panel or dialog on desktop.

    Free

FAQ

Does shadcn/ui have a responsive navbar?

Not as one component. shadcn/ui has a Navigation Menu for the desktop links and panels, and its docs (checked November 5, 2026) say nothing about a hamburger or phone layout, so you add a Sheet for the phone menu yourself or install a navbar block from a gallery.

How do I make a shadcn navigation menu work on mobile?

Hide the Navigation Menu below a breakpoint with CSS and render a menu button that opens a Sheet with the same links, with each dropdown as an accordion section and rows at least 44px tall. Close the sheet when a link is chosen and when the screen grows past the breakpoint.

How do I build a mega menu in Next.js?

Give a top-level item a list of child links with icons and descriptions, lay them out in two or three columns and add a featured card. With the Wingo UI Nav Menu that is data: an item with items, columns: 2 and featured: { title, href, image }.

Why does my sticky navbar hide section headings when I jump to an anchor?

The browser scrolls the target to the top of the viewport, under the sticky bar. Add scroll-padding-top to the html element with the bar's height, for example 4rem for a 64px bar.

Is the Wingo UI navbar free?

Yes. The Marketing Header, Nav Menu, Sheet and Drawer are all in the free tier, and npx wingo-ui@latest add marketing-header copies the header and everything it imports into your project as source.

Try it in your project

Marketing Header

Free

A React marketing header for a SaaS site: a sticky navbar with logo, dropdown panels, Sign in, Get started, a news strip and a phone menu.

75 components free, no account needed. Pro from $8 a month.

Open Marketing Header liveSee pricing
  • Navbar
  • Navigation
  • shadcn/ui
  • Next.js
  • Mobile UI

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Mobile UI in React

100vh Mobile Fix: When to Use dvh, svh or lvh in Tailwind

The 100vh mobile bug explained: why h-screen hides your bottom bar, when to use dvh, svh or lvh in Tailwind, and how to keep a bar above the keyboard.

SRSerban Rusu·Oct 9, 2026·11 min read
Mobile UI in React

React Bottom Sheet for the Web: Snap Points, Drag, Keyboard

Build a React bottom sheet for the web on vaul: snap points, drag to dismiss, nested sheets, the on-screen keyboard and the home indicator, with working code.

SRSerban Rusu·Oct 9, 2026·9 min read
Next.js and Tailwind CSS

React Use Mobile Hook: SSR-Safe, No Hydration Mismatch

Why a React use mobile hook is false on the server, when CSS breakpoints beat it, and an SSR-safe useSyncExternalStore version with no hydration mismatch.

SRSerban Rusu·Oct 9, 2026·9 min read
Newsletter

Get new posts by email

New posts, product news and occasional offers from Wingo UI. We count clicks to keep it relevant.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • For teams
  • Theme generator

Resources

  • Docs
  • Contact
  • Programs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Compare

  • Wingo UI vs paid kits
  • Wingo UI vs shadcn/ui
  • shadcn/ui alternatives
  • MUI alternatives
  • Tailwind Plus alternatives
  • Magic UI alternatives
  • Free components

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI