Skip to content

Documentation page header

Breadcrumb, title, stability badge, summary and a metadata rule carrying version and reading time.

Headersstarterdocsheadermetadatabreadcrumb

Live preview

full widthLive preview — open it in a new tab for the full-height version.
Open the preview in a new tab

Source

This exact file renders the preview above.

import Link from 'next/link'
import { Clock, GitCommit } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import { Breadcrumb } from '@/components/ui/breadcrumb'
import { Container } from '@/components/ui/layout'

/**
 * Documentation page header
 *
 * Breadcrumb, title, summary, and a metadata rule carrying version, reading
 * time and last update. The metadata line is what turns a documentation page
 * from "a page" into "a page you can trust the age of".
 */
export default function DocumentationHeader() {
  return (
    <header className="border-b border-line bg-canvas pt-8 pb-8">
      <Container>
        <Breadcrumb
          items={[
            { label: 'Docs', href: '/docs/getting-started' },
            { label: 'Foundations', href: '/docs/design-tokens' },
            { label: 'Accessibility' },
          ]}
        />

        <div className="mt-5 flex flex-wrap items-center gap-3">
          <h1 className="display-type text-3xl leading-tight font-semibold text-ink-strong sm:text-4xl">
            Accessibility
          </h1>
          <Badge tone="success" dot>
            Stable
          </Badge>
        </div>

        <p className="mt-4 max-w-[46rem] text-md text-ink-muted">
          The keyboard, focus and announcement contracts every Foundry primitive guarantees — and
          how to verify them in your own build.
        </p>

        <div className="mt-6 flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-line-subtle pt-4 text-xs text-ink-subtle">
          <span className="flex items-center gap-1.5">
            <GitCommit className="size-3.5" aria-hidden="true" />
            Updated in <span className="font-mono text-ink-muted">v1.0.0</span>
          </span>
          <span className="flex items-center gap-1.5">
            <Clock className="size-3.5" aria-hidden="true" />
            11 min read
          </span>
          <Link href="/docs/contributing" className="text-accent underline underline-offset-4">
            Suggest an edit
          </Link>
        </div>
      </Container>
    </header>
  )
}

components/blocks/headers/documentation.tsx

import Link from 'next/link'
import { Clock, GitCommit } from 'lucide-react'
import { Badge } from '@/components/ui/badge'
import { Breadcrumb } from '@/components/ui/breadcrumb'
import { Container } from '@/components/ui/layout'

/**
 * Documentation page header
 *
 * Breadcrumb, title, summary, and a metadata rule carrying version, reading
 * time and last update. The metadata line is what turns a documentation page
 * from "a page" into "a page you can trust the age of".
 */
export default function DocumentationHeader() {
  return (
    <header className="border-b border-line bg-canvas pt-8 pb-8">
      <Container>
        <Breadcrumb
          items={[
            { label: 'Docs', href: '/docs/getting-started' },
            { label: 'Foundations', href: '/docs/design-tokens' },
            { label: 'Accessibility' },
          ]}
        />

        <div className="mt-5 flex flex-wrap items-center gap-3">
          <h1 className="display-type text-3xl leading-tight font-semibold text-ink-strong sm:text-4xl">
            Accessibility
          </h1>
          <Badge tone="success" dot>
            Stable
          </Badge>
        </div>

        <p className="mt-4 max-w-[46rem] text-md text-ink-muted">
          The keyboard, focus and announcement contracts every Foundry primitive guarantees — and
          how to verify them in your own build.
        </p>

        <div className="mt-6 flex flex-wrap items-center gap-x-6 gap-y-2 border-t border-line-subtle pt-4 text-xs text-ink-subtle">
          <span className="flex items-center gap-1.5">
            <GitCommit className="size-3.5" aria-hidden="true" />
            Updated in <span className="font-mono text-ink-muted">v1.0.0</span>
          </span>
          <span className="flex items-center gap-1.5">
            <Clock className="size-3.5" aria-hidden="true" />
            11 min read
          </span>
          <Link href="/docs/contributing" className="text-accent underline underline-offset-4">
            Suggest an edit
          </Link>
        </div>
      </Container>
    </header>
  )
}

components/ui/breadcrumb.tsx

import Link from 'next/link'
import { ChevronRight } from 'lucide-react'
import { cn } from '@/lib/cn'

/**
 * Breadcrumb
 *
 * A `<nav>` wrapping an ordered list. The final crumb is not a link and
 * carries `aria-current="page"`, and the separators are decorative text inside
 * `aria-hidden` spans so a screen reader reads "Components, Button" rather
 * than "Components chevron Button".
 *
 * On narrow screens the trail scrolls horizontally instead of wrapping into an
 * unreadable stack.
 */
export interface Crumb {
  label: string
  href?: string
}

export interface BreadcrumbProps {
  items: Crumb[]
  className?: string
  /** Collapses the middle of very deep trails behind an ellipsis. */
  maxItems?: number
}

