Site header
StableThe home pages’ sticky header in three styles: a full bar, a floating glass pill and a minimal mono row.
SiteHeader is clear at the top of the page and frosts with a hairline once the window scrolls. Below 768 pixels its links fold into a menu button. bar has the logo, links and actions; floating is a glass pill inset from the edges; minimal sets the links in mono. HeaderLink is the quiet text link for “Sign in”. The examples are whole pages, so open one to scroll it.
Bar
import { HeaderLink, SiteFooter, SiteHeader, SkipLink } from '@rchursin/home/Chrome'import { ButtonLink, Container } from '@rchursin/home/ui'
/** * The bar header on a whole page. It sticks to the top, is clear until the window scrolls, then * frosts with a hairline. Its four default links point at sections of the page, so they work. * Below 768px the links fold into a menu button. */const sections = [ { id: 'product', title: 'Product', body: 'Reads the sessions your coding agents already write and shows which skills they use, skip or repeat.' }, { id: 'how', title: 'How it works', body: 'Run one command in a repository. It reads the agent folders locally and prints a short report.' }, { id: 'pricing', title: 'Pricing', body: 'The local scan is free. Team mode keeps the history and adds everyone’s machines.' }, { id: 'docs', title: 'Docs', body: 'Set-up, the report explained line by line, and how to share it with your team.' },]
export default function HeaderBarPage() { return ( <div id="top" className="bg-surface text-fg"> <SkipLink /> <SiteHeader secondary={<HeaderLink href="#start">Sign in</HeaderLink>} />Usage
import { HeaderLink, SiteHeader } from '@rchursin/home/Chrome'<SiteHeader secondary={<HeaderLink href="#signin">Sign in</HeaderLink>} /><SiteHeader variant="minimal" links={links} cta="Get the CLI" ctaHref="#install" />Examples
Floating
A glass pill over the page, with custom links.
import { SiteFooter, SiteHeader, SkipLink } from '@rchursin/home/Chrome'import { Container } from '@rchursin/home/ui'
/** * The floating header: a glass pill that hovers over the page, inset from the edges. It frosts * once the window scrolls. Custom links point at this page's own sections. */const links = [ { label: 'Work', href: '#work' }, { label: 'Notes', href: '#notes' }, { label: 'Contact', href: '#contact' },]
export default function HeaderFloatingPage() { return ( <div id="top" className="bg-surface text-fg"> <SkipLink /> <SiteHeader variant="floating" links={links} cta="Get in touch" ctaHref="#contact" /> <main id="main" tabIndex={-1} className="outline-none"> <Container narrow className="flex flex-col gap-6 py-28 text-center sm:py-36"> <p className="font-mono text-[11px] tracking-[0.14em] text-fg-3 uppercase">Sample page</p> <h1 className="text-4xl font-semibold tracking-[-0.035em] text-balance sm:text-6xl">A header that floats over the work.</h1> <p className="mx-auto max-w-xl text-lg/8 text-pretty text-fg-2">Scroll down: the pill stays put and frosts as the page slides under it.</p> </Container> {links.map((l) => ( <section key={l.href} id={l.href.slice(1)} aria-labelledby={`${l.href.slice(1)}-title`} className="scroll-mt-24 border-t border-line"> <Container narrow className="flex min-h-[60vh] flex-col gap-3 py-20"> <h2 id={`${l.href.slice(1)}-title`} className="text-2xl font-semibold tracking-[-0.025em]"> {l.label} </h2> <p className="text-[15px]/7 text-pretty text-fg-2">This section is here so the link above has somewhere to go.</p> </Container> </section> ))} </main> <SiteFooter variant="simple" /> </div> )}Minimal
Mono links for a developer-first page.
import { SiteFooter, SiteHeader, SkipLink } from '@rchursin/home/Chrome'import { CommandLine, Container } from '@rchursin/home/ui'
/** * The minimal header: the logo and a few mono links, for a developer-first page. Here with its * own links and call to action. */const links = [ { label: 'Docs', href: '#docs' }, { label: 'Changelog', href: '#changelog' }, { label: 'Install', href: '#install' },]
export default function HeaderMinimalPage() { return ( <div id="top" className="bg-surface text-fg"> <SkipLink /> <SiteHeader variant="minimal" links={links} cta="Install" ctaHref="#install" /> <main id="main" tabIndex={-1} className="outline-none"> <Container className="flex flex-col items-start gap-6 py-24 sm:py-32"> <p className="font-mono text-[11px] tracking-[0.14em] text-fg-3 uppercase">Sample page</p> <h1 className="max-w-2xl text-4xl font-medium tracking-[-0.035em] text-balance sm:text-6xl">Start a project in one command.</h1> <CommandLine command="npm create vite@latest my-app" className="w-full max-w-md" /> </Container> {links.map((l) => ( <section key={l.href} id={l.href.slice(1)} aria-labelledby={`${l.href.slice(1)}-title`} className="scroll-mt-16 border-t border-line"> <Container className="flex min-h-[60vh] flex-col gap-3 py-20"> <h2 id={`${l.href.slice(1)}-title`} className="font-mono text-xl tracking-[-0.01em]"> {l.label} </h2> <p className="max-w-xl text-[15px]/7 text-pretty text-fg-2">This section is here so the link above has somewhere to go.</p> </Container> </section> ))} </main> <SiteFooter variant="simple" /> </div> )}HeaderLink
The quiet link next to the call to action. Its underline draws in on hover.
import { HeaderLink } from '@rchursin/home/Chrome'import { ButtonLink } from '@rchursin/home/ui'
/** * HeaderLink is the quiet text link that sits left of the call to action ("Sign in"). Its * underline draws in on hover. Pass it to SiteHeader as `secondary`. */export default function HeaderLinkExample() { return ( <div className="flex items-center gap-5"> <HeaderLink href="#sign-in">Sign in</HeaderLink> <ButtonLink href="#start">Start free</ButtonLink> </div> )}API
Site header props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'bar' | 'floating' | 'minimal' | 'bar' | bar: full-width bar. floating: a glass pill inset from the edges. minimal: like bar, with mono links. |
links | { label: string; href: string }[] | Product, How it works, Pricing, Docs | The navigation links, as plain anchors (usually #sections of a one-page site). |
cta | string | 'Start free' | The call to action’s label, drawn as a primary ButtonLink. |
ctaHref | string | '#start' | Where the call to action goes. |
secondary | ReactNode | – | An extra action left of the call to action on wide screens, e.g. <HeaderLink>Sign in</HeaderLink>. Not shown in the phone menu. |
navLabel | string | 'Main' | The <nav>’s accessible name. Change it if the page already has a navigation named “Main”. |
HeaderLink props
| Prop | Type | Default | Description |
|---|---|---|---|
hrefrequired | string | – | Where it goes. |
childrenrequired | ReactNode | – | The label. |
Accessibility
Keyboard
| Key | What it does |
|---|---|
| Tab | Logo, links, then the actions. On a phone: logo, then the menu button. |
| EnterSpace | On the menu button: opens or closes the phone menu. Choosing a link closes it. |
| Esc | Closes the open phone menu and puts focus back on the menu button. |
- A <header> with a <nav> named by navLabel (“Main” by default). At the top level of a page the header is the banner landmark; inside <main> or an <article> it is not a landmark, which is why the examples run as whole pages.
- The <nav> holds the links at every width: below 768 pixels the menu button and its panel sit inside it, so the phone menu is still a navigation landmark.
- Each navigation landmark on a page needs its own name: if the page already has a nav named “Main” (a site shell, say), pass a different navLabel.
- The menu button says “Open menu” or “Close menu” and sets aria-expanded and aria-controls. Opening it leaves focus on the button, so the next Tab enters the panel; Esc closes it from anywhere inside.
- The logo links to #top: give the page’s outer element id="top".
- Frosting follows window.scrollY (useScrolled). In a box that scrolls on its own, the header stays clear.
- The secondary action is only on wide screens; put anything important in the links or the call to action too.
Guidelines
Do
- Put SkipLink before the header and give <main> id="main".
- Keep to four or five links; the call to action is the one primary button.
- Point the links at real sections and give those sections scroll-margin so the header doesn’t cover their headings.
Don’t
- Don’t put the header inside <main>.
- Don’t use two SiteHeaders on one page.
- Don’t use it for an app with routes; its links are plain anchors.
In use
Related
- Skip linkA “Skip to content” link that stays hidden until the first Tab, then jumps past the header to #main.
- Site footerThe home pages’ footer: three link columns with a cropped wordmark, or one simple row.
- Glass headerA header that is clear at the top of the page and frosts over the content once the page scrolls.
- Button linkA link cut from the same lit metal as Button, for calls to action that go somewhere.
Source
The component’s own files, exactly as they run on this site.
/** * Site header and footer shared by the home pages, in three header styles: * - bar Tailwind Plus header: logo left, links, actions right * - floating Aceternity floating pill over the page * - minimal logo + a few mono text links, for the developer-first page * * Details: the header is clear at the top of the page and frosts (glass + hairline) once you * scroll; the logo's underscore blinks like a cursor on hover; links draw their underline in; * the mobile menu eases down from the bar. */import { useId, useRef, useState, type ReactNode } from "react"import { Logo } from "../brand/Logo"import { cx } from "../components/cx"import { MenuIcon } from "../components/icons"import { useScrolled } from "../components/motion"import { useCurrentYear } from "../product/lib/hydration"import { nav } from "./content"import { ButtonLink, Container } from "./ui"
export function SkipLink() {