# Header 8

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

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

## Install

```bash
npx shadcn@latest add https://diarmuradi.com/r/header-8.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`, `lucide-react`, `motion`
- Registry items: `https://diarmuradi.com/r/styles/base-nova/button.json`, `https://diarmuradi.com/r/styles/base-nova/fancy-button.json`, `https://diarmuradi.com/r/styles/base-nova/icons.json`, `https://diarmuradi.com/r/styles/base-nova/navigation-menu.json`

## Files

### `header.tsx`

```tsx
"use client"

import Link from "next/link"

import { DesktopNav } from "@/components/header-8/desktop-nav"
import { MobileNav } from "@/components/header-8/mobile-nav"
import { FancyButton } from "@/components/ui/fancy-button"
import { Icons } from "@/components/ui/icons"

export function SiteHeader() {
  return (
    <header
      className="sticky top-0 z-50 w-xs rounded-3xl bg-card px-2 shadow-elevated-sm lg:w-fit"
      data-slot="header"
    >
      <div className="relative">
        <div className="flex h-14 items-center justify-between gap-2">
          <FancyButton size="icon-lg" render={<Link href="#" role="link" />}>
            <Icons.Logo aria-hidden="true" className="size-5" />
            <span className="sr-only">diarmuradi</span>
          </FancyButton>

          <div className="flex justify-center max-lg:hidden lg:flex-1">
            <DesktopNav />
          </div>

          <div className="flex items-center gap-2">
            <FancyButton
              size="lg"
              className="max-lg:hidden"
              render={<Link href="#sign-up" role="link" />}
            >
              Sign up
            </FancyButton>
            <MobileNav />
          </div>
        </div>
      </div>
    </header>
  )
}
```

### `desktop-nav.tsx`

