# Header 3

A shadcn/ui block from the [@diarmuradi](https://diarmuradi.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/diarmuradi/header-3).

- Page, with a live preview: https://shadcn.exchange/r/diarmuradi/header-3
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/diarmuradi/header-3-light-5811faa45f0403d2.png
- Registry item: https://diarmuradi.com/r/header-3.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://diarmuradi.com/r/header-3.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://diarmuradi.com/r/styles/base-nova/button.json`, `https://diarmuradi.com/r/styles/base-nova/icons.json`, `https://diarmuradi.com/r/styles/base-nova/separator.json`

## Files

### `header.tsx`

```tsx
"use client"

import { useEffect, useId, useRef, useState } from "react"
import Link from "next/link"
import { AnimatePresence, motion, useReducedMotion } from "motion/react"

import { Button } from "@/components/ui/button"
import { Separator } from "@/components/ui/separator"
import { Icons } from "@/components/ui/icons"
import { XIcon } from 'lucide-react'

const NAV_LINKS = [
  { label: "Features", href: "#features" },
  { label: "Pricing", href: "#pricing" },
  { label: "About", href: "#about" },
  { label: "Blog", href: "#blog" },
]

export function SiteHeader() {
  const [isOpen, setIsOpen] = useState(false)
  const mid = Math.ceil(NAV_LINKS.length / 2)

  const menuId = useId()
  const reducedMotion = useReducedMotion()
  const menuRef = useRef<HTMLElement>(null)
  useEffect(() => {
    if (!isOpen) return
    const dismiss = (event: PointerEvent | KeyboardEvent) => {
      if (event instanceof KeyboardEvent) {
        if (event.key !== "Escape") return
        event.preventDefault()
        setIsOpen(false)
        menuRef.current
          ?.querySelector<HTMLButtonElement>('button[aria-expanded="true"]')
          ?.focus()
      } else if (
        event.target instanceof Node &&
        !menuRef.current?.contains(event.target)
      ) {
        setIsOpen(false)
      }
    }
    const viewportWidth = window.innerWidth
    const resize = () => {
      if (window.innerWidth === viewportWidth) return
      setIsOpen(false)
    }
    document.addEventListener("pointerdown", dismiss)
    document.addEventListener("keydown", dismiss)
    window.addEventListener("resize", resize)
    return () => {
      document.removeEventListener("pointerdown", dismiss)
      document.removeEventListener("keydown", dismiss)
      window.removeEventListener("resize", resize)
    }
  }, [isOpen])

  return (
    <header
      ref={menuRef}
      className="sticky top-0 z-50 flex w-full justify-center px-4"
      data-slot="header"
    >
      <nav
        aria-label="Main navigation"
        className="flex w-full max-w-md flex-col gap-2 sm:max-w-fit"
      >
        <div className="rounded-2xl bg-card shadow-elevated-2xs">
          <div className="flex h-14 items-center px-2 sm:hidden">
            <Button
              aria-label="Home"
              variant="ghost"
              size="icon-lg"

              render={<Link href="#" role="link" />}
              nativeButton={false}
            >
              <Icons.Logo
                aria-hidden="true"
                className="size-8 text-foreground"
              />
            </Button>

            <Separator
              orientation="vertical"
              className="mx-2 my-auto h-14 bg-border/50"
            />

            <div className="flex-1" />

            <Separator
              orientation="vertical"
              className="mx-2 my-auto h-14 bg-border/50"
            />

            <Button
              onClick={() => setIsOpen(!isOpen)}
              variant="ghost"
              className="w-8 p-0 text-muted-foreground transition-colors hover:bg-transparent hover:text-foreground hover:no-underline *:[&>svg]:size-5"
              aria-label={isOpen ? "Close Menu" : "Open Menu"}
              aria-expanded={isOpen}
              aria-controls={isOpen ? menuId : undefined}
            >
              {isOpen ? (
                <XIcon  aria-hidden="true" data-icon="inline-end" />
              ) : (
                <svg
                  aria-hidden="true"
                  viewBox="0 0 24 24"
                  fill="none"
                  stroke="currentColor"
                  strokeWidth={2}
                  strokeLinecap="round"
                  className="size-5"
                >
                  <path d="M5 9h14M5 15h14" />
                </svg>
              )}
            </Button>
          </div>

          <div className="group flex h-14 items-center justify-center gap-1 px-2 max-sm:hidden">
            {NAV_LINKS.map((link, i) => (
              <span key={link.label} className="contents">
                {i === mid && (
                  <>
                    <Separator
                      orientation="vertical"
                      className="mr-2 h-14 bg-border/50"
                    />
                    <Button
                      aria-label="Home"
                      variant="ghost"
                      size="icon-lg"

                      render={<Link href="#" role="link" />}
                      nativeButton={false}
                    >
                      <Icons.Logo
                        aria-hidden="true"
                        className="size-8 text-foreground"
                      />
                    </Button>
                    <Separator
                      orientation="vertical"
                      className="ml-2 h-14 bg-border/50"
                    />
                  </>
                )}

                <Button
                  variant="ghost"
                  className="text-muted-foreground transition-colors duration-200 ease-out group-has-[[data-nav-link]:hover]:text-muted-foreground/50 hover:text-foreground hover:no-underline hover:group-has-[[data-nav-link]:hover]:text-foreground"
                  data-nav-link=""
                  render={<Link href={link.href} role="link" />}
                  nativeButton={false}
                >
                  {link.label}
                </Button>
              </span>
            ))}
          </div>
        </div>

        <AnimatePresence>
          {isOpen && (
            <motion.div
              data-slot="navigation-menu-popup"
              id={menuId}
              initial={{ opacity: 0, y: reducedMotion ? 0 : -8 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: reducedMotion ? 0 : -8 }}
              transition={{
                duration: reducedMotion ? 0 : 0.2,
                ease: "easeOut",
              }}
              className="flex w-full flex-col gap-1 rounded-2xl bg-popover p-2 shadow-elevated-2xs sm:hidden"
            >
              {NAV_LINKS.map((link) => (
                <Button
                  key={link.href}
                  variant="ghost"
                  className="w-full justify-start"
                  onClick={() => setIsOpen(false)}
                  render={<Link href={link.href} role="link" />}
                  nativeButton={false}
                >
                  {link.label}
                </Button>
              ))}
            </motion.div>
          )}
        </AnimatePresence>
      </nav>
    </header>
  )
}
```
