# Header 5

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

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

## Install

```bash
npx shadcn@latest add https://diarmuradi.com/r/header-5.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: `cn`, `motion`, `react-use-measure`
- Registry items: `https://diarmuradi.com/r/styles/base-nova/button.json`, `https://diarmuradi.com/r/styles/base-nova/icons.json`

## Files

### `header.tsx`

```tsx
"use client"

import { useEffect, useId, useRef, useState } from "react"
import Link from "next/link"
import { cn } from "cn"
import {
  animate,
  AnimatePresence,
  motion,
  useMotionValue,
  useReducedMotion,
  useTransform,
} from "motion/react"
import useMeasure from "react-use-measure"

import { Button } from "@/components/ui/button"
import { Icons } from "@/components/ui/icons"

const links = [
  { label: "Home", href: "#" },
  { label: "About", href: "#about" },
  { label: "Blog", href: "#blog" },
  { label: "Contact", href: "#contact" },
]

export function SiteHeader() {
  const [isOpen, setIsOpen] = useState(false)
  const [measureRef, bounds] = useMeasure({ offsetSize: true })
  const navRef = useRef<HTMLElement>(null)
  const toggleRef = useRef<HTMLButtonElement>(null)
  const menuId = useId()
  const reducedMotion = useReducedMotion()
  const width = useMotionValue(192)
  const viewBox = useTransform(width, (value) => `0 0 ${value} 96`)
  const fill = useTransform(
    width,
    (value) =>
      `M0 0 H${value} V40 H${value - 8} C${value - 36} 40 ${value - 36} 72 ${value - 64} 72 H64 C36 72 36 40 8 40 H0 Z`
  )
  const stroke = useTransform(
    width,
    (value) =>
      `M0 39.5 H8 C36 39.5 36 71.5 64 71.5 H${value - 64} C${value - 36} 71.5 ${value - 36} 39.5 ${value - 8} 39.5 H${value}`
  )

  useEffect(() => {
    const controls = animate(
      width,
      isOpen ? Math.max(192, bounds.width) : 192,
      reducedMotion
        ? { duration: 0 }
        : {
            type: "spring",
            stiffness: 340,
            damping: 30,
            delay: isOpen ? 0 : 0.2,
          }
    )
    return () => controls.stop()
  }, [bounds.width, isOpen, reducedMotion, width])

  useEffect(() => {
    if (!isOpen) return
    const dismiss = (event: PointerEvent | KeyboardEvent) => {
      if (event instanceof KeyboardEvent) {
        if (event.key !== "Escape") return
        event.preventDefault()
        setIsOpen(false)
        toggleRef.current?.focus()
      } else if (
        event.target instanceof Node &&
        !navRef.current?.contains(event.target)
      )
        setIsOpen(false)
    }
    document.addEventListener("pointerdown", dismiss)
    document.addEventListener("keydown", dismiss)
    return () => {
      document.removeEventListener("pointerdown", dismiss)
      document.removeEventListener("keydown", dismiss)
    }
  }, [isOpen])

  return (
    <header
      data-slot="header"
      className="pointer-events-none fixed inset-x-0 top-0 z-50 flex h-24 justify-center"
    >
      <div
        aria-hidden="true"
        className="absolute inset-x-0 top-0 h-10 border-b border-border/50 bg-card"
      />
      <div
        ref={measureRef}
        className="relative flex w-[calc(100%-2rem)] max-w-xl justify-center"
      >
        <motion.nav
          ref={navRef}
          style={{ width }}
          aria-label="Main navigation"
          className="pointer-events-auto relative h-24 overflow-hidden"
          onPointerEnter={(event) => {
            if (event.pointerType === "mouse") setIsOpen(true)
          }}
          onPointerLeave={() => {
            if (!navRef.current?.contains(document.activeElement))
              setIsOpen(false)
          }}
          onBlur={(event) => {
            if (!event.currentTarget.contains(event.relatedTarget))
              setIsOpen(false)
          }}
        >
          <motion.svg
            aria-hidden="true"
            style={{ width }}
            height="96"
            viewBox={viewBox}
            className="pointer-events-none absolute inset-0 overflow-visible"
          >
            <motion.path d={fill} className="fill-card" />
            <motion.path
              d={stroke}
              fill="none"
              className="stroke-border/50"
              vectorEffect="non-scaling-stroke"
            />
          </motion.svg>
          <Button
            ref={toggleRef}
            type="button"
            aria-label={isOpen ? "Close navigation" : "Open navigation"}
            aria-expanded={isOpen}
            aria-controls={isOpen ? menuId : undefined}
            onClick={() => setIsOpen(!isOpen)}
            variant="ghost"
            size="icon-lg"
            className="absolute top-3.5 left-1/2 z-20 -translate-x-1/2"
          >
            <Icons.Logo aria-hidden="true" className="size-7" />
          </Button>
          <AnimatePresence initial={false}>
            {isOpen && (
              <motion.div
                data-slot="navigation-menu-popup"
                id={menuId}
                initial="closed"
                animate="open"
                exit="closed"
                variants={{
                  open: {
                    transition: {
                      delayChildren: reducedMotion ? 0 : 0.12,
                      staggerChildren: reducedMotion ? 0 : 0.025,
                    },
                  },
                  closed: {
                    transition: {
                      staggerChildren: reducedMotion ? 0 : 0.015,
                      staggerDirection: -1,
                    },
                  },
                }}
                style={{ width: Math.max(192, bounds.width) - 32 }}
                className="absolute top-3.5 left-1/2 grid h-10 -translate-x-1/2 grid-cols-[1fr_2.5rem_1fr] gap-2 sm:gap-6"
              >
                {[links.slice(0, 2), links.slice(2)].map((group, index) => (
                  <div
                    key={group[0].href}
                    className={cn(
                      "flex items-center gap-1 sm:gap-6",
                      index === 0 ? "col-start-1 justify-end" : "col-start-3"
                    )}
                  >
                    {group.map((link) => (
                      <motion.div
                        key={link.href}
                        variants={{
                          open: { opacity: 1, y: 0 },
                          closed: { opacity: 0, y: reducedMotion ? 0 : -10 },
                        }}
                        transition={{
                          duration: reducedMotion ? 0 : 0.16,
                          ease: "easeOut",
                        }}
                      >
                        <Button
                          onClick={() => setIsOpen(false)}
                          variant="ghost"
                          className="sm:text-sm"
                          render={<Link href={link.href} role="link" />}
                          nativeButton={false}
                        >
                          {link.label}
                        </Button>
                      </motion.div>
                    ))}
                  </div>
                ))}
              </motion.div>
            )}
          </AnimatePresence>
        </motion.nav>
      </div>
    </header>
  )
}
```