```tsx
"use client"

import { useEffect, useState } from "react"
import Link from "next/link"
import { cn } from "cn"

import {
  NavigationMenu,
  NavigationMenuContent,
  NavigationMenuItem,
  NavigationMenuLink,
  NavigationMenuList,
  NavigationMenuTrigger,
  navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"
import { IPhoneMockup } from "@/components/header-8/iphone-mockup"
import { ArrowUpRightIcon, CreditCardIcon, LockIcon, MailIcon, TrendingUpIcon, TriangleAlertIcon } from 'lucide-react'

export const FEATURES = [
  {
    key: "payments",
    title: "Payments Hub",
    description: "Manage transfers from one dashboard.",
    href: "#",
    icon: (
      <CreditCardIcon  aria-hidden="true" />
    ),
  },
  {
    key: "invoicing",
    title: "Invoicing",
    description: "Create and send invoices instantly.",
    href: "#",
    icon: (
      <MailIcon  aria-hidden="true" />
    ),
  },
  {
    key: "transfers",
    title: "Payouts",
    description: "Send funds to partners and teams.",
    href: "#",
    icon: (
      <ArrowUpRightIcon  aria-hidden="true" />
    ),
  },
  {
    key: "security",
    title: "Identity Check",
    description: "Verify users before transactions.",
    href: "#",
    icon: (
      <LockIcon  aria-hidden="true" />
    ),
  },
  {
    key: "portfolio",
    title: "Portfolio Tracking",
    description: "Track assets and performance live.",
    href: "#",
    icon: (
      <TrendingUpIcon  aria-hidden="true" />
    ),
  },
  {
    key: "alerts",
    title: "Market Alerts",
    description: "Receive price and market notifications.",
    href: "#",
    icon: (
      <TriangleAlertIcon  aria-hidden="true" />
    ),
  },
]

export function DesktopNav() {
  const [menuValue, setMenuValue] = useState<string | null>(null)
  useEffect(() => {
    if (!menuValue) return
    const viewportWidth = window.innerWidth
    const close = () => {
      if (window.innerWidth === viewportWidth) return
      setMenuValue(null)
    }
    window.addEventListener("resize", close)
    return () => window.removeEventListener("resize", close)
  }, [menuValue])
  return (
    <>
      <style>{`
        .cn-navigation-menu-positioner:has([data-header-content="8"]) {
          translate: 0 0.5rem;
          transition: top 300ms ease-in-out, left 300ms ease-in-out;
        }
        .cn-navigation-menu-popup:has([data-header-content="8"]) {
          border-radius: var(--radius-4xl);
          box-shadow: var(--shadow-elevated-sm);
          scale: 1;
          transition: width 300ms ease-in-out, height 300ms ease-in-out,
            opacity 300ms ease-in-out;
        }
        [data-header-content="8"] {
          height: auto;
        }
        @media (prefers-reduced-motion: reduce) {
          .cn-navigation-menu-positioner:has([data-header-content="8"]),
          .cn-navigation-menu-popup:has([data-header-content="8"]),
          [data-header-content="8"] {
            transition: none;
          }
        }
      `}</style>
      <NavigationMenu
        align="center"
        delay={100}
        closeDelay={150}
        value={menuValue}
        onValueChange={setMenuValue}
        className="group/navigation-menu relative flex max-w-max items-center justify-center"
        aria-label="Main navigation"
      >
        <NavigationMenuList className="gap-2">
          <NavigationMenuItem>
            <NavigationMenuLink
              className={cn(
                "transition-colors duration-200 motion-reduce:transition-none",
                "text-muted-foreground! hover:bg-muted! hover:text-foreground!",
                navigationMenuTriggerStyle()
              )}
              render={<Link href="#" />}
            >
              Home
            </NavigationMenuLink>
          </NavigationMenuItem>
          <NavigationMenuItem>
            <NavigationMenuTrigger
              className={cn(
                "transition-colors duration-200 motion-reduce:transition-none [&>svg]:duration-200 [&>svg]:motion-reduce:transition-none",
                "text-muted-foreground! hover:bg-muted! hover:text-foreground!",
                "data-popup-open:text-foreground data-popup-open:[&_svg]:text-primary"
              )}
            >
              Features
            </NavigationMenuTrigger>
            <NavigationMenuContent
              data-header-content="8"
              className="data-ending-style:data-activation-direction=left:translate-x-52 data-ending-style:data-activation-direction=right:-translate-x-52 data-starting-style:data-activation-direction=left:-translate-x-52 data-starting-style:data-activation-direction=right:translate-x-52 no-scrollbar flex max-h-[calc(100dvh-8rem)] w-3xl gap-4 overflow-y-auto p-2 transition-opacity duration-200 ease-out data-ending-style:opacity-0 data-starting-style:opacity-0 motion-reduce:transition-none"
            >
              <div className="flex w-xs flex-1 flex-col gap-2">
                {FEATURES.map((p) => (
                  <NavigationMenuLink
                    key={p.key}
                    className="group w-full rounded-2xl! transition-colors duration-200 motion-reduce:transition-none"
                    render={
                      <Link href={p.href} className="flex items-center gap-3" />
                    }
                  >
                    <div className="rounded-full p-2 text-muted-foreground transition-[background-color,box-shadow,color] group-hover:bg-popover group-hover:text-primary group-hover:shadow-elevated-sm [&_svg]:size-5">
                      {p.icon}
                    </div>
                    <div className="flex flex-col">
                      <div className="font-medium">{p.title}</div>
                      <div className="text-xs text-muted-foreground">
                        {p.description}
                      </div>
                    </div>
                    <ArrowUpRightIcon  aria-hidden="true" className="invisible mr-2 ml-auto group-hover:visible group-hover:text-primary" />
                  </NavigationMenuLink>
                ))}
              </div>
              <div className="relative flex w-sm flex-col gap-3 overflow-hidden rounded-2xl bg-muted p-6">
                <div className="flex flex-col gap-1">
                  <h3 className="font-semibold text-foreground">
                    Download the mobile app
                  </h3>
                  <p className="text-xs text-muted-foreground">
                    Get the app for iOS and Android.
                  </p>
                </div>
                <IPhoneMockup className="absolute -bottom-48 left-1/2 h-112 w-auto max-w-none -translate-x-1/2" />
              </div>
            </NavigationMenuContent>
          </NavigationMenuItem>
          <NavigationMenuItem>
            <NavigationMenuLink
              className={cn(
                "transition-colors duration-200 motion-reduce:transition-none",
                "text-muted-foreground! hover:bg-muted! hover:text-foreground!",
                navigationMenuTriggerStyle()
              )}
              render={<Link href="#pricing" />}
            >
              Pricing
            </NavigationMenuLink>
          </NavigationMenuItem>
        </NavigationMenuList>
      </NavigationMenu>
    </>
  )
}
```

