Header 1: sticky with mobile menu
Sticky site header with brand, main links, sign-in and primary action, and an accessible mobile menu that closes on Escape or link tap.
// Ballmac UI: Header 1. https://ui.ballmac.com/blocks/header-1
"use client"
import * as React from "react"
import { Menu, X } from "lucide-react"
import { Button, buttonVariants } from "@/components/ballmac/button"
import { cn } from "@/lib/utils"
type NavLink = { label: string; href: string }
type Header1Props = React.ComponentProps<"header"> & {
/** Brand name or logo. */
brand?: React.ReactNode
/** Where the brand links to. */
brandHref?: string
/** Main navigation links. */
links?: NavLink[]
/** Quiet action on the right, e.g. sign in. */
secondaryAction?: NavLink
/** Main action on the right. */
primaryAction?: NavLink
/** Stick to the top with a blurred background. */
sticky?: boolean
}
function Header1({
brand = "Acme",
brandHref = "#",
links = [
{ label: "Product", href: "#" },
{ label: "Pricing", href: "#" },
{ label: "Customers", href: "#" },
{ label: "Docs", href: "#" },
],
secondaryAction = { label: "Sign in", href: "#" },
primaryAction = { label: "Get started", href: "#" },
sticky = true,
className,
...props
}: Header1Props) {
const [open, setOpen] = React.useState(false)
const menuId = React.useId()
React.useEffect(() => {
if (!open) return
const onKey = (e: KeyboardEvent) => e.key === "Escape" && setOpen(false)
document.addEventListener("keydown", onKey)
return () => document.removeEventListener("keydown", onKey)
}, [open])
return (
<header data-slot="header-1" className={cn("z-40 w-full border-b bg-background/80 backdrop-blur-xl", sticky && "sticky top-0", className)} {...props}>
<div className="mx-auto flex h-16 max-w-6xl items-center gap-8 px-4 sm:px-6">
<a href={brandHref} className="rounded-md text-lg font-semibold tracking-tight outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50">
{brand}
</a>
<nav aria-label="Main" className="hidden md:block">
<ul className="flex items-center gap-1">
{links.map((l) => (
<li key={l.label}>
<a href={l.href} className="rounded-md px-3 py-2 text-sm text-muted-foreground transition-colors outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50">
{l.label}
</a>
</li>
))}
</ul>
</nav>
<div className="ms-auto hidden items-center gap-2 md:flex">
<a className={buttonVariants({ variant: "ghost" })} href={secondaryAction.href}>{secondaryAction.label}</a>
<a className={buttonVariants({ shape: "pill" })} href={primaryAction.href}>{primaryAction.label}</a>
</div>
<Button
variant="ghost"
size="icon"
className="ms-auto md:hidden"
aria-expanded={open}
aria-controls={menuId}
aria-label={open ? "Close menu" : "Open menu"}
onClick={() => setOpen((o) => !o)}
>
{open ? <X /> : <Menu />}
</Button>
</div>
<nav id={menuId} aria-label="Mobile" hidden={!open} className="border-t px-4 pt-2 pb-4 md:hidden">
<ul className="flex flex-col">
{links.map((l) => (
<li key={l.label}>
<a href={l.href} onClick={() => setOpen(false)} className="block rounded-md px-2 py-2.5 text-base font-medium hover:bg-accent">
{l.label}
</a>
</li>
))}
</ul>
<div className="mt-3 grid gap-2">
<a className={buttonVariants({ variant: "outline" })} href={secondaryAction.href}>{secondaryAction.label}</a>
<a className={buttonVariants()} href={primaryAction.href}>{primaryAction.label}</a>
</div>
</nav>
</header>
)
}
export { Header1, type Header1Props }Installation
$ pnpm dlx shadcn@latest add @ballmac/header-1Install the dependencies.
$ pnpm add lucide-reactAdd the Ballmac items it builds on.
$ pnpm dlx shadcn@latest add @ballmac/buttonCopy the source into your project.
components/ballmac/blocks/header-1/header-1.tsx// Ballmac UI: Header 1. https://ui.ballmac.com/blocks/header-1 "use client" import * as React from "react" import { Menu, X } from "lucide-react" import { Button, buttonVariants } from "@/components/ballmac/button" import { cn } from "@/lib/utils" type NavLink = { label: string; href: string } type Header1Props = React.ComponentProps<"header"> & { /** Brand name or logo. */ brand?: React.ReactNode /** Where the brand links to. */ brandHref?: string /** Main navigation links. */ links?: NavLink[] /** Quiet action on the right, e.g. sign in. */ secondaryAction?: NavLink /** Main action on the right. */ primaryAction?: NavLink /** Stick to the top with a blurred background. */ sticky?: boolean } function Header1({ brand = "Acme", brandHref = "#", links = [ { label: "Product", href: "#" }, { label: "Pricing", href: "#" }, { label: "Customers", href: "#" }, { label: "Docs", href: "#" }, ], secondaryAction = { label: "Sign in", href: "#" }, primaryAction = { label: "Get started", href: "#" }, sticky = true, className, ...props }: Header1Props) { const [open, setOpen] = React.useState(false) const menuId = React.useId() React.useEffect(() => { if (!open) return const onKey = (e: KeyboardEvent) => e.key === "Escape" && setOpen(false) document.addEventListener("keydown", onKey) return () => document.removeEventListener("keydown", onKey) }, [open]) return ( <header data-slot="header-1" className={cn("z-40 w-full border-b bg-background/80 backdrop-blur-xl", sticky && "sticky top-0", className)} {...props}> <div className="mx-auto flex h-16 max-w-6xl items-center gap-8 px-4 sm:px-6"> <a href={brandHref} className="rounded-md text-lg font-semibold tracking-tight outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"> {brand} </a> <nav aria-label="Main" className="hidden md:block"> <ul className="flex items-center gap-1"> {links.map((l) => ( <li key={l.label}> <a href={l.href} className="rounded-md px-3 py-2 text-sm text-muted-foreground transition-colors outline-none hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50"> {l.label} </a> </li> ))} </ul> </nav> <div className="ms-auto hidden items-center gap-2 md:flex"> <a className={buttonVariants({ variant: "ghost" })} href={secondaryAction.href}>{secondaryAction.label}</a> <a className={buttonVariants({ shape: "pill" })} href={primaryAction.href}>{primaryAction.label}</a> </div> <Button variant="ghost" size="icon" className="ms-auto md:hidden" aria-expanded={open} aria-controls={menuId} aria-label={open ? "Close menu" : "Open menu"} onClick={() => setOpen((o) => !o)} > {open ? <X /> : <Menu />} </Button> </div> <nav id={menuId} aria-label="Mobile" hidden={!open} className="border-t px-4 pt-2 pb-4 md:hidden"> <ul className="flex flex-col"> {links.map((l) => ( <li key={l.label}> <a href={l.href} onClick={() => setOpen(false)} className="block rounded-md px-2 py-2.5 text-base font-medium hover:bg-accent"> {l.label} </a> </li> ))} </ul> <div className="mt-3 grid gap-2"> <a className={buttonVariants({ variant: "outline" })} href={secondaryAction.href}>{secondaryAction.label}</a> <a className={buttonVariants()} href={primaryAction.href}>{primaryAction.label}</a> </div> </nav> </header> ) } export { Header1, type Header1Props }Update the import paths to match your project setup.
Usage
import { Header1 } from "@/components/ballmac/blocks/header-1/header-1"
export default function Header1Demo() {
return <Header1 />
}Dependencies
- npm
- lucide-react
- Registry
- @ballmac/buttonshadcn/utils
Use with AI
The top navigation bar for a marketing site. Pass links and the two actions; it handles the mobile menu. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Header 1: sticky with mobile menu (@ballmac/header-1) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Marketing sites and landing pages
- Docs sites with a few top-level sections
Not for
- Apps with deep navigation (use a sidebar)
Registry JSON: https://ui.ballmac.com/r/header-1.json
Free to use in personal and commercial projects.
More header blocks
Header 2: mega menu with mobile sheet
Sticky header with brand, a mega menu of grouped links and a featured card, sign-in and primary actions. On phones the same links open in a slide-in sheet with accordions.
Header Pro 1: mega menu navigation
A top bar with menus that open full-width panels of grouped links and a feature card, with hover, click and arrow-key support, and a disclosure menu on phones.
Header Pro 2: floating pill with announcement
A dismissible announcement bar above a floating pill header that shrinks as you scroll, with a moving highlight behind the hovered link and a phone menu.