Skip to Content

Keyboard-accessible skip link that jumps past chrome to the main landmark.

Loading...
import { SkipToContent } from 'ui-patterns/SkipToContent'
 
export function SkipToContentDemo() {
  return (
    <div className="relative w-full overflow-hidden rounded-md border bg-studio">
      <div className="flex items-center border-b px-4 py-3 text-sm text-foreground-muted">
        Demo header / navigation
      </div>
      {/* Preview shows the focused appearance; production hides until Tab */}
      <SkipToContent href="#skip-demo-main" className="relative left-3 top-2 translate-y-0" />
      <main id="skip-demo-main" tabIndex={-1} className="outline-hidden p-6 pt-2">
        <p className="text-sm text-foreground-light">
          Main content landmark. In a real app, Tab once to reveal the skip link, then Enter to jump
          here.
        </p>
      </main>
    </div>
  )
}

Keyboard-accessible skip link composed from Button for apps with persistent header and sidebar chrome. Hidden until focused via Tab, then slides into view so users can jump past navigation to the main landmark.

The preview above shows the focused appearance. In product apps, the link stays off-screen until keyboard focus.

See Accessibility for more information on when to use skip links, and how they coexist alongside other navigation aids.

Usage

import { SkipToContent } from 'ui-patterns/SkipToContent'
<SkipToContent href="#main" />
 
<main id="main" tabIndex={-1} className="scroll-mt-(--header-height) outline-hidden">
  {children}
</main>

Place the skip link at the root of the app chrome so Tab reaches it before navigation. The target landmark must be content only. Do not wrap a sidebar inside the same <main>, otherwise a Tab after skip will land in the sidebar navigation.

Props

href

Hash href to the main content landmark, e.g. #main.

children

Link label. Defaults to Skip to content.

className

Optional classes merged onto the positioning wrapper (useful for demos or layout overrides).

Target landmark

Callers own the landmark the skip link points at:

  • Matching id
  • tabIndex={-1} so Enter moves focus onto the landmark
  • scroll-mt when a sticky header is present
  • outline-hidden so the landmark has no visible focus ring. The subsequent Tab will land on the first interactive child