### `iphone-mockup.tsx`

```tsx
"use client"

import { useId, type ReactNode } from "react"

/* Adapted from Max Rudberg's iPhone mockup, CC BY 4.0: https://www.figma.com/community/file/1560672326379407095/iphone-17-mockups */

export const IPhoneMockup = ({
  className,
  children,
}: {
  className?: string
  children?: ReactNode
}) => {
  const id = useId()

  return (
    <svg
      aria-hidden="true"
      width="400"
      height="820"
      viewBox="0 0 400 820"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className={className}
    >
      <rect
        data-slot="iphone-body"
        x="10"
        y="10"
        width="380"
        height="800"
        rx="55"
        className="fill-[#1d1d1f] dark:fill-[#1d1d1f]"
      />

      <rect
        data-slot="iphone-bezel"
        x="15"
        y="15"
        width="370"
        height="790"
        rx="50"
        className="fill-[#2c2c2e] dark:fill-[#2c2c2e]"
      />

      <g clipPath={`url(#${id}-screen)`}>
        <rect
          x="20"
          y="20"
          width="360"
          height="780"
          rx="45"
          className="fill-white dark:fill-black"
        />

        <g clipPath={`url(#${id}-content)`}>
          <rect
            x="20"
            y="20"
            width="360"
            height="780"
            className="fill-[#f5f5f7] dark:fill-[#000000]"
          />
          {children}
        </g>
      </g>

      <rect
        x="130"
        y="30"
        width="140"
        height="37"
        rx="18.5"
        className="fill-black"
      />

      <circle
        cx="240"
        cy="48.5"
        r="6"
        className="fill-[#0a0a0a] dark:fill-[#0a0a0a]"
      />

      <circle
        cx="240"
        cy="48.5"
        r="7.5"
        className="fill-none stroke-[#2c2c2e] dark:stroke-[#2c2c2e]"
        strokeWidth="1"
      />

      <rect
        x="390"
        y="200"
        width="5"
        height="80"
        rx="2.5"
        className="fill-[#1d1d1f] dark:fill-[#1d1d1f]"
      />

      <rect
        x="5"
        y="180"
        width="5"
        height="60"
        rx="2.5"
        className="fill-[#1d1d1f] dark:fill-[#1d1d1f]"
      />

      <rect
        x="5"
        y="260"
        width="5"
        height="60"
        rx="2.5"
        className="fill-[#1d1d1f] dark:fill-[#1d1d1f]"
      />

      <rect
        x="5"
        y="140"
        width="5"
        height="30"
        rx="2.5"
        className="fill-[#1d1d1f] dark:fill-[#1d1d1f]"
      />

      <rect
        x="20"
        y="20"
        width="360"
        height="780"
        rx="45"
        className="fill-white dark:fill-white"
        opacity="0.03"
      />

      <rect
        x="15"
        y="15"
        width="370"
        height="790"
        rx="50"
        className="stroke-white dark:stroke-white"
        strokeWidth="0.5"
        fill="none"
        opacity="0.1"
      />

      <defs>
        <clipPath id={`${id}-screen`}>
          <rect x="20" y="20" width="360" height="780" rx="45" />
        </clipPath>
        <clipPath id={`${id}-content`}>
          <rect x="20" y="20" width="360" height="780" rx="45" />
        </clipPath>
      </defs>
    </svg>
  )
}
```

### `mobile-nav.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 { FEATURES } from "@/components/header-8/desktop-nav"
import { ChevronDownIcon, MenuIcon, XIcon } from 'lucide-react'