export function Breadcrumb({ items, className, maxItems }: BreadcrumbProps) {
  const shouldCollapse = maxItems !== undefined && items.length > maxItems
  const visible: Array<Crumb | 'ellipsis'> = shouldCollapse
    ? [items[0] as Crumb, 'ellipsis', ...items.slice(items.length - (maxItems - 1))]
    : items

  return (
    <nav aria-label="Breadcrumb" className={cn('min-w-0', className)}>
      <ol className="hide-scrollbar flex items-center gap-1 overflow-x-auto text-xs whitespace-nowrap">
        {visible.map((item, index) => {
          const isLast = index === visible.length - 1
          return (
            <li
              key={typeof item === 'string' ? `ellipsis-${index}` : `${item.label}-${index}`}
              className="flex items-center gap-1"
            >
              {index > 0 ? (
                <ChevronRight className="size-3 shrink-0 text-ink-subtle" aria-hidden="true" />
              ) : null}
              {item === 'ellipsis' ? (
                <span className="px-1 text-ink-subtle" aria-hidden="true">
                  …
                </span>
              ) : isLast || !item.href ? (
                <span
                  className={cn('font-medium', isLast ? 'text-ink' : 'text-ink-muted')}
                  aria-current={isLast ? 'page' : undefined}
                >
                  {item.label}
                </span>
              ) : (
                <Link
                  href={item.href}
                  className="rounded-xs text-ink-muted transition-colors duration-150 hover:text-ink"
                >
                  {item.label}
                </Link>
              )}
            </li>
          )
        })}
      </ol>
    </nav>
  )
}

components/ui/badge.tsx

import type { HTMLAttributes, ReactNode } from 'react'
import { variants } from '@/lib/variants'

/**
 * Badge
 *
 * A compact, non-interactive label. Tones map to the status token trios, and
 * because a badge is often the only signal in a dense table, the `dot` option
 * exists to add a second, colour-independent cue alongside the text.
 */
const badgeVariants = variants('inline-flex items-center gap-1.5 whitespace-nowrap font-medium', {
  variants: {
    tone: {
      neutral: 'bg-surface-sunken text-ink-muted border-line',
      accent: 'bg-accent-soft text-accent-soft-ink border-accent-line',
      success: 'bg-success-soft text-success border-success-line',
      warning: 'bg-warning-soft text-warning border-warning-line',
      danger: 'bg-danger-soft text-danger border-danger-line',
      info: 'bg-info-soft text-info border-info-line',
      inverse: 'bg-surface-inverse text-ink-inverse border-transparent',
    },
    appearance: {
      soft: 'border',
      outline: 'border bg-transparent',
      solid: 'border border-transparent',
    },
    size: {
      sm: 'h-4.5 rounded-sm px-1.5 text-2xs',
      md: 'h-5.5 rounded-sm px-2 text-xs',
    },
  },
  defaultVariants: { tone: 'neutral', appearance: 'soft', size: 'md' },
  compound: [
    { appearance: 'solid', tone: 'accent', class: 'bg-accent text-accent-ink' },
    { appearance: 'solid', tone: 'success', class: 'bg-success text-white' },
    { appearance: 'solid', tone: 'warning', class: 'bg-warning text-white' },
    { appearance: 'solid', tone: 'danger', class: 'bg-danger text-white' },
    { appearance: 'solid', tone: 'info', class: 'bg-info text-white' },
    { appearance: 'solid', tone: 'neutral', class: 'bg-ink text-ink-inverse' },
    { appearance: 'outline', tone: 'neutral', class: 'text-ink-muted' },
  ],
})

export type BadgeTone = 'neutral' | 'accent' | 'success' | 'warning' | 'danger' | 'info' | 'inverse'

export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
  tone?: BadgeTone
  appearance?: 'soft' | 'outline' | 'solid'
  size?: 'sm' | 'md'
  /** Adds a leading dot so the badge does not rely on hue alone. */
  dot?: boolean
  icon?: ReactNode
}

export function Badge({
  tone = 'neutral',
  appearance = 'soft',
  size = 'md',
  dot = false,
  icon,
  className,
  children,
  ...props
}: BadgeProps) {
  return (
    <span className={badgeVariants({ tone, appearance, size, className })} {...props}>
      {dot ? (
        <span className="size-1.5 shrink-0 rounded-full bg-current" aria-hidden="true" />
      ) : null}
      {icon}
      {children}
    </span>
  )
}

Demo source — adapt to your project. Foundry is not published as a package.

Usage

The metadata line is what turns a documentation page from "a page" into "a page whose age you can judge". Without it, readers cannot tell whether they are looking at current guidance.

  • Stability badges should reflect a real policy; a badge that always says Stable says nothing.
  • Keep the edit link visible — it is the cheapest documentation improvement mechanism there is.

Variants and states

Every entry below is a genuine difference in behaviour or layout, and every one of them is visible in the preview above.

  • Stability badge
  • Version and reading time
  • Suggest-an-edit link

Accessibility

Breadcrumb
A labelled `<nav>` with `aria-current` on the final crumb.
Metadata
Icons are decorative; every value is also present as text.

Foundry implements published ARIA patterns and is tested against them. No WCAG certification is claimed — see the accessibility documentation for what is and is not covered.