# Navbar Section v10

> Edra-inspired two-column navbar with logo chip and hamburger that expands in-place into a dialog card with primary and footer links, plus a Sign up CTA.

A shadcn/ui block from the [@uiception](https://uiception.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uiception/navbar-section-v10).

- Page, with a live preview: https://shadcn.exchange/r/uiception/navbar-section-v10
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uiception/navbar-section-v10-light-74431b2140bbacd3.png
- Registry item: https://uiception.com/r/navbar-section-v10.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://uiception.com/r/navbar-section-v10.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`, `motion`
- Registry items: `https://uiception.com/r/block-media.json`, `button`

## Files

### `registry/new-york/blocks/navbar-section-v10/page.tsx`

```tsx
import NavbarLoremDemo from "./components/navbar-lorem-demo"
import NavbarSectionV10 from "./components/navbar-section-v10"

export default function Page() {
  return (
    <>
      <NavbarSectionV10 />
      <NavbarLoremDemo />
    </>
  )
}
```

### `registry/new-york/blocks/navbar-section-v10/components/navbar-section-v10.tsx`

```tsx
import { NavbarSectionV10Root } from "./navbar-section-v10-root"
import { createBlockImage } from "@/lib/block-media"

const blockImage = createBlockImage("navbar-section-v10")
export default function NavbarSectionV10() {
  return <NavbarSectionV10Root logoSrc={blockImage("logo.svg")} />
}
```

### `registry/new-york/blocks/navbar-section-v10/components/navbar-section-v10-root.tsx`

```tsx
"use client"

import { useEffect, useRef, useState } from "react"
import Image from "next/image"
import Link from "next/link"
import {
  AnimatePresence,
  motion,
  useReducedMotion,
  type Variants,
} from "motion/react"

import { MenuIcon, XIcon } from "lucide-react"

import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"

const SITE_NAME = "uiception"
const LOGO_HREF = "#"
const CTA = { label: "Sign Up", href: "#" }

const PRIMARY_LINKS = [
  { label: "Features", href: "#" },
  { label: "Pricing", href: "#" },
  { label: "Docs", href: "#" },
] as const

const FOOTER_COLUMNS = [
  {
    label: "Connect",
    links: [
      { label: "GitHub", href: "#" },
      { label: "X", href: "#" },
    ],
  },
  {
    label: "Resources",
    links: [
      { label: "Blog", href: "#" },
      { label: "Changelog", href: "#" },
    ],
  },
] as const

const primaryLinkClass =
  "text-lg font-medium leading-tight text-foreground transition-colors hover:text-muted-foreground"

const footerLinkClass =
  "text-sm text-foreground transition-colors hover:text-muted-foreground"

const EASE_OUT = [0.22, 1, 0.36, 1] as const
const EASE_IN = [0.4, 0, 1, 1] as const

const cardVariants: Variants = {
  initial: {
    opacity: 0,
    scale: 0.88,
    y: -10,
  },
  animate: {
    opacity: 1,
    scale: 1,
    y: 0,
    transition: {
      duration: 0.42,
      ease: EASE_OUT,
      opacity: { duration: 0.28, ease: EASE_OUT },
    },
  },
  exit: {
    opacity: 0,
    scale: 0.94,
    y: -8,
    transition: {
      duration: 0.22,
      ease: EASE_IN,
    },
  },
}

const backdropVariants: Variants = {
  initial: { opacity: 0 },
  animate: {
    opacity: 1,
    transition: { duration: 0.28, ease: EASE_OUT },
  },
  exit: {
    opacity: 0,
    transition: { duration: 0.2, ease: EASE_IN },
  },
}

const reducedCardVariants: Variants = {
  initial: { opacity: 0 },
  animate: { opacity: 1, transition: { duration: 0.15 } },
  exit: { opacity: 0, transition: { duration: 0.1 } },
}

function BrandMark({
  className,
  logoSrc,
}: {
  className?: string
  logoSrc: string
}) {
  return (
    <>
      <Image
        src={logoSrc}
        alt=""
        width={20}
        height={20}
        unoptimized
        aria-hidden
        className="size-5 shrink-0"
      />
      <span className={cn("text-base font-medium", className)}>
        {SITE_NAME}
      </span>
    </>
  )
}

function BrandChip({ logoSrc }: { logoSrc: string }) {
  return (
    <Button asChild variant="secondary" size="lg">
      <Link
        href={LOGO_HREF}
        aria-label={`${SITE_NAME} home`}
        onClick={(e) => e.preventDefault()}
      >
        <BrandMark logoSrc={logoSrc} />
      </Link>
    </Button>
  )
}

function NavCard({
  onClose,
  reduceMotion,
  logoSrc,
}: {
  onClose: () => void
  reduceMotion: boolean
  logoSrc: string
}) {
  const handleLinkClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
    e.preventDefault()
    onClose()
  }

  return (
    <motion.nav
      aria-label="Primary"
      variants={reduceMotion ? reducedCardVariants : cardVariants}
      initial="initial"
      animate="animate"
      exit="exit"
      style={{ transformOrigin: "top left" }}
      className="w-[min(100vw-3rem,26.25rem)] origin-top-left rounded-md bg-muted p-2.5 will-change-transform"
    >
      <div className="flex items-center justify-between gap-3 px-1.5 pb-4 pt-1">
        <Link
          href={LOGO_HREF}
          aria-label={`${SITE_NAME} home`}
          className="inline-flex items-center gap-2.5"
          onClick={(e) => {
            e.preventDefault()
            onClose()
          }}
        >
          <BrandMark logoSrc={logoSrc} />
        </Link>
        <Button
          type="button"
          variant="ghost"
          size="default"
          aria-label="Close menu"
          onClick={onClose}
        >
          <XIcon strokeWidth={1.5} aria-hidden />
        </Button>
      </div>

      <ul className="flex flex-col gap-6 px-1.5 pb-6">
        {PRIMARY_LINKS.map((link) => (
          <li key={link.label}>
            <a
              href={link.href}
              className={primaryLinkClass}
              onClick={handleLinkClick}
            >
              {link.label}
            </a>
          </li>
        ))}
      </ul>

      <div className="grid grid-cols-2 gap-8 px-1.5 pb-2">
        {FOOTER_COLUMNS.map((column) => (
          <div key={column.label}>
            <p className="mb-3 text-xs uppercase tracking-wide text-muted-foreground">
              {column.label}
            </p>
            <ul className="flex flex-col gap-2">
              {column.links.map((link) => (
                <li key={link.label}>
                  <a
                    href={link.href}
                    className={footerLinkClass}
                    onClick={handleLinkClick}
                  >
                    {link.label}
                  </a>
                </li>
              ))}
            </ul>
          </div>
        ))}
      </div>
    </motion.nav>
  )
}

export function NavbarSectionV10Root({ logoSrc }: { logoSrc: string }) {
  const [menuOpen, setMenuOpen] = useState(false)
  const leftClusterRef = useRef<HTMLDivElement>(null)
  const reduceMotion = useReducedMotion() ?? false

  useEffect(() => {
    if (!menuOpen) return

    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape") setMenuOpen(false)
    }

    document.addEventListener("keydown", handleKeyDown)
    return () => document.removeEventListener("keydown", handleKeyDown)
  }, [menuOpen])

  useEffect(() => {
    if (!menuOpen) return

    const handlePointerDown = (event: MouseEvent) => {
      const target = event.target as Node
      if (leftClusterRef.current?.contains(target)) return
      setMenuOpen(false)
    }

    document.addEventListener("mousedown", handlePointerDown)
    return () => document.removeEventListener("mousedown", handlePointerDown)
  }, [menuOpen])

  return (
    <>
      <AnimatePresence>
        {menuOpen ? (
          <motion.button
            key="nav-backdrop"
            type="button"
            aria-label="Close menu"
            variants={backdropVariants}
            initial="initial"
            animate="animate"
            exit="exit"
            className="fixed inset-0 z-40 bg-background/40"
            onClick={() => setMenuOpen(false)}
          />
        ) : null}
      </AnimatePresence>

      <div className="sticky top-0 z-50 border-b border-border bg-background/80 backdrop-blur-sm">
        <header>
          <div className="mx-auto flex h-16 w-full max-w-7xl items-center justify-between gap-4 px-6">
            <div ref={leftClusterRef} className="relative z-50">
              {/* Anchor stays in flow so the header never reflows */}
              <div
                className={cn(
                  "flex items-center gap-1.5",
                  menuOpen && "invisible"
                )}
                aria-hidden={menuOpen}
              >
                <BrandChip logoSrc={logoSrc} />
                <Button
                  type="button"
                  variant="secondary"
                  size="lg"
                  aria-expanded={menuOpen}
                  aria-label="Open menu"
                  tabIndex={menuOpen ? -1 : undefined}
                  onClick={() => setMenuOpen(true)}
                >
                  <MenuIcon strokeWidth={1.5} aria-hidden />
                </Button>
              </div>

              <AnimatePresence>
                {menuOpen ? (
                  <div
                    key="nav-card"
                    className="absolute top-0 left-0 z-50"
                  >
                    <NavCard
                      onClose={() => setMenuOpen(false)}
                      reduceMotion={reduceMotion}
                      logoSrc={logoSrc}
                    />
                  </div>
                ) : null}
              </AnimatePresence>
            </div>

            <Button
              asChild
              className={cn(
                "relative z-50",
                menuOpen && "invisible sm:visible"
              )}
              aria-hidden={menuOpen}
              tabIndex={menuOpen ? -1 : undefined}
            >
              <Link href={CTA.href} onClick={(e) => e.preventDefault()}>
                {CTA.label}
              </Link>
            </Button>
          </div>
        </header>
      </div>
    </>
  )
}
```

### `registry/new-york/blocks/navbar-section-v10/components/navbar-lorem-demo.tsx`

```tsx
import { Instrument_Serif } from "next/font/google"

const instrumentSerif = Instrument_Serif({
  subsets: ["latin"],
  weight: ["400"],
})

function Spacer({ size = "md" }: { size?: "sm" | "md" | "lg" | "xl" }) {
  const heights = { sm: "h-4", md: "h-8", lg: "h-16", xl: "h-28" }
  return <div className={heights[size]} />
}

const ROW_KEYS = ["a", "b", "c", "d", "e"] as const
const COLUMN_KEYS = ["left", "center", "right"] as const

function Block({ rows = 1 }: { rows?: number }) {
  return (
    <div className="space-y-2">
      {ROW_KEYS.slice(0, rows).map((id) => (
        <div key={id} className="h-3" />
      ))}
    </div>
  )
}

export default function NavbarLoremDemo() {
  return (
    <main className="px-4 py-4 md:py-6 lg:py-8">
      <article
        aria-label="Sample document preview"
        className="mx-auto w-full max-w-2xl rounded-none border border-border bg-card text-card-foreground shadow-sm"
      >
        <header className="px-8 py-8 pt-10 sm:px-10">
          <span className={`${instrumentSerif.className} text-2xl text-foreground`}>
            uiception.com
          </span>
        </header>

        <div className="px-8 py-12 sm:px-10">
          <Spacer size="xl" />
          <Block rows={3} />
          <Spacer size="xl" />
          <Block rows={3} />
          <Spacer size="xl" />
          <div className="grid grid-cols-3 gap-6">
            {COLUMN_KEYS.map((id) => (
              <Block key={id} rows={3} />
            ))}
          </div>
          <Spacer size="xl" />
          <Block rows={3} />
          <Spacer size="xl" />
          <Block rows={3} />
          <Spacer size="xl" />
        </div>
      </article>
    </main>
  )
}
```

### `public/images/blocks/navbar-section-v10/logo.svg.gitkeep`

```gitkeep
Place your asset here. Same base name, any supported format: jpg, jpeg, png, webp, avif, gif, or svg. Remove the .gitkeep extension from the filename.
```

### `public/images/blocks/navbar-section-v10/logo.svg`

Left out to keep this page a readable size (780,247 characters). It is in the registry item above.
