Navbar
The bar across the top of a marketing site: the mark, the pages and the way in.
Simple navbar
navbar-01The mark, five pages with the current one ruled underneath, and the two ways in; on a phone the pages and the ways in move into a sheet.
import * as React from "react"
import { Navbar01Bar } from "./navbar-01.client"
import { BRAND, CALL_TO_ACTION, LINKS, MENU, NAV_LABEL, SIGN_IN } from "./navbar-01.data"
export type Navbar01Props = {
/** The page the bar is on, when the route alone does not say: `/pricing` lights Pricing. */
activeHref?: string
/** Opens the phone's menu on the first render — for a preview; false when installed. */
defaultOpen?: boolean
}
/**
* The plain marketing bar: the mark on the left, five pages beside it, and
* the two ways in — sign in, and the one filled button — on the right. The
* band is the page's `<header>` itself, so it stays the page's banner
* landmark, holding the `<nav>` named "Main".
*
* A server component; the bar is one island because it reads the current
* route and owns the phone's menu. Every word is in `./navbar-01.data`.
*/
export function Navbar01({ activeHref, defaultOpen = false }: Navbar01Props) {
return (
<header
data-section="navbar-01"
data-slot="section"
className="sticky top-0 z-40 w-full border-b border-rule bg-background text-foreground"
>
<Navbar01Bar
brand={BRAND}
label={NAV_LABEL}
links={LINKS}
signIn={SIGN_IN}
cta={CALL_TO_ACTION}
menu={MENU}
activeHref={activeHref}
defaultOpen={defaultOpen}
/>
</header>
)
}
export default Navbar01"use client"
import * as React from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { MenuIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { currentHref } from "@/lib/nav-config"
import { Button, buttonVariants } from "@/components/ui/button"
import { LogoMark, type LogoName } from "@/components/ui/logo-mark"
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import type { NavLink } from "./navbar-01.data"
export type Navbar01BarProps = {
brand: { name: LogoName; href: string; label: string }
label: string
links: readonly NavLink[]
signIn: NavLink
cta: NavLink
menu: { open: string; title: string; description: string }
/** The page the bar is on; the current route when left out. */
activeHref?: string
defaultOpen?: boolean
}
/**
* The bar itself: the mark, the pages, and the two ways in. Below `lg` the
* pages and the ways in move into a sheet the menu button opens, and the
* filled button stays on the bar down to `sm`. The sheet is the kit's modal
* dialog, so focus is held inside it while it is open, Escape closes it, and
* focus goes back to the button that opened it. The page the reader is on is
* marked for assistive technology and drawn as a rule under its word — in
* the sheet, as the brand tint.
*/
export function Navbar01Bar({ brand, label, links, signIn, cta, menu, activeHref, defaultOpen = false }: Navbar01BarProps) {
const pathname = usePathname()
const current = activeHref ?? currentHref(links.map((link) => link.href), pathname)
const [open, setOpen] = React.useState(defaultOpen)
const close = () => setOpen(false)
return (
<div className="mx-auto flex h-16 max-w-7xl items-center gap-6 px-4 md:px-6 lg:gap-10">
<Link href={brand.href} aria-label={brand.label} className="focus-ring -mx-1 flex h-10 items-center rounded-md px-1">
<LogoMark name={brand.name} />
</Link>
<nav aria-label={label} className="hidden h-full lg:block">
<ul className="flex h-full items-center">
{links.map((link) => (
<li key={link.href} className="h-full">
<Link
href={link.href}
aria-current={link.href === current ? "page" : undefined}
className="relative flex h-full items-center px-3 text-sm text-muted-foreground transition-colors duration-(--duration-fast) ease-(--ease-standard) hover:text-foreground focus-ring-inset aria-[current=page]:font-medium aria-[current=page]:text-foreground aria-[current=page]:after:absolute aria-[current=page]:after:inset-x-3 aria-[current=page]:after:bottom-0 aria-[current=page]:after:h-0.5 aria-[current=page]:after:bg-brand"
>
{link.label}
</Link>
</li>
))}
</ul>
</nav>
<div className="ml-auto flex items-center gap-2">
<Link href={signIn.href} className={cn(buttonVariants({ variant: "ghost" }), "max-lg:hidden")}>
{signIn.label}
</Link>
<Link href={cta.href} className={cn(buttonVariants(), "max-sm:hidden")}>
{cta.label}
</Link>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger
render={
<Button variant="ghost" size="icon" aria-label={menu.open} className="max-md:size-10 lg:hidden">
<MenuIcon />
</Button>
}
/>
<SheetContent side="right" className="gap-0 p-0">
<SheetHeader className="h-16 flex-row items-center border-b border-border px-4 py-0">
<LogoMark name={brand.name} aria-hidden="true" />
<SheetTitle className="sr-only">{menu.title}</SheetTitle>
<SheetDescription className="sr-only">{menu.description}</SheetDescription>
</SheetHeader>
<nav aria-label={label} className="flex flex-col gap-0.5 p-2">
{links.map((link) => (
<Link
key={link.href}
href={link.href}
onClick={close}
aria-current={link.href === current ? "page" : undefined}
className="focus-ring flex h-11 items-center rounded-md px-3 text-base text-foreground transition-colors hover:bg-muted aria-[current=page]:bg-brand-muted aria-[current=page]:font-medium aria-[current=page]:text-brand"
>
{link.label}
</Link>
))}
</nav>
<SheetFooter className="border-t border-border">
<Link href={signIn.href} onClick={close} className={cn(buttonVariants({ variant: "outline", size: "lg" }), "h-10")}>
{signIn.label}
</Link>
<Link href={cta.href} onClick={close} className={cn(buttonVariants({ size: "lg" }), "h-10")}>
{cta.label}
</Link>
</SheetFooter>
</SheetContent>
</Sheet>
</div>
</div>
)
}/**
* Every word `navbar-01` sets: the brand, the pages it links, the two ways
* in, and what the mobile menu is called. The routes are your own site's;
* replace them and the bar, its active page and its menu follow.
*/
import type { LogoName } from "@/components/ui/logo-mark"
export type NavLink = { label: string; href: string }
/** The mark, where it leads, and what the link is called, since the mark alone reads as a name. */
export const BRAND: { name: LogoName; href: string; label: string } = {
name: "Northwind",
href: "/",
label: "Northwind, home",
}
/** The `nav` landmark's name. */
export const NAV_LABEL = "Main"
export const LINKS: readonly NavLink[] = [
{ label: "Product", href: "/product" },
{ label: "Customers", href: "/customers" },
{ label: "Pricing", href: "/pricing" },
{ label: "Docs", href: "/docs" },
{ label: "Changelog", href: "/changelog" },
]
export const SIGN_IN: NavLink = { label: "Sign in", href: "/sign-in" }
export const CALL_TO_ACTION: NavLink = { label: "Start free trial", href: "/sign-up" }
/** The phone's menu: its button, and the sheet's name and description. */
export const MENU = {
open: "Open menu",
title: "Menu",
description: "Every page of the site, and the way in.",
}npx shadcn@latest add @vibra/navbar-01Navbar with a mega menu
navbar-02Product, Solutions and Resources open panels of pages — each with an icon and a line on what it holds, Product's with a column for what is new — beside two links and the two ways in.
import * as React from "react"
import { Navbar02Bar } from "./navbar-02.client"
import { BRAND, CALL_TO_ACTION, LINKS, MENU, MENUS, NAV_LABEL, SIGN_IN } from "./navbar-02.data"
export type Navbar02Props = {
/** The page the bar is on, when the route alone does not say. */
activeHref?: string
/** Opens the phone's menu on the first render — for a preview; false when installed. */
defaultOpen?: boolean
}
/**
* The bar with menus: Product, Solutions and Resources open panels of pages
* from the kit's navigation menu — Product's with a column for what is new —
* beside two plain links and the two ways in. The band is the page's
* `<header>`, holding the menu's `<nav>` named "Main".
*
* A server component; the bar is one island because the menus and the
* phone's sheet hold state. Every word is in `./navbar-02.data`.
*/
export function Navbar02({ activeHref, defaultOpen = false }: Navbar02Props) {
return (
<header
data-section="navbar-02"
data-slot="section"
className="sticky top-0 z-40 w-full border-b border-rule bg-background text-foreground"
>
<Navbar02Bar
brand={BRAND}
label={NAV_LABEL}
menus={MENUS}
links={LINKS}
signIn={SIGN_IN}
cta={CALL_TO_ACTION}
menu={MENU}
activeHref={activeHref}
defaultOpen={defaultOpen}
/>
</header>
)
}
export default Navbar02"use client"
import * as React from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import {
ActivityIcon,
ArrowRightIcon,
BookOpenIcon,
CodeIcon,
CompassIcon,
DatabaseIcon,
GaugeIcon,
HistoryIcon,
LayoutDashboardIcon,
LifeBuoyIcon,
MenuIcon,
PackageIcon,
PlugIcon,
ScrollTextIcon,
TrendingUpIcon,
WalletIcon,
type LucideIcon,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { currentHref } from "@/lib/nav-config"
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion"
import { Badge } from "@/components/ui/badge"
import { Button, buttonVariants } from "@/components/ui/button"
import { LogoMark, type LogoName } from "@/components/ui/logo-mark"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import type { EntryIcon, Menu, MenuEntry, NavLink } from "./navbar-02.data"
/** The picture each page is drawn with: vocabulary of the layout, not copy. */
const ICONS: Record<EntryIcon, LucideIcon> = {
dashboards: LayoutDashboardIcon,
reports: ScrollTextIcon,
forecasts: TrendingUpIcon,
integrations: PlugIcon,
api: CodeIcon,
warehouse: DatabaseIcon,
finance: WalletIcon,
operations: GaugeIcon,
product: PackageIcon,
support: LifeBuoyIcon,
docs: BookOpenIcon,
guides: CompassIcon,
changelog: HistoryIcon,
status: ActivityIcon,
}
export type Navbar02BarProps = {
brand: { name: LogoName; href: string; label: string }
label: string
menus: readonly Menu[]
links: readonly NavLink[]
signIn: NavLink
cta: NavLink
menu: { open: string; title: string; description: string }
activeHref?: string
defaultOpen?: boolean
}
/** One page in a panel: its icon on the surface plane, its name, and one line on what it holds. */
function PanelEntry({ entry, current }: { entry: MenuEntry; current?: string }) {
const Icon = ICONS[entry.icon]
return (
<li>
<NavigationMenuLink render={<Link href={entry.href} />} active={entry.href === current} className="items-start gap-3 p-2.5">
<span className="flex size-8 shrink-0 items-center justify-center rounded-md bg-surface text-foreground ring-1 ring-border">
<Icon aria-hidden="true" />
</span>
<span className="flex flex-col gap-0.5">
<span className="font-medium text-foreground">{entry.label}</span>{" "}
<span className="text-xs text-muted-foreground">{entry.description}</span>
</span>
</NavigationMenuLink>
</li>
)
}
/** A menu's panel: one column per group, or one group two up; a feature takes the last column. */
function Panel({ menu, current }: { menu: Menu; current?: string }) {
const columns = menu.groups.length > 1
return (
<div className={cn("grid gap-2 p-2", menu.feature ? "w-[50rem] grid-cols-[1fr_1fr_15rem]" : "w-[36rem]")}>
{menu.groups.map((group) => (
<div key={group.label} className="flex flex-col">
<p className="px-2.5 pt-2 pb-1 type-eyebrow">{group.label}</p>
<ul className={cn("grid gap-0.5", !columns && "grid-cols-2")}>
{group.entries.map((entry) => (
<PanelEntry key={entry.href} entry={entry} current={current} />
))}
</ul>
</div>
))}
{menu.feature ? (
<div className="flex flex-col items-start gap-2 rounded-md bg-surface p-4 ring-1 ring-border">
<Badge variant="outline">{menu.feature.tag}</Badge>
<p className="mt-1 font-medium text-foreground">{menu.feature.title}</p>
<p className="text-xs text-pretty text-muted-foreground">{menu.feature.body}</p>
<NavigationMenuLink
render={<Link href={menu.feature.link.href} />}
className="mt-auto -ml-2 gap-1.5 px-2 py-1.5 font-medium text-brand"
>
{menu.feature.link.label}
<ArrowRightIcon aria-hidden="true" />
</NavigationMenuLink>
</div>
) : null}
</div>
)
}
/**
* The bar with menus: three triggers that open the kit's navigation menu —
* panels of pages, each with an icon and a line on what it holds, and one
* column given to what is new — beside two plain links and the two ways in.
* The menu is Base UI's: hover or click opens a panel, the arrow keys move
* through it and Escape closes it. Below `lg` the same pages are in a sheet,
* each menu an accordion section, so nothing on the bar is out of reach.
*/
export function Navbar02Bar({ brand, label, menus, links, signIn, cta, menu, activeHref, defaultOpen = false }: Navbar02BarProps) {
const pathname = usePathname()
const hrefs = [...menus.flatMap((item) => item.groups.flatMap((group) => group.entries.map((entry) => entry.href))), ...links.map((link) => link.href)]
const current = activeHref ?? currentHref(hrefs, pathname)
const [open, setOpen] = React.useState(defaultOpen)
const close = () => setOpen(false)
return (
<div className="mx-auto flex h-16 max-w-7xl items-center gap-6 px-4 md:px-6 lg:gap-8">
<Link href={brand.href} aria-label={brand.label} className="focus-ring -mx-1 flex h-10 items-center rounded-md px-1">
<LogoMark name={brand.name} />
</Link>
<NavigationMenu aria-label={label} className="max-lg:hidden">
<NavigationMenuList className="gap-0.5">
{menus.map((item) => (
<NavigationMenuItem key={item.label}>
<NavigationMenuTrigger className="text-muted-foreground hover:text-foreground data-popup-open:text-foreground">
{item.label}
</NavigationMenuTrigger>
<NavigationMenuContent>
<Panel menu={item} current={current} />
</NavigationMenuContent>
</NavigationMenuItem>
))}
{links.map((link) => (
<NavigationMenuItem key={link.href}>
<NavigationMenuLink
render={<Link href={link.href} />}
active={link.href === current}
className={cn(navigationMenuTriggerStyle(), "text-muted-foreground hover:text-foreground data-active:text-foreground")}
>
{link.label}
</NavigationMenuLink>
</NavigationMenuItem>
))}
</NavigationMenuList>
</NavigationMenu>
<div className="ml-auto flex items-center gap-2">
<Link href={signIn.href} className={cn(buttonVariants({ variant: "ghost" }), "max-lg:hidden")}>
{signIn.label}
</Link>
<Link href={cta.href} className={cn(buttonVariants(), "max-sm:hidden")}>
{cta.label}
</Link>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger
render={
<Button variant="ghost" size="icon" aria-label={menu.open} className="max-md:size-10 lg:hidden">
<MenuIcon />
</Button>
}
/>
<SheetContent side="right" className="gap-0 p-0">
<SheetHeader className="h-16 flex-row items-center border-b border-border px-4 py-0">
<LogoMark name={brand.name} aria-hidden="true" />
<SheetTitle className="sr-only">{menu.title}</SheetTitle>
<SheetDescription className="sr-only">{menu.description}</SheetDescription>
</SheetHeader>
<nav aria-label={label} className="flex-1 overflow-y-auto px-4 py-2">
<Accordion>
{menus.map((item) => (
<AccordionItem key={item.label} value={item.label} className="border-border">
<AccordionTrigger className="h-12 items-center py-0 text-base hover:no-underline">{item.label}</AccordionTrigger>
<AccordionContent className="[&_a]:no-underline">
<ul className="flex flex-col">
{item.groups
.flatMap((group) => group.entries)
.map((entry) => {
const Icon = ICONS[entry.icon]
return (
<li key={entry.href}>
<Link
href={entry.href}
onClick={close}
aria-current={entry.href === current ? "page" : undefined}
className="focus-ring flex h-10 items-center gap-3 rounded-md px-2 text-sm text-foreground hover:bg-muted aria-[current=page]:bg-brand-muted aria-[current=page]:font-medium aria-[current=page]:text-brand"
>
<Icon aria-hidden="true" className="size-4 shrink-0 text-muted-foreground" />
{entry.label}
</Link>
</li>
)
})}
</ul>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
<div className="flex flex-col border-t border-border pt-2">
{links.map((link) => (
<Link
key={link.href}
href={link.href}
onClick={close}
aria-current={link.href === current ? "page" : undefined}
className="focus-ring flex h-12 items-center rounded-md text-base font-medium text-foreground aria-[current=page]:text-brand"
>
{link.label}
</Link>
))}
</div>
</nav>
<SheetFooter className="border-t border-border">
<Link href={signIn.href} onClick={close} className={cn(buttonVariants({ variant: "outline", size: "lg" }), "h-10")}>
{signIn.label}
</Link>
<Link href={cta.href} onClick={close} className={cn(buttonVariants({ size: "lg" }), "h-10")}>
{cta.label}
</Link>
</SheetFooter>
</SheetContent>
</Sheet>
</div>
</div>
)
}/**
* Every word `navbar-02` sets: the brand, the three menus with every page in
* them, the plain links beside them, the two ways in, and the phone menu's
* name. Each page names an icon the layout draws; the words live here, the
* drawing with the component. Replace the pages and every panel follows.
*/
import type { LogoName } from "@/components/ui/logo-mark"
export type NavLink = { label: string; href: string }
export type EntryIcon =
| "dashboards"
| "reports"
| "forecasts"
| "integrations"
| "api"
| "warehouse"
| "finance"
| "operations"
| "product"
| "support"
| "docs"
| "guides"
| "changelog"
| "status"
/** A page inside a menu: what it is called, where it is, one line on what it holds. */
export type MenuEntry = NavLink & { description: string; icon: EntryIcon }
export type Menu = {
label: string
groups: readonly { label: string; entries: readonly MenuEntry[] }[]
/** A panel may give its last column to one thing worth reading now. */
feature?: { tag: string; title: string; body: string; link: NavLink }
}
export const BRAND: { name: LogoName; href: string; label: string } = {
name: "Northwind",
href: "/",
label: "Northwind, home",
}
export const NAV_LABEL = "Main"
export const MENUS: readonly Menu[] = [
{
label: "Product",
groups: [
{
label: "Analyze",
entries: [
{ label: "Dashboards", href: "/product/dashboards", description: "Every team's figures on one page", icon: "dashboards" },
{ label: "Reports", href: "/product/reports", description: "Scheduled, shared and never rebuilt", icon: "reports" },
{ label: "Forecasts", href: "/product/forecasts", description: "The rest of the quarter, with its working", icon: "forecasts" },
],
},
{
label: "Connect",
entries: [
{ label: "Integrations", href: "/integrations", description: "Billing, CRM, product and support", icon: "integrations" },
{ label: "API", href: "/docs/api", description: "Read and write every metric", icon: "api" },
{ label: "Warehouse sync", href: "/product/warehouse", description: "Your models, refreshed hourly", icon: "warehouse" },
],
},
],
feature: {
tag: "New",
title: "Forecasts are here",
body: "Project the quarter from your own history, with the assumption behind every line.",
link: { label: "Read the launch notes", href: "/changelog/4-0" },
},
},
{
label: "Solutions",
groups: [
{
label: "By team",
entries: [
{ label: "Finance", href: "/solutions/finance", description: "Close the month in two days", icon: "finance" },
{ label: "Operations", href: "/solutions/operations", description: "Know what moved and why", icon: "operations" },
{ label: "Product", href: "/solutions/product", description: "Usage beside the revenue it earns", icon: "product" },
{ label: "Support", href: "/solutions/support", description: "Tickets matched to accounts", icon: "support" },
],
},
],
},
{
label: "Resources",
groups: [
{
label: "Learn",
entries: [
{ label: "Documentation", href: "/docs", description: "Set up, connect and model", icon: "docs" },
{ label: "Guides", href: "/guides", description: "Recipes for common reports", icon: "guides" },
{ label: "Changelog", href: "/changelog", description: "What shipped, every week", icon: "changelog" },
{ label: "Status", href: "/status", description: "Uptime and incidents", icon: "status" },
],
},
],
},
]
/** The pages that need no menu, after the three that do. */
export const LINKS: readonly NavLink[] = [
{ label: "Customers", href: "/customers" },
{ label: "Pricing", href: "/pricing" },
]
export const SIGN_IN: NavLink = { label: "Sign in", href: "/sign-in" }
export const CALL_TO_ACTION: NavLink = { label: "Book a demo", href: "/demo" }
export const MENU = {
open: "Open menu",
title: "Menu",
description: "Every page of the site, grouped as the bar groups them, and the way in.",
}npx shadcn@latest add @vibra/navbar-02Docs navbar with search
navbar-03A documentation site's bar: the mark and its section, the docs' sections, a search field that `/` jumps to, and the two ways in; on a phone the search button opens the sheet into the field.
import * as React from "react"
import { Navbar03Bar } from "./navbar-03.client"
import { BRAND, CALL_TO_ACTION, LINKS, MENU, NAV_LABEL, SEARCH, SIGN_IN } from "./navbar-03.data"
export type Navbar03Props = {
/** The page the bar is on, when the route alone does not say. */
activeHref?: string
/** Opens the phone's menu on the first render — for a preview; false when installed. */
defaultOpen?: boolean
}
/**
* A documentation site's bar with search: the mark and the section it names,
* the docs' sections, a search field that `/` jumps to, and the two ways in.
* The band is the page's `<header>`, holding the `<nav>` named
* "Documentation" and a search landmark.
*
* A server component; the bar is one island because it reads the current
* route, listens for its shortcut and owns the phone's sheet. Every word is
* in `./navbar-03.data`.
*/
export function Navbar03({ activeHref, defaultOpen = false }: Navbar03Props) {
return (
<header
data-section="navbar-03"
data-slot="section"
className="sticky top-0 z-40 w-full border-b border-rule bg-background text-foreground"
>
<Navbar03Bar
brand={BRAND}
label={NAV_LABEL}
links={LINKS}
search={SEARCH}
signIn={SIGN_IN}
cta={CALL_TO_ACTION}
menu={MENU}
activeHref={activeHref}
defaultOpen={defaultOpen}
/>
</header>
)
}
export default Navbar03"use client"
import * as React from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { MenuIcon, SearchIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { currentHref } from "@/lib/nav-config"
import { Button, buttonVariants } from "@/components/ui/button"
import { LogoMark, type LogoName } from "@/components/ui/logo-mark"
import { SearchInput } from "@/components/ui/search-input"
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import type { NavLink } from "./navbar-03.data"
type Search = { action: string; param: string; label: string; placeholder: string; shortcut: string; open: string }
export type Navbar03BarProps = {
brand: { name: LogoName; section: string; href: string; label: string }
label: string
links: readonly NavLink[]
search: Search
signIn: NavLink
cta: NavLink
menu: { open: string; title: string; description: string }
activeHref?: string
defaultOpen?: boolean
}
/** The search as a plain GET form: Enter sends the query to `action`, with or without script. */
function SearchForm({
search,
id,
scope,
className,
}: {
search: Search
id?: string
/** The band the field's shortcut answers in; without one it prints no keycap and binds nothing. */
scope?: React.RefObject<HTMLElement | null>
className?: string
}) {
return (
<form role="search" action={search.action} method="get" aria-label={search.label} className={className}>
<SearchInput
id={id}
name={search.param}
aria-label={search.label}
placeholder={search.placeholder}
shortcut={scope ? search.shortcut : undefined}
// The band's own key, not the window's: a page holds other bands.
shortcutScope={scope}
/>
</form>
)
}
/**
* A documentation site's bar: the mark and the section it names, the docs'
* sections, and a search field — a GET form, so a query reaches your search
* page with or without script — that `/` jumps to from anywhere outside a
* field. Below `lg` the sections move into a sheet; below `md` the field does
* too, and the search button opens the sheet with the field already focused.
* The sheet is the kit's modal dialog: it holds focus, closes on Escape, and
* gives focus back to whichever button opened it.
*/
export function Navbar03Bar({ brand, label, links, search, signIn, cta, menu, activeHref, defaultOpen = false }: Navbar03BarProps) {
const pathname = usePathname()
const current = activeHref ?? currentHref(links.map((link) => link.href), pathname)
const sheetSearchId = React.useId()
const sheetNavId = React.useId()
const [open, setOpen] = React.useState(defaultOpen)
const [searchFirst, setSearchFirst] = React.useState(false)
const close = () => setOpen(false)
const bar = React.useRef<HTMLDivElement>(null)
return (
<div ref={bar} className="mx-auto flex h-16 max-w-7xl items-center gap-4 px-4 md:px-6 lg:gap-8">
<Link href={brand.href} aria-label={brand.label} className="focus-ring -mx-1 flex h-10 shrink-0 items-center gap-2.5 rounded-md px-1">
<LogoMark name={brand.name} />
<span aria-hidden="true" className="h-5 w-px bg-border" />
<span className="text-sm font-medium text-muted-foreground">{brand.section}</span>
</Link>
<nav aria-label={label} className="hidden h-full lg:block">
<ul className="flex h-full items-center">
{links.map((link) => (
<li key={link.href} className="h-full">
<Link
href={link.href}
aria-current={link.href === current ? "page" : undefined}
className="relative flex h-full items-center px-3 text-sm whitespace-nowrap text-muted-foreground transition-colors hover:text-foreground focus-ring-inset aria-[current=page]:font-medium aria-[current=page]:text-foreground aria-[current=page]:after:absolute aria-[current=page]:after:inset-x-3 aria-[current=page]:after:bottom-0 aria-[current=page]:after:h-0.5 aria-[current=page]:after:bg-brand"
>
{link.label}
</Link>
</li>
))}
</ul>
</nav>
<SearchForm search={search} scope={bar} className="ms-auto hidden w-full max-w-xs md:block lg:max-w-60 xl:max-w-xs" />
<div className="flex items-center gap-2 max-md:ms-auto">
<Link href={signIn.href} className={cn(buttonVariants({ variant: "ghost" }), "max-lg:hidden")}>
{signIn.label}
</Link>
<Link href={cta.href} className={cn(buttonVariants(), "max-lg:hidden")}>
{cta.label}
</Link>
<Button
variant="ghost"
size="icon"
aria-label={search.open}
className="max-md:size-10 md:hidden"
onClick={() => {
setSearchFirst(true)
setOpen(true)
}}
>
<SearchIcon />
</Button>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger
render={
<Button
variant="ghost"
size="icon"
aria-label={menu.open}
className="max-md:size-10 lg:hidden"
onClick={() => setSearchFirst(false)}
>
<MenuIcon />
</Button>
}
/>
<SheetContent
side="right"
className="gap-0 p-0"
// The search button lands in the field; the menu button on the
// first page, so opening the menu never raises a phone's keyboard.
initialFocus={() =>
searchFirst
? document.getElementById(sheetSearchId)
: (document.getElementById(sheetNavId)?.querySelector<HTMLElement>("a") ?? true)
}
>
<SheetHeader className="h-16 flex-row items-center gap-2.5 border-b border-border px-4 py-0">
<LogoMark name={brand.name} aria-hidden="true" />
<span aria-hidden="true" className="text-sm font-medium text-muted-foreground">
{brand.section}
</span>
<SheetTitle className="sr-only">{menu.title}</SheetTitle>
<SheetDescription className="sr-only">{menu.description}</SheetDescription>
</SheetHeader>
<div className="border-b border-border p-4">
<SearchForm search={search} id={sheetSearchId} />
</div>
<nav id={sheetNavId} aria-label={label} className="flex flex-col gap-0.5 p-2">
{links.map((link) => (
<Link
key={link.href}
href={link.href}
onClick={close}
aria-current={link.href === current ? "page" : undefined}
className="focus-ring flex h-11 items-center rounded-md px-3 text-base text-foreground transition-colors hover:bg-muted aria-[current=page]:bg-brand-muted aria-[current=page]:font-medium aria-[current=page]:text-brand"
>
{link.label}
</Link>
))}
</nav>
<SheetFooter className="border-t border-border">
<Link href={signIn.href} onClick={close} className={cn(buttonVariants({ variant: "outline", size: "lg" }), "h-10")}>
{signIn.label}
</Link>
<Link href={cta.href} onClick={close} className={cn(buttonVariants({ size: "lg" }), "h-10")}>
{cta.label}
</Link>
</SheetFooter>
</SheetContent>
</Sheet>
</div>
</div>
)
}/**
* Every word `navbar-03` sets: a documentation site's bar — the brand and the
* section it names, the sections of the docs, the search and where it sends
* a query, the two ways in, and the phone menu's name. The search is a plain
* GET form, so it works before a line of script has loaded: point `action`
* at your own search page.
*/
import type { LogoName } from "@/components/ui/logo-mark"
export type NavLink = { label: string; href: string }
export const BRAND: { name: LogoName; section: string; href: string; label: string } = {
name: "Northwind",
section: "Docs",
href: "/docs",
label: "Northwind Docs, home",
}
export const NAV_LABEL = "Documentation"
export const LINKS: readonly NavLink[] = [
{ label: "Guides", href: "/docs/guides" },
{ label: "API reference", href: "/docs/api" },
{ label: "Integrations", href: "/docs/integrations" },
{ label: "Changelog", href: "/changelog" },
]
/** The search: where a query goes, under which parameter, and the key that jumps to it. */
export const SEARCH = {
action: "/docs/search",
param: "q",
label: "Search the docs",
placeholder: "Search the docs",
shortcut: "/",
open: "Search the docs",
}
export const SIGN_IN: NavLink = { label: "Sign in", href: "/sign-in" }
export const CALL_TO_ACTION: NavLink = { label: "Start free trial", href: "/sign-up" }
export const MENU = {
open: "Open menu",
title: "Menu",
description: "The documentation's sections, its search and the way in.",
}npx shadcn@latest add @vibra/navbar-03Navbar with an announcement bar
navbar-04One line on what is new, linked and dismissible for good, above the mark, the pages and the two ways in.
import * as React from "react"
import { Navbar04Bar } from "./navbar-04.client"
import { ANNOUNCEMENT, BRAND, CALL_TO_ACTION, LINKS, MENU, NAV_LABEL, SIGN_IN } from "./navbar-04.data"
export type Navbar04Props = {
/** The page the bar is on, when the route alone does not say. */
activeHref?: string
/** Opens the phone's menu on the first render — for a preview; false when installed. */
defaultOpen?: boolean
}
/**
* The bar with an announcement over it: one line on what is new, linked to
* where to read about it and dismissible for good, above the mark, the pages
* and the two ways in. Both rows are the page's `<header>`, which holds the
* `<nav>` named "Main".
*
* A server component; the bar is one island because it remembers the
* dismissal, reads the current route and owns the phone's sheet. Every word
* is in `./navbar-04.data`.
*/
export function Navbar04({ activeHref, defaultOpen = false }: Navbar04Props) {
return (
<header
data-section="navbar-04"
data-slot="section"
className="sticky top-0 z-40 w-full border-b border-rule bg-background text-foreground"
>
<Navbar04Bar
announcement={ANNOUNCEMENT}
brand={BRAND}
label={NAV_LABEL}
links={LINKS}
signIn={SIGN_IN}
cta={CALL_TO_ACTION}
menu={MENU}
activeHref={activeHref}
defaultOpen={defaultOpen}
/>
</header>
)
}
export default Navbar04"use client"
import * as React from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { ArrowRightIcon, MenuIcon, XIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { useLocalStorage } from "@/hooks/use-local-storage"
import { currentHref } from "@/lib/nav-config"
import { Badge } from "@/components/ui/badge"
import { Button, buttonVariants } from "@/components/ui/button"
import { LogoMark, type LogoName } from "@/components/ui/logo-mark"
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import type { NavLink } from "./navbar-04.data"
export type Navbar04BarProps = {
announcement: { id: string; tag: string; text: string; link: NavLink; dismiss: string }
brand: { name: LogoName; href: string; label: string }
label: string
links: readonly NavLink[]
signIn: NavLink
cta: NavLink
menu: { open: string; title: string; description: string }
activeHref?: string
defaultOpen?: boolean
}
/**
* An announcement over the bar: what is new as one link, and a button that
* puts it away — remembered in the browser under the announcement's own id,
* so it stays away until there is a new one, and handing focus to the brand
* so a keyboard is not dropped at the top of the page. It is a strip on the
* surface plane, not a notice: it announces nothing to a screen reader
* uninvited, and reads in its place in the header.
*
* Under it, the bar: the mark, the pages and the two ways in, the pages and
* the ways in moving into the kit's Sheet below `lg`.
*/
export function Navbar04Bar({ announcement, brand, label, links, signIn, cta, menu, activeHref, defaultOpen = false }: Navbar04BarProps) {
const pathname = usePathname()
const current = activeHref ?? currentHref(links.map((link) => link.href), pathname)
const [dismissed, setDismissed] = useLocalStorage(`announcement:${announcement.id}`, false)
const [open, setOpen] = React.useState(defaultOpen)
const brandRef = React.useRef<HTMLAnchorElement>(null)
const close = () => setOpen(false)
return (
<>
{dismissed ? null : (
<div className="border-b border-border bg-surface">
<div className="mx-auto grid max-w-7xl grid-cols-[2rem_minmax(0,1fr)_2rem] items-center gap-2 px-4 py-1.5 md:px-6">
<Link
href={announcement.link.href}
className="group/announcement focus-ring col-start-2 inline-flex min-w-0 items-center justify-self-center gap-x-2 rounded-md py-1 text-sm text-foreground"
>
<Badge className="shrink-0">{announcement.tag}</Badge>{" "}
<span className="min-w-0 text-pretty">
{announcement.text}{" "}
<span className="font-medium max-md:sr-only">
<span aria-hidden="true" className="text-muted-foreground">
·
</span>{" "}
{announcement.link.label}
</span>
</span>
<ArrowRightIcon
aria-hidden="true"
className="size-3.5 shrink-0 text-muted-foreground transition-transform duration-(--duration-fast) ease-(--ease-standard) group-hover/announcement:translate-x-0.5"
/>
</Link>
<Button
variant="ghost"
size="icon-sm"
aria-label={announcement.dismiss}
className="justify-self-end text-muted-foreground max-md:size-10 hover:text-foreground"
onClick={() => {
setDismissed(true)
brandRef.current?.focus()
}}
>
<XIcon />
</Button>
</div>
</div>
)}
<div className="mx-auto flex h-16 max-w-7xl items-center gap-6 px-4 md:px-6 lg:gap-10">
<Link ref={brandRef} href={brand.href} aria-label={brand.label} className="focus-ring -mx-1 flex h-10 items-center rounded-md px-1">
<LogoMark name={brand.name} />
</Link>
<nav aria-label={label} className="hidden h-full lg:block">
<ul className="flex h-full items-center">
{links.map((link) => (
<li key={link.href} className="h-full">
<Link
href={link.href}
aria-current={link.href === current ? "page" : undefined}
className="relative flex h-full items-center px-3 text-sm text-muted-foreground transition-colors hover:text-foreground focus-ring-inset aria-[current=page]:font-medium aria-[current=page]:text-foreground aria-[current=page]:after:absolute aria-[current=page]:after:inset-x-3 aria-[current=page]:after:bottom-0 aria-[current=page]:after:h-0.5 aria-[current=page]:after:bg-brand"
>
{link.label}
</Link>
</li>
))}
</ul>
</nav>
<div className="ml-auto flex items-center gap-2">
<Link href={signIn.href} className={cn(buttonVariants({ variant: "ghost" }), "max-lg:hidden")}>
{signIn.label}
</Link>
<Link href={cta.href} className={cn(buttonVariants(), "max-sm:hidden")}>
{cta.label}
</Link>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger
render={
<Button variant="ghost" size="icon" aria-label={menu.open} className="max-md:size-10 lg:hidden">
<MenuIcon />
</Button>
}
/>
<SheetContent side="right" className="gap-0 p-0">
<SheetHeader className="h-16 flex-row items-center border-b border-border px-4 py-0">
<LogoMark name={brand.name} aria-hidden="true" />
<SheetTitle className="sr-only">{menu.title}</SheetTitle>
<SheetDescription className="sr-only">{menu.description}</SheetDescription>
</SheetHeader>
<nav aria-label={label} className="flex flex-col gap-0.5 p-2">
{links.map((link) => (
<Link
key={link.href}
href={link.href}
onClick={close}
aria-current={link.href === current ? "page" : undefined}
className="focus-ring flex h-11 items-center rounded-md px-3 text-base text-foreground transition-colors hover:bg-muted aria-[current=page]:bg-brand-muted aria-[current=page]:font-medium aria-[current=page]:text-brand"
>
{link.label}
</Link>
))}
</nav>
<SheetFooter className="border-t border-border">
<Link href={signIn.href} onClick={close} className={cn(buttonVariants({ variant: "outline", size: "lg" }), "h-10")}>
{signIn.label}
</Link>
<Link href={cta.href} onClick={close} className={cn(buttonVariants({ size: "lg" }), "h-10")}>
{cta.label}
</Link>
</SheetFooter>
</SheetContent>
</Sheet>
</div>
</div>
</>
)
}/**
* Every word `navbar-04` sets: the announcement over the bar — what is new,
* where to read about it, and the key its dismissal is remembered under —
* then the brand, the pages, the two ways in and the phone menu's name.
* Change `id` when the announcement changes, and a reader who dismissed the
* last one sees the new one.
*/
import type { LogoName } from "@/components/ui/logo-mark"
export type NavLink = { label: string; href: string }
export const ANNOUNCEMENT = {
/** Names this announcement in storage; a new one needs a new id. */
id: "release-4-0",
tag: "New",
text: "Northwind 4.0 is out, with forecasts and anomaly alerts",
link: { label: "Read the release notes", href: "/changelog/4-0" } satisfies NavLink,
dismiss: "Dismiss the announcement",
}
export const BRAND: { name: LogoName; href: string; label: string } = {
name: "Northwind",
href: "/",
label: "Northwind, home",
}
export const NAV_LABEL = "Main"
export const LINKS: readonly NavLink[] = [
{ label: "Product", href: "/product" },
{ label: "Customers", href: "/customers" },
{ label: "Pricing", href: "/pricing" },
{ label: "Docs", href: "/docs" },
]
export const SIGN_IN: NavLink = { label: "Sign in", href: "/sign-in" }
export const CALL_TO_ACTION: NavLink = { label: "Start free trial", href: "/sign-up" }
export const MENU = {
open: "Open menu",
title: "Menu",
description: "Every page of the site, and the way in.",
}npx shadcn@latest add @vibra/navbar-04Store navbar with a centred logo
navbar-05The mark on the page's centre line, the departments to its left, and search, the account and the cart with its count to its right.
import * as React from "react"
import { Navbar05Bar } from "./navbar-05.client"
import { ACCOUNT, BRAND, CART, DEPARTMENTS, MENU, NAV_LABEL, SEARCH } from "./navbar-05.data"
export type Navbar05Props = {
/** How many items are in the reader's cart; the data file's sample count when left out. */
cartCount?: number
/** The department the page is in, when the route alone does not say. */
activeHref?: string
/** Opens the phone's menu on the first render — for a preview; false when installed. */
defaultOpen?: boolean
}
/**
* A store's bar with the mark in the middle: the departments to its left,
* and search, the account and the cart — with its count — to its right. The
* band is the page's `<header>`, holding the `<nav>` named "Departments".
*
* A server component; the bar is one island because it reads the current
* route and owns the phone's sheet. Every word is in `./navbar-05.data`;
* pass `cartCount` from your cart.
*/
export function Navbar05({ cartCount = CART.count, activeHref, defaultOpen = false }: Navbar05Props) {
return (
<header
data-section="navbar-05"
data-slot="section"
className="sticky top-0 z-40 w-full border-b border-rule bg-background text-foreground"
>
<Navbar05Bar
brand={BRAND}
label={NAV_LABEL}
departments={DEPARTMENTS}
search={SEARCH}
account={ACCOUNT}
cart={CART}
cartCount={cartCount}
menu={MENU}
activeHref={activeHref}
defaultOpen={defaultOpen}
/>
</header>
)
}
export default Navbar05"use client"
import * as React from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { MenuIcon, SearchIcon, ShoppingBagIcon, UserIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { currentHref } from "@/lib/nav-config"
import { Button, buttonVariants } from "@/components/ui/button"
import { LogoMark, type LogoName } from "@/components/ui/logo-mark"
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import type { NavLink } from "./navbar-05.data"
export type Navbar05BarProps = {
brand: { name: LogoName; caption: string; href: string; label: string }
label: string
departments: readonly NavLink[]
search: NavLink
account: NavLink
cart: NavLink
cartCount: number
menu: { open: string; title: string; description: string }
activeHref?: string
defaultOpen?: boolean
}
/** The mark and the part of the company it names, as one lockup. */
function Lockup({ brand }: { brand: Navbar05BarProps["brand"] }) {
return (
<>
<LogoMark name={brand.name} />
<span aria-hidden="true" className="h-5 w-px bg-border" />
<span className="text-sm font-medium text-muted-foreground">{brand.caption}</span>
</>
)
}
/**
* A store's bar with the mark in the middle: three columns, the outer two
* the same width, so the mark sits on the page's centre line whatever is
* either side of it. The departments are on the left and search, the account
* and the cart on the right, each an icon named for where it goes — the cart
* with how much is in it. Below `lg` the departments move into a sheet that
* slides in from the side they were on; the cart never leaves the bar.
*/
export function Navbar05Bar({
brand,
label,
departments,
search,
account,
cart,
cartCount,
menu,
activeHref,
defaultOpen = false,
}: Navbar05BarProps) {
const pathname = usePathname()
const current = activeHref ?? currentHref(departments.map((link) => link.href), pathname)
const [open, setOpen] = React.useState(defaultOpen)
const close = () => setOpen(false)
const cartLabel = `${cart.label}, ${cartCount} ${cartCount === 1 ? "item" : "items"}`
const utility = [
{ ...search, icon: SearchIcon },
{ ...account, icon: UserIcon },
]
return (
<div className="mx-auto grid h-16 max-w-7xl grid-cols-[1fr_auto_1fr] items-center gap-4 px-4 md:px-6">
<div className="flex h-full items-center">
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger
render={
<Button variant="ghost" size="icon" aria-label={menu.open} className="-ml-2 max-md:size-10 lg:hidden">
<MenuIcon />
</Button>
}
/>
<SheetContent side="left" className="gap-0 p-0">
<SheetHeader className="h-16 flex-row items-center gap-2.5 border-b border-border px-4 py-0">
<span aria-hidden="true" className="flex items-center gap-2.5">
<Lockup brand={brand} />
</span>
<SheetTitle className="sr-only">{menu.title}</SheetTitle>
<SheetDescription className="sr-only">{menu.description}</SheetDescription>
</SheetHeader>
<nav aria-label={label} className="flex flex-col gap-0.5 p-2">
{departments.map((link) => (
<Link
key={link.href}
href={link.href}
onClick={close}
aria-current={link.href === current ? "page" : undefined}
className="focus-ring flex h-11 items-center rounded-md px-3 text-base text-foreground transition-colors hover:bg-muted aria-[current=page]:bg-brand-muted aria-[current=page]:font-medium aria-[current=page]:text-brand"
>
{link.label}
</Link>
))}
</nav>
<div className="flex flex-col gap-0.5 border-t border-border p-2">
{utility.map(({ icon: Icon, ...link }) => (
<Link
key={link.href}
href={link.href}
onClick={close}
className="focus-ring flex h-11 items-center gap-3 rounded-md px-3 text-sm text-foreground transition-colors hover:bg-muted"
>
<Icon aria-hidden="true" className="size-4 text-muted-foreground" />
{link.label}
</Link>
))}
</div>
</SheetContent>
</Sheet>
<nav aria-label={label} className="hidden h-full lg:block">
<ul className="-ml-3 flex h-full items-center">
{departments.map((link) => (
<li key={link.href} className="h-full">
<Link
href={link.href}
aria-current={link.href === current ? "page" : undefined}
className="relative flex h-full items-center px-3 text-sm text-muted-foreground transition-colors hover:text-foreground focus-ring-inset aria-[current=page]:font-medium aria-[current=page]:text-foreground aria-[current=page]:after:absolute aria-[current=page]:after:inset-x-3 aria-[current=page]:after:bottom-0 aria-[current=page]:after:h-0.5 aria-[current=page]:after:bg-brand"
>
{link.label}
</Link>
</li>
))}
</ul>
</nav>
</div>
<Link href={brand.href} aria-label={brand.label} className="focus-ring flex h-10 items-center gap-2.5 rounded-md px-1">
<Lockup brand={brand} />
</Link>
<div className="flex items-center justify-end gap-1">
{utility.map(({ icon: Icon, ...link }) => (
<Link
key={link.href}
href={link.href}
aria-label={link.label}
className={cn(buttonVariants({ variant: "ghost", size: "icon" }), "max-md:hidden")}
>
<Icon aria-hidden="true" />
</Link>
))}
<Link
href={cart.href}
aria-label={cartLabel}
className={cn(buttonVariants({ variant: "ghost", size: "icon" }), "relative -mr-2 max-md:size-10 md:mr-0")}
>
<ShoppingBagIcon aria-hidden="true" />
{cartCount > 0 ? (
<span
aria-hidden="true"
className="absolute top-0.5 right-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-foreground px-1 text-xs/none font-medium text-background tabular-nums max-md:top-1 max-md:right-1"
>
{cartCount}
</span>
) : null}
</Link>
</div>
</div>
)
}/**
* Every word `navbar-05` sets: a store's bar with its mark in the middle —
* the departments on the left, and search, the account and the cart on the
* right — plus the phone menu's name. The cart's count is the section's
* `cartCount` prop; this is only what it shows before your cart says.
*/
import type { LogoName } from "@/components/ui/logo-mark"
export type NavLink = { label: string; href: string }
export const BRAND: { name: LogoName; caption: string; href: string; label: string } = {
name: "Northwind",
caption: "Store",
href: "/",
label: "Northwind Store, home",
}
export const NAV_LABEL = "Departments"
export const DEPARTMENTS: readonly NavLink[] = [
{ label: "New in", href: "/collections/new" },
{ label: "Office", href: "/collections/office" },
{ label: "Furniture", href: "/collections/furniture" },
{ label: "Outdoor", href: "/collections/outdoor" },
]
/** The three places on the right, each an icon named for what it opens. */
export const SEARCH: NavLink = { label: "Search the store", href: "/search" }
export const ACCOUNT: NavLink = { label: "Your account", href: "/account" }
export const CART = { label: "Cart", href: "/cart", count: 2 }
export const MENU = {
open: "Open menu",
title: "Menu",
description: "The store's departments, search and your account.",
}npx shadcn@latest add @vibra/navbar-05Navbar with a sidebar drawer
navbar-06Three pages on the bar and the whole site one Menu button away, in a drawer drawn as the app's sidebar: sections, icons, the current page filled.
import * as React from "react"
import { Navbar06Bar } from "./navbar-06.client"
import { BRAND, CALL_TO_ACTION, DRAWER_LABEL, GROUPS, MENU, NAV_LABEL, QUICK_LINKS, SIGN_IN } from "./navbar-06.data"
export type Navbar06Props = {
/** The page the bar is on, when the route alone does not say. */
activeHref?: string
/** Opens the drawer on the first render — for a preview; false when installed. */
defaultOpen?: boolean
}
/**
* The bar that keeps three pages in view and the whole site in a drawer: a
* Menu button at its start opens the site's sections as a sidebar — groups,
* icons, the current page filled — from the left, at every width. The band
* is the page's `<header>`, holding the `<nav>` named "Main"; the drawer's
* own `<nav>` is "All pages".
*
* A server component; the bar is one island because it reads the current
* route and owns the drawer. Every word is in `./navbar-06.data`.
*/
export function Navbar06({ activeHref, defaultOpen = false }: Navbar06Props) {
return (
<header
data-section="navbar-06"
data-slot="section"
className="sticky top-0 z-40 w-full border-b border-rule bg-background text-foreground"
>
<Navbar06Bar
brand={BRAND}
label={NAV_LABEL}
quickLinks={QUICK_LINKS}
drawerLabel={DRAWER_LABEL}
groups={GROUPS}
signIn={SIGN_IN}
cta={CALL_TO_ACTION}
menu={MENU}
activeHref={activeHref}
defaultOpen={defaultOpen}
/>
</header>
)
}
export default Navbar06"use client"
import * as React from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import {
ActivityIcon,
BookOpenIcon,
BriefcaseIcon,
HistoryIcon,
LayoutDashboardIcon,
MailIcon,
MenuIcon,
PlugIcon,
ScrollTextIcon,
ShieldCheckIcon,
TrendingUpIcon,
UsersIcon,
type LucideIcon,
} from "lucide-react"
import { cn } from "@/lib/utils"
import { currentHref } from "@/lib/nav-config"
import { Button, buttonVariants } from "@/components/ui/button"
import { LogoMark, type LogoName } from "@/components/ui/logo-mark"
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import type { DrawerIcon, DrawerLink, NavLink } from "./navbar-06.data"
/** The picture each page is drawn with: vocabulary of the layout, not copy. */
const ICONS: Record<DrawerIcon, LucideIcon> = {
dashboards: LayoutDashboardIcon,
reports: ScrollTextIcon,
forecasts: TrendingUpIcon,
integrations: PlugIcon,
customers: UsersIcon,
changelog: HistoryIcon,
careers: BriefcaseIcon,
contact: MailIcon,
docs: BookOpenIcon,
status: ActivityIcon,
security: ShieldCheckIcon,
}
export type Navbar06BarProps = {
brand: { name: LogoName; href: string; label: string }
label: string
quickLinks: readonly NavLink[]
drawerLabel: string
groups: readonly { label: string; links: readonly DrawerLink[] }[]
signIn: NavLink
cta: NavLink
menu: { open: string; title: string; description: string }
activeHref?: string
defaultOpen?: boolean
}
/**
* A bar that keeps three pages in view and the whole site one button away.
* The button — the word "Menu" from `md`, the icon alone on a phone — opens
* a drawer from the left drawn as the kit's sidebar: its quiet ground, the
* sections as eyebrows, every page a row with its icon, the page the reader
* is on a filled pill, a word like "New" an outlined chip in the success
* tone, and the two ways in at its foot. The drawer is the kit's Sheet, so it
* holds focus, closes on Escape or on a chosen page, and hands focus back to
* the button.
*/
export function Navbar06Bar({
brand,
label,
quickLinks,
drawerLabel,
groups,
signIn,
cta,
menu,
activeHref,
defaultOpen = false,
}: Navbar06BarProps) {
const pathname = usePathname()
const groupId = React.useId()
const hrefs = [...quickLinks.map((link) => link.href), ...groups.flatMap((group) => group.links.map((link) => link.href))]
const current = activeHref ?? currentHref(hrefs, pathname)
const [open, setOpen] = React.useState(defaultOpen)
const drawerRef = React.useRef<HTMLDivElement>(null)
// Open on the first render with no one asking — a preview — focus waits on
// the drawer itself; opened by the reader, it lands on the first page.
const openedByDefault = React.useRef(defaultOpen)
const change = (next: boolean) => {
openedByDefault.current = false
setOpen(next)
}
const close = () => change(false)
return (
<div className="mx-auto flex h-16 max-w-7xl items-center gap-2 px-4 md:gap-4 md:px-6">
<Sheet open={open} onOpenChange={change}>
<SheetTrigger
render={
<Button variant="ghost" className="-ml-2 max-md:size-10 max-md:px-0 md:-ml-2.5">
<MenuIcon aria-hidden="true" />
<span className="max-md:sr-only">{menu.open}</span>
</Button>
}
/>
<SheetContent
ref={drawerRef}
side="left"
initialFocus={() => (openedByDefault.current ? drawerRef.current : true)}
className="w-72 gap-0 bg-sidebar p-0 text-sidebar-foreground outline-none data-[side=left]:sm:max-w-72"
>
<SheetHeader className="h-16 flex-row items-center px-5 py-0">
<LogoMark name={brand.name} aria-hidden="true" />
<SheetTitle className="sr-only">{menu.title}</SheetTitle>
<SheetDescription className="sr-only">{menu.description}</SheetDescription>
</SheetHeader>
<nav aria-label={drawerLabel} className="flex flex-1 flex-col gap-5 overflow-y-auto px-3 pb-4">
{groups.map((group, index) => (
<div key={group.label} className="flex flex-col gap-1">
<p id={`${groupId}-${index}`} className="px-2 type-eyebrow">
{group.label}
</p>
<ul aria-labelledby={`${groupId}-${index}`} className="flex flex-col gap-0.5">
{group.links.map((link) => {
const Icon = ICONS[link.icon]
return (
<li key={link.href}>
<Link
href={link.href}
onClick={close}
aria-current={link.href === current ? "page" : undefined}
className="focus-ring flex h-10 items-center gap-2 rounded-md px-2 text-sm text-sidebar-foreground transition-colors hover:bg-sidebar-accent/60 md:h-8 aria-[current=page]:bg-sidebar-accent aria-[current=page]:font-medium aria-[current=page]:text-sidebar-accent-foreground"
>
<Icon aria-hidden="true" className="size-4 shrink-0" />
{link.label}
{link.badge ? " " : null}
{link.badge ? (
<span className="ml-auto inline-flex h-5 items-center rounded-md border border-success/40 px-1 text-xs font-medium text-success">
{link.badge}
</span>
) : null}
</Link>
</li>
)
})}
</ul>
</div>
))}
</nav>
<SheetFooter className="border-t border-sidebar-border">
<Link href={signIn.href} onClick={close} className={cn(buttonVariants({ variant: "outline", size: "lg" }), "h-10")}>
{signIn.label}
</Link>
<Link href={cta.href} onClick={close} className={cn(buttonVariants({ size: "lg" }), "h-10")}>
{cta.label}
</Link>
</SheetFooter>
</SheetContent>
</Sheet>
<Link href={brand.href} aria-label={brand.label} className="focus-ring flex h-10 items-center rounded-md px-1">
<LogoMark name={brand.name} />
</Link>
<nav aria-label={label} className="ml-4 hidden h-full lg:block">
<ul className="flex h-full items-center">
{quickLinks.map((link) => (
<li key={link.href} className="h-full">
<Link
href={link.href}
aria-current={link.href === current ? "page" : undefined}
className="relative flex h-full items-center px-3 text-sm text-muted-foreground transition-colors hover:text-foreground focus-ring-inset aria-[current=page]:font-medium aria-[current=page]:text-foreground aria-[current=page]:after:absolute aria-[current=page]:after:inset-x-3 aria-[current=page]:after:bottom-0 aria-[current=page]:after:h-0.5 aria-[current=page]:after:bg-brand"
>
{link.label}
</Link>
</li>
))}
</ul>
</nav>
<div className="ml-auto flex items-center gap-2">
<Link href={signIn.href} className={cn(buttonVariants({ variant: "ghost" }), "max-lg:hidden")}>
{signIn.label}
</Link>
<Link href={cta.href} className={cn(buttonVariants(), "max-sm:hidden")}>
{cta.label}
</Link>
</div>
</div>
)
}/**
* Every word `navbar-06` sets: the three pages the bar keeps, the whole site
* in the drawer — grouped, each page naming an icon the layout draws and,
* where it has one, a word such as "New" — the two ways in, and the menu
* button's name. Add a page to a group and the drawer lists it.
*/
import type { LogoName } from "@/components/ui/logo-mark"
export type NavLink = { label: string; href: string }
export type DrawerIcon =
| "dashboards"
| "reports"
| "forecasts"
| "integrations"
| "customers"
| "changelog"
| "careers"
| "contact"
| "docs"
| "status"
| "security"
export type DrawerLink = NavLink & { icon: DrawerIcon; badge?: string }
export const BRAND: { name: LogoName; href: string; label: string } = {
name: "Northwind",
href: "/",
label: "Northwind, home",
}
/** The bar's own `nav`: the pages worth keeping in view from `lg`. */
export const NAV_LABEL = "Main"
export const QUICK_LINKS: readonly NavLink[] = [
{ label: "Pricing", href: "/pricing" },
{ label: "Customers", href: "/customers" },
{ label: "Docs", href: "/docs" },
]
/** The drawer's `nav`: every page, by section. */
export const DRAWER_LABEL = "All pages"
export const GROUPS: readonly { label: string; links: readonly DrawerLink[] }[] = [
{
label: "Product",
links: [
{ label: "Dashboards", href: "/product/dashboards", icon: "dashboards" },
{ label: "Reports", href: "/product/reports", icon: "reports" },
{ label: "Forecasts", href: "/product/forecasts", icon: "forecasts", badge: "New" },
{ label: "Integrations", href: "/integrations", icon: "integrations" },
],
},
{
label: "Company",
links: [
{ label: "Customers", href: "/customers", icon: "customers" },
{ label: "Changelog", href: "/changelog", icon: "changelog" },
{ label: "Careers", href: "/careers", icon: "careers" },
{ label: "Contact", href: "/contact", icon: "contact" },
],
},
{
label: "Help",
links: [
{ label: "Documentation", href: "/docs", icon: "docs" },
{ label: "Status", href: "/status", icon: "status" },
{ label: "Security", href: "/security", icon: "security" },
],
},
]
export const SIGN_IN: NavLink = { label: "Sign in", href: "/sign-in" }
export const CALL_TO_ACTION: NavLink = { label: "Start free trial", href: "/sign-up" }
/** The button's word, and the drawer's name and description. */
export const MENU = {
open: "Menu",
title: "Site menu",
description: "Every page of the site, by section, and the way in.",
}npx shadcn@latest add @vibra/navbar-06Floating navbar with a call to action
navbar-07A bar floating on the kit's raised layer: the pages, a way to sales, and one call to action that stays on the bar at every width.
import * as React from "react"
import { Navbar07Bar } from "./navbar-07.client"
import { BRAND, CALL_TO_ACTION, LINKS, MENU, NAV_LABEL, PHONE, SALES } from "./navbar-07.data"
export type Navbar07Props = {
/** The page the bar is on, when the route alone does not say. */
activeHref?: string
/** Opens the phone's menu on the first render — for a preview; false when installed. */
defaultOpen?: boolean
}
/**
* The floating bar with a call to action that never leaves it: the mark,
* the pages and the way to sales on the kit's floating layer, inset from the
* page's edges, and "Book a demo" on the bar at every width. The band is the
* page's `<header>`, transparent around the bar and letting clicks through
* it, holding the `<nav>` named "Main".
*
* A server component; the bar is one island because it reads the current
* route and owns the phone's sheet. Every word is in `./navbar-07.data`.
*/
export function Navbar07({ activeHref, defaultOpen = false }: Navbar07Props) {
return (
<header
data-section="navbar-07"
data-slot="section"
className="pointer-events-none sticky top-0 z-40 w-full px-3 pt-3 text-foreground md:px-6 md:pt-4"
>
<Navbar07Bar
brand={BRAND}
label={NAV_LABEL}
links={LINKS}
sales={SALES}
phone={PHONE}
cta={CALL_TO_ACTION}
menu={MENU}
activeHref={activeHref}
defaultOpen={defaultOpen}
/>
</header>
)
}
export default Navbar07"use client"
import * as React from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { MenuIcon, PhoneIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { currentHref } from "@/lib/nav-config"
import { Button, buttonVariants } from "@/components/ui/button"
import { LogoMark, type LogoName } from "@/components/ui/logo-mark"
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import type { NavLink } from "./navbar-07.data"
export type Navbar07BarProps = {
brand: { name: LogoName; href: string; label: string }
label: string
links: readonly NavLink[]
sales: NavLink
phone: { caption: string; label: string; href: string }
cta: NavLink
menu: { open: string; title: string; description: string }
activeHref?: string
defaultOpen?: boolean
}
/**
* A floating bar for a site that sells through a conversation: the pages,
* the way to reach sales, and one call to action that stays on the bar at
* every width — on a phone beside the menu button, sized down, never moved
* into the sheet. The bar is the kit's floating layer; the band around it is
* transparent and lets clicks through, so it floats over the page without
* taking the strip it sits in. The sheet adds the sales number as a link a
* phone can call.
*/
export function Navbar07Bar({ brand, label, links, sales, phone, cta, menu, activeHref, defaultOpen = false }: Navbar07BarProps) {
const pathname = usePathname()
const current = activeHref ?? currentHref(links.map((link) => link.href), pathname)
const [open, setOpen] = React.useState(defaultOpen)
const close = () => setOpen(false)
return (
<div className="pointer-events-auto mx-auto flex h-14 max-w-5xl items-center gap-2 elev-1 pr-2 pl-3 md:gap-6 md:pl-4">
<Link href={brand.href} aria-label={brand.label} className="focus-ring flex h-10 items-center rounded-md px-1">
<LogoMark name={brand.name} />
</Link>
<nav aria-label={label} className="hidden lg:block">
<ul className="flex items-center gap-0.5">
{links.map((link) => (
<li key={link.href}>
<Link
href={link.href}
aria-current={link.href === current ? "page" : undefined}
className="focus-ring flex h-8 items-center rounded-md px-3 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:bg-brand-muted aria-[current=page]:font-medium aria-[current=page]:text-foreground"
>
{link.label}
</Link>
</li>
))}
</ul>
</nav>
<div className="ml-auto flex items-center gap-1.5">
<Link href={sales.href} className={cn(buttonVariants({ variant: "ghost" }), "max-lg:hidden")}>
{sales.label}
</Link>
<Link href={cta.href} className={cn(buttonVariants(), "max-md:h-10 max-md:px-3.5")}>
{cta.label}
</Link>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger
render={
<Button variant="ghost" size="icon" aria-label={menu.open} className="max-md:size-10 lg:hidden">
<MenuIcon />
</Button>
}
/>
<SheetContent side="right" className="gap-0 p-0">
<SheetHeader className="h-16 flex-row items-center border-b border-border px-4 py-0">
<LogoMark name={brand.name} aria-hidden="true" />
<SheetTitle className="sr-only">{menu.title}</SheetTitle>
<SheetDescription className="sr-only">{menu.description}</SheetDescription>
</SheetHeader>
<nav aria-label={label} className="flex flex-col gap-0.5 p-2">
{links.map((link) => (
<Link
key={link.href}
href={link.href}
onClick={close}
aria-current={link.href === current ? "page" : undefined}
className="focus-ring flex h-11 items-center rounded-md px-3 text-base text-foreground transition-colors hover:bg-muted aria-[current=page]:bg-brand-muted aria-[current=page]:font-medium aria-[current=page]:text-brand"
>
{link.label}
</Link>
))}
</nav>
<SheetFooter className="border-t border-border">
<Link href={sales.href} onClick={close} className={cn(buttonVariants({ variant: "outline", size: "lg" }), "h-10")}>
{sales.label}
</Link>
<p className="flex flex-wrap items-center justify-center gap-x-1.5 pt-1 text-sm text-muted-foreground">
{phone.caption}
<a href={phone.href} className="focus-ring inline-flex items-center gap-1.5 rounded-md font-medium text-foreground tabular-nums">
<PhoneIcon aria-hidden="true" className="size-3.5" />
{phone.label}
</a>
</p>
</SheetFooter>
</SheetContent>
</Sheet>
</div>
</div>
)
}/**
* Every word `navbar-07` sets: a sales-led site's bar — the brand, the
* pages, the way to reach sales, the phone number the sheet offers, the one
* call to action that never leaves the bar, and the phone menu's name.
*/
import type { LogoName } from "@/components/ui/logo-mark"
export type NavLink = { label: string; href: string }
export const BRAND: { name: LogoName; href: string; label: string } = {
name: "Northwind",
href: "/",
label: "Northwind, home",
}
export const NAV_LABEL = "Main"
export const LINKS: readonly NavLink[] = [
{ label: "Product", href: "/product" },
{ label: "Customers", href: "/customers" },
{ label: "Pricing", href: "/pricing" },
{ label: "Docs", href: "/docs" },
]
export const SALES: NavLink = { label: "Talk to sales", href: "/contact/sales" }
/** A number in the 555-01xx range, which is reserved for fiction: replace it with your own. */
export const PHONE = { caption: "Or call sales", label: "+1 (555) 010-2400", href: "tel:+15550102400" }
export const CALL_TO_ACTION: NavLink = { label: "Book a demo", href: "/demo" }
export const MENU = {
open: "Open menu",
title: "Menu",
description: "Every page of the site, and the ways to reach sales.",
}npx shadcn@latest add @vibra/navbar-07Two-row navbar with product sections
navbar-08The site's bar over one product's sections: the mark, the pages and the ways in, then the product's name and its sections as tabs, with a second way in.
import * as React from "react"
import { Navbar08Bar } from "./navbar-08.client"
import {
BRAND,
CALL_TO_ACTION,
DEMO,
MENU,
PRODUCT,
SECTIONS,
SIGN_IN,
SITE_LABEL,
SITE_LINKS,
} from "./navbar-08.data"
export type Navbar08Props = {
/** The page the bar is on, when the route alone does not say: it lights a site page or a product section. */
activeHref?: string
/** Opens the phone's menu on the first render — for a preview; false when installed. */
defaultOpen?: boolean
}
/**
* The two-row bar of a company with more than one product: the site's own
* row — the mark, its pages, the two ways in — over one product's sections,
* led by the product's name, with a second way in at the end. The band is
* the page's `<header>`, holding two `<nav>`s: "Main" for the site and one
* named by the product.
*
* A server component; the bar is one island because it reads the current
* route and owns the phone's sheet. Every word is in `./navbar-08.data`.
*/
export function Navbar08({ activeHref, defaultOpen = false }: Navbar08Props) {
return (
<header
data-section="navbar-08"
data-slot="section"
className="sticky top-0 z-40 w-full border-b border-rule bg-background text-foreground"
>
<Navbar08Bar
brand={BRAND}
siteLabel={SITE_LABEL}
siteLinks={SITE_LINKS}
product={PRODUCT}
sections={SECTIONS}
demo={DEMO}
signIn={SIGN_IN}
cta={CALL_TO_ACTION}
menu={MENU}
activeHref={activeHref}
defaultOpen={defaultOpen}
/>
</header>
)
}
export default Navbar08"use client"
import * as React from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { ChartColumnIcon, MenuIcon } from "lucide-react"
import { cn } from "@/lib/utils"
import { currentHref } from "@/lib/nav-config"
import { Button, buttonVariants } from "@/components/ui/button"
import { LogoMark, type LogoName } from "@/components/ui/logo-mark"
import {
Sheet,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import type { NavLink } from "./navbar-08.data"
export type Navbar08BarProps = {
brand: { name: LogoName; href: string; label: string }
siteLabel: string
siteLinks: readonly NavLink[]
product: NavLink
sections: readonly NavLink[]
demo: NavLink
signIn: NavLink
cta: NavLink
menu: { open: string; title: string; description: string }
activeHref?: string
defaultOpen?: boolean
}
/** A link drawn as a tab: full height, its rule on the row's own bottom edge when it is the page. */
const TAB =
"relative flex h-full shrink-0 items-center px-3 text-sm whitespace-nowrap text-muted-foreground transition-colors hover:text-foreground focus-ring-inset aria-[current=page]:font-medium aria-[current=page]:text-foreground aria-[current=page]:after:absolute aria-[current=page]:after:inset-x-3 aria-[current=page]:after:bottom-0 aria-[current=page]:after:h-0.5 aria-[current=page]:after:bg-brand"
/**
* Two rows: the site's bar — the mark, its pages and the two ways in — and
* under it one product's own sections, led by the product's name, each a
* tab whose rule sits on the header's bottom edge when it is the page. The
* second row is the product's `nav`, named by the product, and it never
* folds away: on a phone it scrolls sideways inside itself while the site's
* pages move into the kit's Sheet.
*/
export function Navbar08Bar({
brand,
siteLabel,
siteLinks,
product,
sections,
demo,
signIn,
cta,
menu,
activeHref,
defaultOpen = false,
}: Navbar08BarProps) {
const pathname = usePathname()
const site = activeHref ?? currentHref(siteLinks.map((link) => link.href), pathname)
const section = activeHref ?? currentHref(sections.map((link) => link.href), pathname)
const [open, setOpen] = React.useState(defaultOpen)
const close = () => setOpen(false)
const sectionsRef = React.useRef<HTMLElement>(null)
// On a phone the sections scroll sideways: bring the current one into view,
// moving only the row, never the page.
React.useEffect(() => {
const row = sectionsRef.current
const current = row?.querySelector<HTMLElement>('[aria-current="page"]')
if (!row || !current) return
const start = current.offsetLeft
const end = start + current.offsetWidth
if (start < row.scrollLeft || end > row.scrollLeft + row.clientWidth) {
row.scrollLeft = start - (row.clientWidth - current.offsetWidth) / 2
}
}, [section])
return (
<>
<div className="mx-auto flex h-14 max-w-7xl items-center gap-6 px-4 md:px-6 lg:gap-10">
<Link href={brand.href} aria-label={brand.label} className="focus-ring -mx-1 flex h-10 items-center rounded-md px-1">
<LogoMark name={brand.name} />
</Link>
<nav aria-label={siteLabel} className="hidden h-full lg:block">
<ul className="flex h-full items-center">
{siteLinks.map((link) => (
<li key={link.href} className="h-full">
<Link href={link.href} aria-current={link.href === site ? "page" : undefined} className={TAB}>
{link.label}
</Link>
</li>
))}
</ul>
</nav>
<div className="ml-auto flex items-center gap-2">
<Link href={signIn.href} className={cn(buttonVariants({ variant: "ghost" }), "max-lg:hidden")}>
{signIn.label}
</Link>
<Link href={cta.href} className={cn(buttonVariants(), "max-sm:hidden")}>
{cta.label}
</Link>
<Sheet open={open} onOpenChange={setOpen}>
<SheetTrigger
render={
<Button variant="ghost" size="icon" aria-label={menu.open} className="max-md:size-10 lg:hidden">
<MenuIcon />
</Button>
}
/>
<SheetContent side="right" className="gap-0 p-0">
<SheetHeader className="h-14 flex-row items-center border-b border-border px-4 py-0">
<LogoMark name={brand.name} aria-hidden="true" />
<SheetTitle className="sr-only">{menu.title}</SheetTitle>
<SheetDescription className="sr-only">{menu.description}</SheetDescription>
</SheetHeader>
<nav aria-label={siteLabel} className="flex flex-col gap-0.5 p-2">
{siteLinks.map((link) => (
<Link
key={link.href}
href={link.href}
onClick={close}
aria-current={link.href === site ? "page" : undefined}
className="focus-ring flex h-11 items-center rounded-md px-3 text-base text-foreground transition-colors hover:bg-muted aria-[current=page]:bg-brand-muted aria-[current=page]:font-medium aria-[current=page]:text-brand"
>
{link.label}
</Link>
))}
</nav>
<SheetFooter className="border-t border-border">
<Link href={signIn.href} onClick={close} className={cn(buttonVariants({ variant: "outline", size: "lg" }), "h-10")}>
{signIn.label}
</Link>
<Link href={cta.href} onClick={close} className={cn(buttonVariants({ size: "lg" }), "h-10")}>
{cta.label}
</Link>
</SheetFooter>
</SheetContent>
</Sheet>
</div>
</div>
<div className="border-t border-border">
<div className="mx-auto flex h-12 max-w-7xl items-center gap-3 px-4 md:px-6">
<Link href={product.href} className="focus-ring flex h-8 shrink-0 items-center gap-2 rounded-md text-sm font-semibold text-foreground">
<ChartColumnIcon aria-hidden="true" className="size-4" />
{product.label}
</Link>
<span aria-hidden="true" className="h-5 w-px shrink-0 bg-border" />
<nav
ref={sectionsRef}
aria-label={product.label}
className="relative h-full min-w-0 flex-1 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
>
<ul className="flex h-full items-center">
{sections.map((link) => (
<li key={link.href} className="h-full">
<Link href={link.href} aria-current={link.href === section ? "page" : undefined} className={TAB}>
{link.label}
</Link>
</li>
))}
</ul>
</nav>
<Link href={demo.href} className={cn(buttonVariants({ variant: "outline", size: "sm" }), "shrink-0 max-lg:hidden")}>
{demo.label}
</Link>
</div>
</div>
</>
)
}/**
* Every word `navbar-08` sets: two rows — the site's own bar (the brand, its
* pages and the two ways in) over one product's sections (its name, where
* each section lives, and a second way in) — and the phone menu's name.
* Point `PRODUCT` and `SECTIONS` at another product and the second row
* follows it.
*/
import type { LogoName } from "@/components/ui/logo-mark"
export type NavLink = { label: string; href: string }
export const BRAND: { name: LogoName; href: string; label: string } = {
name: "Northwind",
href: "/",
label: "Northwind, home",
}
/** The first row: the whole site. */
export const SITE_LABEL = "Main"
export const SITE_LINKS: readonly NavLink[] = [
{ label: "Products", href: "/products" },
{ label: "Customers", href: "/customers" },
{ label: "Pricing", href: "/pricing" },
{ label: "Docs", href: "/docs" },
]
/** The second row: one product, named by its own `nav`. */
export const PRODUCT: NavLink = { label: "Analytics", href: "/analytics" }
export const SECTIONS: readonly NavLink[] = [
{ label: "Overview", href: "/analytics" },
{ label: "Dashboards", href: "/analytics/dashboards" },
{ label: "Reports", href: "/analytics/reports" },
{ label: "Forecasts", href: "/analytics/forecasts" },
{ label: "Integrations", href: "/analytics/integrations" },
{ label: "Security", href: "/analytics/security" },
]
export const DEMO: NavLink = { label: "Book a demo", href: "/demo" }
export const SIGN_IN: NavLink = { label: "Sign in", href: "/sign-in" }
export const CALL_TO_ACTION: NavLink = { label: "Start free trial", href: "/sign-up" }
export const MENU = {
open: "Open menu",
title: "Menu",
description: "The site's pages and the way in; the product's sections stay on the bar.",
}npx shadcn@latest add @vibra/navbar-08