# Capsule Nav Header Without A CTA

> Header with the wordmark on the left and the navigation collected into one muted capsule on the right, with a sliding active pill and no call to action.

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

- Page, with a live preview: https://shadcn.exchange/r/7ovr/header-14
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/7ovr/header-14-light-1ee2f31822ecb4c1.png
- Registry item: https://7ovr.com/r/header-14.json
- Categories: header
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://7ovr.com/r/header-14.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: `@base-ui/react`, `motion`
- Registry items: `button`, `sheet`

## Notes from the registry

Requires a `base-*` style in components.json (the `shadcn init` default). Legacy `new-york`/`radix-*` styles install Radix primitives, which reject the `render` prop this block uses.

## Files

### `registry/blocks/header/14/header-block.tsx`

```tsx
"use client"

import { useEffect, useRef, useState } from "react"
import { motion, useReducedMotion } from "motion/react"
import { Button } from "@/components/ui/button"
import {
  Sheet,
  SheetClose,
  SheetContent,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"
import { cn } from "@/lib/utils"
import { IconPlaceholder } from "@/components/icons/icon-placeholder"

const pillSpring = { type: "spring" as const, stiffness: 400, damping: 34 }

const sections = [
  { id: "overview", label: "Overview" },
  { id: "features", label: "Features" },
  { id: "pricing", label: "Pricing" },
  { id: "faq", label: "FAQ" },
]

export default function HeaderBlock() {
  const reduce = useReducedMotion()
  const [open, setOpen] = useState(false)
  const [active, setActive] = useState(0)
  const [hovered, setHovered] = useState<number | null>(null)
  const navRef = useRef<HTMLElement>(null)

  const { anchorRef, scrolled } = useAncestorScroll()

  useEffect(() => {
    const root = findScrollParent(navRef.current)
    const els = sections
      .map((s) => document.getElementById(s.id))
      .filter((el): el is HTMLElement => el !== null)
    if (els.length === 0) return

    const io = new IntersectionObserver(
      (entries) => {
        for (const entry of entries) {
          if (!entry.isIntersecting) continue
          const index = els.indexOf(entry.target as HTMLElement)
          if (index !== -1) setActive(index)
        }
      },
      { root, rootMargin: "-45% 0px -50% 0px", threshold: 0 }
    )
    els.forEach((el) => io.observe(el))
    return () => io.disconnect()
  }, [])

  const shown = hovered ?? active

  return (
    <header
      ref={anchorRef}
      className={cn(
        "sticky top-0 z-40 w-full border-b bg-background/85 backdrop-blur transition-colors duration-300",
        scrolled ? "border-border" : "border-transparent"
      )}
    >
      <div className="mx-auto flex h-16 w-full max-w-6xl items-center gap-6 px-6">
        <a href="#" className="flex shrink-0 items-center gap-2.5">
          <svg
            viewBox="0 0 24 24"
            fill="currentColor"
            aria-hidden="true"
            className="size-6 shrink-0 text-primary"
          >
            <rect x="3" y="3" width="8" height="8" transform="rotate(-6 7 7)" />
            <rect
              x="3"
              y="13"
              width="8"
              height="8"
              transform="rotate(5 7 17)"
            />
            <rect
              x="13"
              y="13"
              width="8"
              height="8"
              transform="rotate(-4 17 17)"
            />
            <rect
              x="13"
              y="3"
              width="8"
              height="8"
              transform="rotate(15 17 7)"
            />
          </svg>
          <span className="text-base font-bold tracking-tight">Acme</span>
        </a>

        <nav
          ref={navRef}
          onMouseLeave={() => setHovered(null)}
          onBlur={(e) => {
            if (!e.currentTarget.contains(e.relatedTarget as Node))
              setHovered(null)
          }}
          className="ml-auto hidden items-center gap-0.5 rounded-[calc(var(--radius-md)+4px)] border border-border bg-muted/60 p-1 backdrop-blur md:flex"
        >
          {sections.map((s, i) => {
            const isShown = shown === i
            return (
              <a
                key={s.id}
                href={`#${s.id}`}
                aria-current={active === i ? "true" : undefined}
                onMouseEnter={() => setHovered(i)}
                onFocus={() => setHovered(i)}
                onClick={() => setActive(i)}
                className={cn(
                  "relative rounded-md px-3.5 py-1.5 text-sm font-medium transition-colors",
                  isShown
                    ? "text-foreground"
                    : "text-muted-foreground hover:text-foreground"
                )}
              >
                {isShown && (
                  <motion.span
                    layoutId="header-14-pill"
                    aria-hidden="true"
                    transition={reduce ? { duration: 0 } : pillSpring}
                    className="absolute inset-0 -z-10 rounded-md bg-background shadow-sm"
                  />
                )}
                {s.label}
              </a>
            )
          })}
        </nav>

        <Sheet open={open} onOpenChange={setOpen}>
          <SheetTrigger
            render={
              <Button
                variant="outline"
                size="icon"
                aria-label="Open menu"
                className="ml-auto md:hidden"
              />
            }
          >
            <IconPlaceholder
              lucide="Menu"
              tabler="IconMenu"
              hugeicons="MenuIcon"
              phosphor="List"
              remixicon="RiMenuLine"
              aria-hidden="true"
            />
          </SheetTrigger>
          <SheetContent side="right" className="w-3/4 max-w-xs">
            <SheetHeader>
              <SheetTitle className="flex items-center gap-2.5">
                <svg
                  viewBox="0 0 24 24"
                  fill="currentColor"
                  aria-hidden="true"
                  className="size-5 shrink-0 text-primary"
                >
                  <rect
                    x="3"
                    y="3"
                    width="8"
                    height="8"
                    transform="rotate(-6 7 7)"
                  />
                  <rect
                    x="3"
                    y="13"
                    width="8"
                    height="8"
                    transform="rotate(5 7 17)"
                  />
                  <rect
                    x="13"
                    y="13"
                    width="8"
                    height="8"
                    transform="rotate(-4 17 17)"
                  />
                  <rect
                    x="13"
                    y="3"
                    width="8"
                    height="8"
                    transform="rotate(15 17 7)"
                  />
                </svg>
                Acme
              </SheetTitle>
            </SheetHeader>

            <nav className="flex flex-col px-4">
              {sections.map((s, i) => (
                <SheetClose
                  key={s.id}
                  nativeButton={false}
                  render={
                    <a
                      href={`#${s.id}`}
                      className={cn(
                        "border-l-2 py-3 pl-3 text-sm font-medium transition-colors",
                        active === i
                          ? "border-primary bg-muted/60 text-foreground"
                          : "border-transparent text-muted-foreground hover:text-foreground"
                      )}
                    />
                  }
                >
                  {s.label}
                </SheetClose>
              ))}
            </nav>
          </SheetContent>
        </Sheet>
      </div>
    </header>
  )
}

function findScrollParent(node: HTMLElement | null): HTMLElement | null {
  let el = node?.parentElement ?? null
  while (el) {
    const overflowY = getComputedStyle(el).overflowY
    if (
      (overflowY === "auto" || overflowY === "scroll") &&
      el.scrollHeight > el.clientHeight
    ) {
      return el
    }
    el = el.parentElement
  }
  return null
}

function useAncestorScroll() {
  const anchorRef = useRef<HTMLElement>(null)
  const [scrolled, setScrolled] = useState(false)

  useEffect(() => {
    const anchor = anchorRef.current
    if (!anchor) return
    const scroller = findScrollParent(anchor)
    const target: HTMLElement | Window = scroller ?? window
    const read = () =>
      setScrolled((scroller ? scroller.scrollTop : window.scrollY) > 8)
    read()
    target.addEventListener("scroll", read, { passive: true })
    window.addEventListener("resize", read)
    return () => {
      target.removeEventListener("scroll", read)
      window.removeEventListener("resize", read)
    }
  }, [])

  return { anchorRef, scrolled }
}
```
