Skip to content
    Style: Wireframe

    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.

    nav-links.classes.ts
    /** 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;
    };
    nav-links.tsx
    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>
      );
    }