Search

Jump to any page.

Marketing

Site Header

Sticky header with wordmark, nav, primary CTA and a mobile menu.

pnpm dlx shadcn@latest add @paragon/site-header
Brings in@paragon/button@paragon/wordmarklucide-reactcn
registry/paragon/blocks/site-header/site-header.tsx
"use client"

import * as React from "react"
import Link from "next/link"
import { Menu, X } from "lucide-react"
import { cn } from "cn"

import { Button } from "@/registry/paragon/ui/button"
import { Wordmark } from "@/registry/paragon/components/wordmark"

type NavLink = { label: string; href: string }

export function SiteHeader({
  links = [
    { label: "Constellation", href: "/constellation" },
    { label: "Telos", href: "/telos" },
    { label: "Research", href: "/research" },
    { label: "Company", href: "/company" },
  ],
  cta = { label: "Talk to us", href: "/contact" },
  className,
}: {
  links?: NavLink[]
  cta?: NavLink
  className?: string
}) {
  const [open, setOpen] = React.useState(false)
  const menuId = React.useId()

  return (
    <header
      className={cn(
        "sticky top-0 z-40 px-inset pt-inset",
        className
      )}
    >
      <div className="mx-auto flex h-16 max-w-page items-center justify-between gap-6 rounded-card px-[calc(var(--spacing-gutter)-var(--spacing-inset))] backdrop-blur-md supports-backdrop-filter:bg-background/70">
        <Link href="/" aria-label="Paragon home" className="rounded-sm outline-none focus-visible:ring-3 focus-visible:ring-ring/40">
          <Wordmark />
        </Link>

        <nav aria-label="Main" className="hidden items-center gap-8 lg:flex">
          {links.map((link) => (
            <Link
              key={link.href}
              href={link.href}
              className="text-[0.9375rem] font-medium text-foreground/75 transition-colors duration-(--duration-base) hover:text-foreground"
            >
              {link.label}
            </Link>
          ))}
        </nav>

        <div className="flex items-center gap-2">
          <Button render={<Link href={cta.href} />} nativeButton={false}>
            {cta.label}
          </Button>
          <Button
            variant="outline"
            className="lg:hidden"
            aria-expanded={open}
            aria-controls={menuId}
            onClick={() => setOpen((value) => !value)}
          >
            {open ? <X aria-hidden="true" /> : <Menu aria-hidden="true" />}
            Menu
          </Button>
        </div>
      </div>

      <nav
        id={menuId}
        aria-label="Main"
        hidden={!open}
        className="mx-auto mt-2 max-w-page rounded-card bg-card p-2 shadow-lg ring-1 ring-border lg:hidden"
      >
        <ul className="flex flex-col">
          {links.map((link) => (
            <li key={link.href}>
              <Link
                href={link.href}
                onClick={() => setOpen(false)}
                className="flex h-12 items-center rounded-button px-4 text-lg font-medium hover:bg-foreground/5"
              >
                {link.label}
              </Link>
            </li>
          ))}
        </ul>
      </nav>
    </header>
  )
}