# Header 4

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

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

## Install

```bash
npx shadcn@latest add https://diarmuradi.com/r/header-4.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`, `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 {
  AnimatePresence,
  motion,
  MotionConfig,
  useReducedMotion,
} from "motion/react"
import useMeasure from "react-use-measure"

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

export function SiteHeader() {
  const [isOpen, setIsOpen] = useState(false)
  const [measureRef, { height }] = useMeasure()

  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 (
    <MotionConfig
      reducedMotion="user"
      transition={{ duration: reducedMotion ? 0 : 0.2, ease: "easeOut" }}
    >
      <header
        ref={menuRef}
        data-slot="header"
        className="pointer-events-none fixed inset-x-0 top-0 z-50 flex items-start justify-center overflow-visible"
      >
        <motion.div
          initial={false}
          animate={{ height: isOpen ? 40 + height : 40 }}
          className="pointer-events-auto w-full flex-1 border-b border-border/50 bg-card"
        />

        <motion.div
          initial={false}
          animate={{ height: isOpen ? 64 + height : 64 }}
          className="pointer-events-auto relative -ml-px w-12 shrink-0"
        >
          <svg
            aria-hidden="true"
            className="absolute inset-0 size-full"
            width="48"
            height="100%"
          >
            <motion.path
              initial={false}
              animate={{
                d: `M0 0 H48 V${isOpen ? 64 + height : 64} C24 ${isOpen ? 64 + height : 64} 24 ${isOpen ? 40 + height : 40} 0 ${isOpen ? 40 + height : 40} Z`,
              }}
              className="fill-current text-card"
            />
            <motion.path
              initial={false}
              animate={{
                d: `M0 ${isOpen ? 39.5 + height : 39.5} C24 ${isOpen ? 39.5 + height : 39.5} 24 ${isOpen ? 63.5 + height : 63.5} 48 ${isOpen ? 63.5 + height : 63.5}`,
              }}
              className="stroke-current text-border/50"
              fill="none"
              strokeWidth="1"
            />
          </svg>
        </motion.div>

        <motion.div
          initial={false}
          animate={{ height: isOpen ? 64 + height : 64 }}
          className="pointer-events-auto relative -ml-px flex w-full max-w-[calc(100%-6rem)] flex-none flex-col overflow-hidden border-b border-border/50 bg-card md:max-w-xl lg:max-w-2xl"
        >
          <div className="flex h-16 shrink-0 items-center justify-between px-4 sm:px-8">
            <div className="flex items-center gap-8">
              <Button
                aria-label="Home"
                variant="ghost"
                size="icon-lg"
                render={<Link href="#" role="link" />}
                nativeButton={false}
              >
                <Icons.Logo aria-hidden="true" className="size-7" />
              </Button>

              <nav
                aria-label="Main navigation"
                className="flex items-center gap-6 max-md:hidden"
              >
                {["Products", "Solutions", "Pricing"].map((item) => (
                  <Button
                    key={item}
                    variant="ghost"
                    render={
                      <Link href={`/${item.toLowerCase()}`} role="link" />
                    }
                    nativeButton={false}
                  >
                    {item}
                  </Button>
                ))}
              </nav>
            </div>

            <div className="flex items-center gap-4">
              <Button
                variant="ghost"
                className="max-sm:hidden"
                render={<Link href="#sign-in" role="link" />}
                nativeButton={false}
              >
                Log in
              </Button>
              <Button
                onClick={() => setIsOpen(false)}
                className="max-md:hidden"
                render={<Link href="#get-started" role="link" />}
                nativeButton={false}
              >
                Get started
              </Button>
              <Button
                type="button"
                aria-label={isOpen ? "Close menu" : "Open menu"}
                aria-expanded={isOpen}
                aria-controls={isOpen ? menuId : undefined}
                onClick={() => setIsOpen(!isOpen)}
                variant="secondary"
                size="icon-lg"
                className="md:hidden"
              >
                {isOpen ? (
                  <XIcon  aria-hidden="true" />
                ) : (
                  <svg
                    aria-hidden="true"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    strokeWidth={2}
                    strokeLinecap="round"
                    className="size-4"
                  >
                    <path d="M5 9h14M5 15h14" />
                  </svg>
                )}
              </Button>
            </div>
          </div>

          <div className="absolute inset-x-0 top-16 w-full md:hidden">
            <div ref={measureRef}>
              <AnimatePresence>
                {isOpen && (
                  <motion.div
                    data-slot="navigation-menu-popup"
                    initial={{ opacity: 0, y: reducedMotion ? 0 : -10 }}
                    animate={{ opacity: 1, y: 0 }}
                    exit={{ opacity: 0, y: reducedMotion ? 0 : -10 }}
                    id={menuId}
                    className="no-scrollbar flex max-h-[calc(100dvh-8rem)] flex-col overflow-y-auto px-4 pt-2 pb-6"
                  >
                    <nav className="flex flex-col gap-5">
                      <div className="flex flex-col gap-4">
                        {["Products", "Solutions", "Pricing"].map((item) => (
                          <Button
                            key={item}
                            onClick={() => setIsOpen(false)}
                            variant="ghost"
                            render={
                              <Link
                                href={`/${item.toLowerCase()}`}
                                role="link"
                              />
                            }
                            nativeButton={false}
                          >
                            {item}
                          </Button>
                        ))}
                      </div>

                      <div className="h-px w-full bg-border/50" />

                      <div className="flex flex-col gap-2.5">
                        <Button
                          onClick={() => setIsOpen(false)}
                          variant="secondary"
                          className="h-10 sm:hidden"
                          render={<Link href="#sign-in" role="link" />}
                          nativeButton={false}
                        >
                          Log in
                        </Button>
                        <Button
                          onClick={() => setIsOpen(false)}
                          className="h-10 w-full"
                          render={<Link href="#get-started" role="link" />}
                          nativeButton={false}
                        >
                          Get started
                        </Button>
                      </div>
                    </nav>
                  </motion.div>
                )}
              </AnimatePresence>
            </div>
          </div>
        </motion.div>

        <motion.div
          initial={false}
          animate={{ height: isOpen ? 64 + height : 64 }}
          className="pointer-events-auto relative -ml-px w-12 shrink-0"
        >
          <svg
            aria-hidden="true"
            className="absolute inset-0 size-full"
            width="48"
            height="100%"
          >
            <motion.path
              initial={false}
              animate={{
                d: `M0 0 H48 V${isOpen ? 40 + height : 40} C24 ${isOpen ? 40 + height : 40} 24 ${isOpen ? 64 + height : 64} 0 ${isOpen ? 64 + height : 64} Z`,
              }}
              className="fill-current text-card"
            />
            <motion.path
              initial={false}
              animate={{
                d: `M0 ${isOpen ? 63.5 + height : 63.5} C24 ${isOpen ? 63.5 + height : 63.5} 24 ${isOpen ? 39.5 + height : 39.5} 48 ${isOpen ? 39.5 + height : 39.5}`,
              }}
              className="stroke-current text-border/50"
              fill="none"
              strokeWidth="1"
            />
          </svg>
        </motion.div>

        <motion.div
          initial={false}
          animate={{ height: isOpen ? 40 + height : 40 }}
          className="pointer-events-auto w-full flex-1 border-b border-border/50 bg-card"
        />
      </header>
    </MotionConfig>
  )
}
```
