# Navbar 5

> Navbar that sits transparent over a photo hero and turns solid on scroll (from Marbrook)

A shadcn/ui block from the [@cnippet-blocks](https://blocks.cnippet.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/cnippet-blocks/navbar-5).

- Page, with a live preview: https://shadcn.exchange/r/cnippet-blocks/navbar-5
- Screenshot: https://shadcn.exchange/shot/cnippet-blocks/navbar-5/light.png
- Registry item: https://blocks.cnippet.dev/r/navbar-5.json
- Categories: navbar
- Listed: 2026-11-01, source last changed 2026-11-01

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/cnippet-blocks/navbar-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: `lucide-react`
- Registry items: `@cnippet/button`, `@cnippet/sheet`, `@cnippet/utils`

## Files

### `registry/default/sections/navbar-5/index.tsx`

```tsx
"use client";

import { Menu, Phone, X } from "lucide-react";
import Link from "next/link";
import { useEffect, useState } from "react";

import { Button } from "@/components/ui/button";
import {
  Sheet,
  SheetClose,
  SheetPopup,
  SheetTrigger,
} from "@/components/ui/sheet";
import { cn } from "@/lib/utils";

import type { NavbarContent } from "../_types/navbar";
import { defaultNavbar5Content } from "./data";

/** `/properties/blackwood-house` keeps the Properties link lit. */
function isActive(activeHref: string | undefined, href: string): boolean {
  if (!activeHref) return false;
  return activeHref === href || activeHref.startsWith(`${href}/`);
}

function telHref(phone: string): string {
  return `tel:${phone.replace(/[^\d+]/g, "")}`;
}

export default function Navbar5({
  activeHref,
  content = defaultNavbar5Content,
  fixed = false,
  overlay = false,
}: {
  /** The current path, used to underline the matching link. */
  activeHref?: string;
  content?: NavbarContent;
  /** Pin the bar to the top of the viewport. */
  fixed?: boolean;
  /** Transparent with white text until the page scrolls (sit it over a photo hero). */
  overlay?: boolean;
}) {
  const [scrolled, setScrolled] = useState(false);

  useEffect(() => {
    if (!overlay) return;
    const handleScroll = () => setScrolled(window.scrollY > 24);
    handleScroll();
    window.addEventListener("scroll", handleScroll, { passive: true });
    return () => window.removeEventListener("scroll", handleScroll);
  }, [overlay]);

  const solid = !overlay || scrolled;
  const { brand, cta, links, phone } = content;

  return (
    <header
      className={cn(
        "inset-x-0 top-0 z-50 transition-colors duration-300",
        fixed ? "fixed" : overlay ? "absolute" : "relative",
        solid
          ? "border-border/80 border-b bg-background/95 backdrop-blur-md"
          : "bg-transparent",
      )}
    >
      <div className="mx-auto flex max-w-7xl items-center justify-between gap-6 px-5 py-4 sm:px-8 lg:px-12">
        <Link
          className={cn(
            "flex items-baseline gap-2 transition-colors",
            solid ? "text-primary" : "text-white",
          )}
          href={brand.href}
        >
          <span className="font-display text-2xl leading-none tracking-tight">
            {brand.name}
          </span>
          {brand.tagline && (
            <span
              className={cn(
                "hidden font-mono text-[0.625rem] uppercase tracking-[0.2em] sm:inline",
                solid ? "text-muted-foreground/70" : "text-white/60",
              )}
            >
              {brand.tagline}
            </span>
          )}
        </Link>

        <nav className="hidden items-center gap-9 lg:flex">
          {links.map((link) => {
            const active = isActive(activeHref, link.href);

            return (
              <Link
                aria-current={active ? "page" : undefined}
                className={cn(
                  "relative py-1 text-sm transition-colors after:absolute after:inset-x-0 after:-bottom-0.5 after:h-px after:origin-left after:bg-current after:transition-transform after:duration-300 hover:after:scale-x-100",
                  // The underline is the active state, so it starts drawn.
                  active ? "after:scale-x-100" : "after:scale-x-0",
                  solid
                    ? active
                      ? "text-primary"
                      : "text-muted-foreground hover:text-primary"
                    : active
                      ? "text-white"
                      : "text-white/80 hover:text-white",
                )}
                href={link.href}
                key={link.href}
              >
                {link.label}
              </Link>
            );
          })}
        </nav>

        <div className="flex items-center gap-3">
          {phone && (
            <a
              className={cn(
                "hidden items-center gap-2 text-sm transition-colors xl:flex",
                solid
                  ? "text-muted-foreground hover:text-primary"
                  : "text-white/80 hover:text-white",
              )}
              href={telHref(phone)}
            >
              <Phone className="size-4" />
              {phone}
            </a>
          )}

          {cta && (
            <Button
              className={cn(
                "hidden border-none px-6 shadow-none sm:inline-flex",
                solid
                  ? "bg-primary text-primary-foreground hover:bg-primary/90"
                  : "bg-white text-primary hover:bg-white/90",
              )}
              render={<Link href={cta.href} />}
              size="lg"
            >
              {cta.label}
            </Button>
          )}

          <Sheet>
            <SheetTrigger
              aria-label="Open menu"
              className={cn(
                "border-none bg-transparent shadow-none lg:hidden",
                solid
                  ? "text-primary hover:bg-muted"
                  : "text-white hover:bg-white/10",
              )}
              render={<Button size="icon-lg" variant="ghost" />}
            >
              <Menu className="size-5" />
            </SheetTrigger>

            <SheetPopup
              className="w-[86%] max-w-sm border-border bg-background"
              showCloseButton={false}
              side="right"
            >
              <div className="flex items-center justify-between border-border border-b px-6 py-5">
                <span className="font-display text-2xl text-primary leading-none">
                  {brand.name}
                </span>
                <SheetClose
                  aria-label="Close menu"
                  className="text-muted-foreground"
                  render={<Button size="icon" variant="ghost" />}
                >
                  <X className="size-5" />
                </SheetClose>
              </div>

              <nav className="flex flex-col divide-y divide-border">
                {links.map((link) => {
                  const active = isActive(activeHref, link.href);

                  return (
                    <SheetClose
                      aria-current={active ? "page" : undefined}
                      className={cn(
                        "px-6 py-4 font-display text-2xl text-primary transition-colors hover:bg-muted",
                        active && "border-secondary border-l-2 bg-muted",
                      )}
                      key={link.href}
                      nativeButton={false}
                      render={<Link href={link.href} />}
                    >
                      {link.label}
                    </SheetClose>
                  );
                })}
              </nav>

              <div className="mt-auto space-y-4 border-border border-t p-6">
                {phone && (
                  <a
                    className="flex items-center gap-2 text-muted-foreground text-sm"
                    href={telHref(phone)}
                  >
                    <Phone className="size-4" />
                    {phone}
                  </a>
                )}
                {cta && (
                  <Button
                    className="w-full border-none bg-primary px-6 text-primary-foreground shadow-none hover:bg-primary/90"
                    render={<Link href={cta.href} />}
                    size="lg"
                  >
                    {cta.label}
                  </Button>
                )}
              </div>
            </SheetPopup>
          </Sheet>
        </div>
      </div>
    </header>
  );
}
```

### `registry/default/sections/navbar-5/data.ts`

```ts
import type { NavbarContent } from "../_types/navbar";

export const defaultNavbar5Content: NavbarContent = {
  brand: {
    href: "/",
    name: "Marbrook",
    tagline: "Est. 1994",
  },
  cta: {
    href: "/contact",
    label: "Book a viewing",
  },
  links: [
    {
      href: "/properties",
      label: "Properties",
    },
    {
      href: "/neighbourhoods",
      label: "Neighbourhoods",
    },
    {
      href: "/services",
      label: "Services",
    },
    {
      href: "/about",
      label: "About",
    },
    {
      href: "/journal",
      label: "Journal",
    },
  ],
  phone: "+1 (800) 555-0199",
};
```

### `registry/default/sections/_types/common.ts`

```ts
/**
 * Shapes shared by every token-based section. Each section ships this file
 * alongside its own category types, so installing several sections simply
 * rewrites the same file.
 */

/** Which band a section sits on. A style choice, never part of `content`. */
export type Surface = "default" | "muted" | "primary" | "secondary";

export type ImageAsset = {
  alt: string;
  src: string;
};

export type LinkAction = {
  href: string;
  label: string;
};

/** Eyebrow + heading + optional lede, the header most sections open with. */
export type SectionIntro = {
  description?: string;
  eyebrow: string;
  title: string;
};

/** A figure that counts up when it scrolls into view. */
export type Stat = {
  decimals?: number;
  label: string;
  prefix?: string;
  suffix?: string;
  value: number;
};

/** A figure that is displayed as written ("1,240+", "4.9/5"). */
export type Highlight = {
  label: string;
  value: string;
};

export type FormField = {
  label: string;
  name: string;
  /** Only for `type: "select"`. The first option is the default. */
  options?: string[];
  placeholder?: string;
  required?: boolean;
  /** `full` spans both columns of a two-column form. */
  span?: "full" | "half";
  type?: "email" | "select" | "tel" | "text" | "textarea";
};

export type FormContent = {
  description?: string;
  fields: FormField[];
  note?: string;
  submitLabel: string;
  success: {
    description: string;
    resetLabel: string;
    title: string;
  };
  title: string;
};
```

### `registry/default/sections/_types/navbar.ts`

```ts
import type { LinkAction } from "./common";

export type NavbarContent = {
  brand: {
    href: string;
    name: string;
    tagline?: string;
  };
  cta?: LinkAction;
  links: LinkAction[];
  phone?: string;
};
```
