Skip Nav
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-navLayout
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>Multiple skip links
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.
| Data Attribute | Value |
|---|---|
[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
| Key | Description |
|---|---|
| Tab | Reveals the skip link when it is the first focusable element. |
| Enter | Moves focus to the target element. |