const groups: {
  title: string
  items: { title: string; href: string; description?: string }[]
}[] = [{ title: "Features", items: FEATURES }]
export function MobileNav() {
  const [isOpen, setIsOpen] = useState(false)
  const menuId = useId()
  const triggerRef = useRef<HTMLButtonElement>(null)
  const panelRef = useRef<HTMLElement>(null)
  const reducedMotion = useReducedMotion()
  useEffect(() => {
    if (!isOpen) return
    const dismiss = (event: PointerEvent | KeyboardEvent) => {
      if (event instanceof KeyboardEvent) {
        if (event.key !== "Escape") return
        event.preventDefault()
        setIsOpen(false)
        triggerRef.current?.focus()
      } else if (
        event.target instanceof Node &&
        !panelRef.current?.contains(event.target) &&
        !triggerRef.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 (
    <>
      <Button
        ref={triggerRef}
        type="button"
        variant="ghost"
        size="icon-lg"
        aria-label={isOpen ? "Close menu" : "Open menu"}
        aria-expanded={isOpen}
        aria-controls={isOpen ? menuId : undefined}
        onClick={() => setIsOpen(!isOpen)}
        className="lg:hidden"
      >
        {isOpen ? (
          <XIcon  aria-hidden="true" />
        ) : (
          <MenuIcon  aria-hidden="true" />
        )}
      </Button>
      <AnimatePresence initial={false}>
        {isOpen && (
          <motion.nav
            data-slot="navigation-menu-popup"
            ref={panelRef}
            id={menuId}
            aria-label="Main navigation"
            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="absolute inset-x-0 top-full z-50 mt-2 no-scrollbar max-h-[calc(100dvh-6rem)] overflow-y-auto rounded-3xl bg-popover p-3 text-popover-foreground shadow-elevated-sm lg:hidden"
            onClick={(event) => {
              if ((event.target as HTMLElement).closest("a")) setIsOpen(false)
            }}
          >
            <div className="flex flex-col gap-1">
              <Button
                variant="ghost"
                size="lg"
                className="w-full justify-start"
                render={<Link href="#" role="link" />}
                nativeButton={false}
              >
                Home
              </Button>
              <Button
                variant="ghost"
                size="lg"
                className="w-full justify-start"
                render={<Link href="#pricing" role="link" />}
                nativeButton={false}
              >
                Pricing
              </Button>
              <Button
                variant="ghost"
                size="lg"
                className="w-full justify-start"
                render={<Link href="#sign-up" role="link" />}
                nativeButton={false}
              >
                Sign up
              </Button>
              {groups.map((group) => (
                <details key={group.title} className="group/section">
                  <Button
                    variant="ghost"
                    size="lg"
                    className="w-full justify-between"
                    render={<summary />}
                    nativeButton={false}
                  >
                    {group.title}
                    <ChevronDownIcon  aria-hidden="true" className="text-muted-foreground transition-transform duration-200 group-open/section:rotate-180 motion-reduce:transition-none" data-icon="inline-end" />
                  </Button>
                  <ul className="flex flex-col gap-1 py-1">
                    {group.items.map((item) => (
                      <li key={item.title}>
                        <Button
                          variant="ghost"
                          size="lg"
                          className="h-auto w-full flex-col items-start justify-start py-2.5 text-left whitespace-normal"
                          render={<Link href={item.href} role="link" />}
                          nativeButton={false}
                        >
                          <span className="font-medium">{item.title}</span>
                          {item.description && (
                            <span className="text-xs text-muted-foreground">
                              {item.description}
                            </span>
                          )}
                        </Button>
                      </li>
                    ))}
                  </ul>
                </details>
              ))}
            </div>
          </motion.nav>
        )}
      </AnimatePresence>
    </>
  )
}
```
