Blocks · Navigation
Navigation links
A row of text links with the current page marked (aria-current, underlined); in a narrow block it scrolls sideways instead of wrapping.
38% of the 2,073 sites in The UI Index have a navigation (790 sites).
Both previews render the same markup; the HTML tab below is that markup without a framework.
Install
- React
- npx shadcn@
latest add https:// www.themes.dev/ ui/ r/ nav-links.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ ui/ r/ astro/ nav-links.json
Or copy the files below into your project. They need clsx, tailwind-merge and the components they import, and the themes.dev UI theme. The HTML needs only the compiled CSS.
/** Navigation links: a row of text links with the current page marked (aria-current, underlined and heavier).
* Wide enough, the row sits on one line; in a narrow block it scrolls sideways with the next link's edge showing
* instead of wrapping. The block's own width decides (container query), not the window's. */
export const navLinks = {
root: '@container/nav bg-background text-foreground',
inner: 'mx-auto max-w-6xl px-6',
// py-2 / -my-2 keep the focus ring of a link from being clipped by the scroll row.
list: '-mx-3 -my-2 flex list-none scroll-px-8 overflow-x-auto py-2 ps-3 pe-8 [scrollbar-width:none] [mask-image:linear-gradient(to_right,#000_calc(100%-2rem),transparent)] [&::-webkit-scrollbar]:hidden @3xl/nav:scroll-px-0 @3xl/nav:overflow-visible @3xl/nav:[mask-image:none]',
link: "relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100",
};
export type NavLinksLink = { label: string; href: string };
export type NavLinksContent = {
links: NavLinksLink[];
/** The link whose href equals this is the current page. */
currentPath?: string;
/** The navigation's name for screen readers; a page with two navs needs two names. Default "Primary". */
label?: string;
};import { cn } from '@/lib/utils';
import { navLinks as c, type NavLinksContent } from './nav-links.classes';
export type NavLinksProps = NavLinksContent & { className?: string };
/** A row of text links with the current page marked; a scroll row in a narrow block. */
export function NavLinks({ links, currentPath, label = 'Primary', className }: NavLinksProps) {
return (
<div className={cn(c.root, className)}>
<div className={c.inner}>
<nav aria-label={label}>
<ul role="list" className={c.list}>
{links.map((l) => (
<li key={l.href}>
<a href={l.href} aria-current={l.href === currentPath ? 'page' : undefined} className={c.link}>{l.label}</a>
</li>
))}
</ul>
</nav>
</div>
</div>
);
}---
import { cn } from '@/lib/utils';
import { navLinks as c, type NavLinksContent } from './nav-links.classes';
type Props = NavLinksContent & { class?: string };
const { links, currentPath, label = 'Primary', class: className } = Astro.props;
---
<div class={cn(c.root, className)}>
<div class={c.inner}>
<nav aria-label={label}>
<ul role="list" class={c.list}>
{links.map((l) => (
<li>
<a href={l.href} aria-current={l.href === currentPath ? 'page' : undefined} class={c.link}>{l.label}</a>
</li>
))}
</ul>
</nav>
</div>
</div>/** Navigation links: a row of text links with the current page marked (aria-current, underlined and heavier).
* Wide enough, the row sits on one line; in a narrow block it scrolls sideways with the next link's edge showing
* instead of wrapping. The block's own width decides (container query), not the window's. */
export const navLinks = {
root: '@container/nav bg-background text-foreground',
inner: 'mx-auto max-w-6xl px-6',
// py-2 / -my-2 keep the focus ring of a link from being clipped by the scroll row.
list: '-mx-3 -my-2 flex list-none scroll-px-8 overflow-x-auto py-2 ps-3 pe-8 [scrollbar-width:none] [mask-image:linear-gradient(to_right,#000_calc(100%-2rem),transparent)] [&::-webkit-scrollbar]:hidden @3xl/nav:scroll-px-0 @3xl/nav:overflow-visible @3xl/nav:[mask-image:none]',
link: "relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100",
};
export type NavLinksLink = { label: string; href: string };
export type NavLinksContent = {
links: NavLinksLink[];
/** The link whose href equals this is the current page. */
currentPath?: string;
/** The navigation's name for screen readers; a page with two navs needs two names. Default "Primary". */
label?: string;
};<div class="grid gap-6 pt-4">
<div class="@container/nav bg-background text-foreground">
<div class="mx-auto max-w-6xl px-6">
<nav aria-label="Project">
<ul role="list" class="-mx-3 -my-2 flex list-none scroll-px-8 overflow-x-auto py-2 ps-3 pe-8 [scrollbar-width:none] [mask-image:linear-gradient(to_right,#000_calc(100%-2rem),transparent)] [&::-webkit-scrollbar]:hidden @3xl/nav:scroll-px-0 @3xl/nav:overflow-visible @3xl/nav:[mask-image:none]">
<li>
<a href="#overview" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Overview
</a>
</li>
<li>
<a href="#deploys" aria-current="page" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Deploys
</a>
</li>
<li>
<a href="#domains" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Domains
</a>
</li>
<li>
<a href="#analytics" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Analytics
</a>
</li>
<li>
<a href="#usage" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Usage
</a>
</li>
<li>
<a href="#settings" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Settings
</a>
</li>
</ul>
</nav>
</div>
</div>
<div class="max-w-xs rounded-lg border">
<div class="@container/nav bg-background text-foreground">
<div class="mx-auto max-w-6xl px-6">
<nav aria-label="Project, narrow">
<ul role="list" class="-mx-3 -my-2 flex list-none scroll-px-8 overflow-x-auto py-2 ps-3 pe-8 [scrollbar-width:none] [mask-image:linear-gradient(to_right,#000_calc(100%-2rem),transparent)] [&::-webkit-scrollbar]:hidden @3xl/nav:scroll-px-0 @3xl/nav:overflow-visible @3xl/nav:[mask-image:none]">
<li>
<a href="#overview" aria-current="page" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Overview
</a>
</li>
<li>
<a href="#deploys" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Deploys
</a>
</li>
<li>
<a href="#domains" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Domains
</a>
</li>
<li>
<a href="#analytics" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Analytics
</a>
</li>
<li>
<a href="#usage" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Usage
</a>
</li>
<li>
<a href="#settings" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Settings
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="inverted rounded-lg bg-background py-6">
<div class="@container/nav bg-background text-foreground">
<div class="mx-auto max-w-6xl px-6">
<nav aria-label="Project, inverted">
<ul role="list" class="-mx-3 -my-2 flex list-none scroll-px-8 overflow-x-auto py-2 ps-3 pe-8 [scrollbar-width:none] [mask-image:linear-gradient(to_right,#000_calc(100%-2rem),transparent)] [&::-webkit-scrollbar]:hidden @3xl/nav:scroll-px-0 @3xl/nav:overflow-visible @3xl/nav:[mask-image:none]">
<li>
<a href="#overview" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Overview
</a>
</li>
<li>
<a href="#deploys" aria-current="page" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Deploys
</a>
</li>
<li>
<a href="#domains" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Domains
</a>
</li>
<li>
<a href="#analytics" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Analytics
</a>
</li>
<li>
<a href="#usage" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Usage
</a>
</li>
<li>
<a href="#settings" class="relative inline-flex min-h-11 items-center rounded-md px-3 text-base whitespace-nowrap text-muted-foreground transition-colors after:absolute after:inset-x-3 after:bottom-1.5 after:h-0.5 after:rounded-full after:bg-foreground after:opacity-0 hover:bg-accent hover:text-foreground active:bg-pressed aria-[current=page]:text-foreground aria-[current=page]:after:opacity-100">
Settings
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
</div>