# Shadcn Navbar: Mega Menu, Mobile Menu, Sticky Header

> Build a responsive shadcn navbar in Next.js: dropdown and mega menu panels on desktop, a full-screen phone menu below 768px, sticky, with keyboard support.

- Author: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Published: Nov 5, 2026
- Category: [Build it: screens and apps](https://wingo-ui.com/blog/category/build)
- Reading time: 9 min
- Canonical: https://wingo-ui.com/blog/shadcn-navbar

## 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.

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](https://ui.shadcn.com/docs/components/navigation-menu) 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](https://wingo-ui.com/components/nav-menu) for a custom header and the [Marketing Header](https://wingo-ui.com/components/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](https://www.shadcnblocks.com/blocks/navbar) lists 21+ navbar blocks next to its All Access offer, and Aceternity's [Resizable Navbar](https://ui.aceternity.com/components/resizable-navbar) 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](https://wingo-ui.com/components/sheet) and [Accordion](https://wingo-ui.com/components/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.

> Live demo (Nav Menu): 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. Try it at [Nav Menu](https://wingo-ui.com/components/nav-menu) and install it with `npx wingo-ui@latest add nav-menu`.

### 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](https://wingo-ui.com/components/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](https://www.radix-ui.com/primitives/docs/components/navigation-menu) (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](https://wingo-ui.com/components/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.

> Live demo (Marketing Header): 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. Try it at [Marketing Header](https://wingo-ui.com/components/marketing-header) and install it with `npx wingo-ui@latest add marketing-header`.

## 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](https://wingo-ui.com/blog/nextjs-saas-landing-page) walks through the sections under it, the [SaaS website template guide](https://wingo-ui.com/blog/saas-website-template) covers the pages the navbar links to, and the [Landing Page template](https://wingo-ui.com/templates/landing-page) ships with this Marketing Header already in place. The [Build it category](https://wingo-ui.com/blog/category/build) and its pillar, the [Next.js admin dashboard tutorial](https://wingo-ui.com/blog/nextjs-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](https://wingo-ui.com/components/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. Install: `npx wingo-ui@latest add marketing-header`
- [Nav Menu](https://wingo-ui.com/components/nav-menu) (Free): A React navigation menu for websites: links and dropdown panels, lists or mega menus with a featured card, morphing through one panel. Install: `npx wingo-ui@latest add nav-menu`
- [Sheet](https://wingo-ui.com/components/sheet) (Free): 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. Install: `npx wingo-ui@latest add sheet`
- [Drawer](https://wingo-ui.com/components/drawer) (Free): A React drawer and bottom sheet built on vaul: drag to dismiss, snap points, nesting, and a side panel or dialog on desktop. Install: `npx wingo-ui@latest add drawer`

## 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.

---

Source: https://wingo-ui.com/blog/shadcn-navbar
