Skip to content
Dice UI

Skip Nav

API

A link that lets keyboard and screen reader users skip past repeated navigation to the main content.

Installation

pnpm dlx shadcn@latest add @diceui/skip-nav

Layout

Render the skip link as the first focusable element on the page, and give your main content the MAIN_CONTENT_ID it targets by default.

import { MAIN_CONTENT_ID, SkipNav } from "@/components/skip-nav";
 
<body>
  <SkipNav />
  <header>{/* Navigation */}</header>
  <main id={MAIN_CONTENT_ID}>{/* Page content */}</main>
</body>

If your page has a sticky header, give the target a scroll-margin-top so it isn't scrolled underneath it.

<main id={MAIN_CONTENT_ID} className="scroll-mt-16">
  {/* Page content */}
</main>

Examples

Custom target

Point href at any element on the page and change the label with children.

<SkipNav href="#search">Skip to search</SkipNav>

Render more than one when the page has several landmarks worth jumping to.

<SkipNav />
<SkipNav href="#footer">Skip to footer</SkipNav>

API Reference

SkipNav

A link that is visually hidden until it receives keyboard focus.

Prop

Type

Data AttributeValue
[data-slot]skip-nav

MAIN_CONTENT_ID

The id that SkipNav targets by default, "main-content". Use it on your main content so the link and its target stay in sync.

<main id={MAIN_CONTENT_ID}>{/* Page content */}</main>

It only applies to the default href. When you pass a custom href, SkipNav ignores MAIN_CONTENT_ID and targets the id in that href, so give that element the matching id instead.

<SkipNav href="#search">Skip to search</SkipNav>
<input id="search" aria-label="Search" />

Accessibility

Skip links satisfy WCAG 2.4.1 Bypass Blocks. The link stays in the accessibility tree and becomes visible when focused, so sighted keyboard users can see where it goes.

On activation, focus moves to the target element, not just the scroll position. If the target isn't focusable, like <main>, it gets tabindex="-1" so it can receive focus, and the next Tab continues from there. Its focus outline is hidden while it has focus, and both changes are undone when focus leaves it. The URL hash isn't changed, so the skip doesn't add a history entry.

While focused, the link uses z-index: 100 to stack above shadcn's z-50 layers like sticky headers and popovers, so it's never covered (WCAG 2.4.11 Focus Not Obscured). If your app has its own layers above 100, raise it with className="focus-visible:z-[…]".

Keyboard Interactions

KeyDescription
TabReveals the skip link when it is the first focusable element.
EnterMoves focus to the target element.

On this page