Navigation Menu
Główna nawigacja witryny z rozbudowanymi, animowanymi panelami — dla stron marketingowych i dokumentacji, w których każda sekcja potrzebuje czegoś więcej niż jednego linku.
import Link from "next/link"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"
const components = [
{
title: "Button",
href: "/pl/docs/components/button",
description: "Wywołuje akcję. Najważniejszy element interaktywny.",
},
{
title: "Dialog",
href: "/pl/docs/components/dialog",
description: "Skupione zadanie nad stroną, z jasną drogą wyjścia.",
},
{
title: "Tabs",
href: "/pl/docs/components/tabs",
description: "Przełączanie powiązanych widoków bez opuszczania strony.",
},
{
title: "Command",
href: "/pl/docs/components/command",
description: "Szybkie wyszukiwanie i akcje, przede wszystkim z klawiatury.",
},
{
title: "Select",
href: "/pl/docs/components/select",
description: "Wybór jednej opcji z listy zdefiniowanych wartości.",
},
{
title: "Toast",
href: "/pl/docs/components/toast",
description: "Krótka, nieblokująca informacja zwrotna o operacji.",
},
]
export default function NavigationMenuDemo() {
return (
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Pierwsze kroki</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-120 grid-cols-[11rem_1fr] gap-1.5">
<li className="row-span-3">
<NavigationMenuLink
render={<Link href="/pl/docs" />}
className="flex h-full flex-col items-start justify-end gap-1.5 rounded-lg bg-muted p-4 transition-colors duration-fast hover:bg-gray-4"
>
<span className="text-heading-sm text-foreground">prfct</span>
<span className="text-body-xs text-muted-foreground">
Interfejsy dopięte na ostatni guzik.
</span>
</NavigationMenuLink>
</li>
{[
{
title: "Wprowadzenie",
href: "/pl/docs",
description: "Czym jest prfct i jak jest zorganizowany.",
},
{
title: "Instalacja",
href: "/pl/docs/installation",
description: "Dodaj prfct do projektu Next.js.",
},
{
title: "Fundamenty",
href: "/pl/docs/foundations/color",
description: "Kolor, typografia, odstępy, ruch.",
},
].map((item) => (
<li key={item.title}>
<NavigationMenuLink
render={<Link href={item.href} />}
className="flex-col items-start gap-0.5 p-3"
>
<span className="font-medium text-foreground">
{item.title}
</span>
<span className="text-body-xs text-muted-foreground">
{item.description}
</span>
</NavigationMenuLink>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>Komponenty</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-136 grid-cols-2 gap-1">
{components.map((component) => (
<li key={component.title}>
<NavigationMenuLink
render={<Link href={component.href} />}
className="flex-col items-start gap-0.5 p-3"
>
<span className="font-medium text-foreground">
{component.title}
</span>
<span className="line-clamp-2 text-body-xs text-muted-foreground">
{component.description}
</span>
</NavigationMenuLink>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
render={<Link href="/pl/themes" />}
className={navigationMenuTriggerStyle()}
>
Motywy
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
)
}Instalacja
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/navigation-menu.jsonUżycie
import Link from "next/link"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Products</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuLink render={<Link href="/analytics" />}>
Analytics
</NavigationMenuLink>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
render={<Link href="/pricing" />}
className={navigationMenuTriggerStyle()}
>
Pricing
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>Wszystkie panele dzielą jeden popup. Gdy użytkownik przechodzi od jednego wyzwalacza do kolejnego, popup płynnie dopasowuje się do rozmiaru nowego panelu, a treść wsuwa się z kierunku ruchu — to wyraźna wskazówka przestrzenna, że przegląda elementy tego samego poziomu, a nie otwiera czegoś nowego.
Przykłady
Nawigacja witryny
Łącz wyzwalacze otwierające panele ze zwykłymi linkami ostylowanymi przez navigationMenuTriggerStyle() — dzięki temu każdy element paska wygląda i zachowuje się spójnie.
import Link from "next/link"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"
const components = [
{
title: "Button",
href: "/pl/docs/components/button",
description: "Wywołuje akcję. Najważniejszy element interaktywny.",
},
{
title: "Dialog",
href: "/pl/docs/components/dialog",
description: "Skupione zadanie nad stroną, z jasną drogą wyjścia.",
},
{
title: "Tabs",
href: "/pl/docs/components/tabs",
description: "Przełączanie powiązanych widoków bez opuszczania strony.",
},
{
title: "Command",
href: "/pl/docs/components/command",
description: "Szybkie wyszukiwanie i akcje, przede wszystkim z klawiatury.",
},
{
title: "Select",
href: "/pl/docs/components/select",
description: "Wybór jednej opcji z listy zdefiniowanych wartości.",
},
{
title: "Toast",
href: "/pl/docs/components/toast",
description: "Krótka, nieblokująca informacja zwrotna o operacji.",
},
]
export default function NavigationMenuDemo() {
return (
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Pierwsze kroki</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-120 grid-cols-[11rem_1fr] gap-1.5">
<li className="row-span-3">
<NavigationMenuLink
render={<Link href="/pl/docs" />}
className="flex h-full flex-col items-start justify-end gap-1.5 rounded-lg bg-muted p-4 transition-colors duration-fast hover:bg-gray-4"
>
<span className="text-heading-sm text-foreground">prfct</span>
<span className="text-body-xs text-muted-foreground">
Interfejsy dopięte na ostatni guzik.
</span>
</NavigationMenuLink>
</li>
{[
{
title: "Wprowadzenie",
href: "/pl/docs",
description: "Czym jest prfct i jak jest zorganizowany.",
},
{
title: "Instalacja",
href: "/pl/docs/installation",
description: "Dodaj prfct do projektu Next.js.",
},
{
title: "Fundamenty",
href: "/pl/docs/foundations/color",
description: "Kolor, typografia, odstępy, ruch.",
},
].map((item) => (
<li key={item.title}>
<NavigationMenuLink
render={<Link href={item.href} />}
className="flex-col items-start gap-0.5 p-3"
>
<span className="font-medium text-foreground">
{item.title}
</span>
<span className="text-body-xs text-muted-foreground">
{item.description}
</span>
</NavigationMenuLink>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>Komponenty</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-136 grid-cols-2 gap-1">
{components.map((component) => (
<li key={component.title}>
<NavigationMenuLink
render={<Link href={component.href} />}
className="flex-col items-start gap-0.5 p-3"
>
<span className="font-medium text-foreground">
{component.title}
</span>
<span className="line-clamp-2 text-body-xs text-muted-foreground">
{component.description}
</span>
</NavigationMenuLink>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuLink
render={<Link href="/pl/themes" />}
className={navigationMenuTriggerStyle()}
>
Motywy
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
)
}Linki z ikonami
Dwukolumnowa lista miejsc docelowych, każde z ikoną i jednowierszowym opisem. To opisy robią tu najwięcej: pozwalają wybrać bez przeklikiwania się przez kolejne strony.
import {
ChartColumnIcon,
DatabaseIcon,
GlobeIcon,
ShieldCheckIcon,
WorkflowIcon,
ZapIcon,
} from "lucide-react"
import Link from "next/link"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"
const products = [
{
title: "Analityka",
description: "Śledź wykorzystanie w czasie rzeczywistym.",
icon: ChartColumnIcon,
},
{
title: "Automatyzacje",
description: "Przepływy pracy, które działają same.",
icon: WorkflowIcon,
},
{
title: "Sieć brzegowa",
description: "Obsługuj każdy region w mniej niż 50 ms.",
icon: GlobeIcon,
},
{
title: "Magazyn danych",
description: "Trwały magazyn obiektów z wersjonowaniem.",
icon: DatabaseIcon,
},
{
title: "Bezpieczeństwo",
description: "SSO, logi audytowe i SCIM.",
icon: ShieldCheckIcon,
},
{
title: "Funkcje",
description: "Uruchamiaj kod blisko swoich użytkowników.",
icon: ZapIcon,
},
]
export default function NavigationMenuIconList() {
return (
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Produkty</NavigationMenuTrigger>
<NavigationMenuContent>
<ul className="grid w-144 grid-cols-2 gap-1">
{products.map(({ title, description, icon: Icon }) => (
<li key={title}>
<NavigationMenuLink
render={<Link href="/pl/docs/components/navigation-menu" />}
className="items-start gap-3 p-3"
>
<span className="flex size-9 shrink-0 items-center justify-center rounded-lg border bg-card text-muted-foreground shadow-raised">
<Icon className="size-4" />
</span>
<span className="flex flex-col gap-0.5">
<span className="font-medium text-foreground">
{title}
</span>
<span className="text-body-xs text-muted-foreground">
{description}
</span>
</span>
</NavigationMenuLink>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
)
}Wyróżniona treść
Zarezerwuj boczną kolumnę panelu na jedną aktualną pozycję — premierę, poradnik, wydarzenie. Jedno wyróżnienie na panel; dwa zaczną ze sobą konkurować.
import {
ArrowRightIcon,
BookOpenIcon,
NewspaperIcon,
PlayCircleIcon,
} from "lucide-react"
import Link from "next/link"
import { Badge } from "@/components/ui/badge"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"
const resources = [
{
title: "Dokumentacja",
description: "Przewodniki i dokumentacja API.",
icon: BookOpenIcon,
},
{
title: "Lista zmian",
description: "Co wydaliśmy w tym tygodniu.",
icon: NewspaperIcon,
},
{
title: "Samouczki",
description: "Lekcje wideo krok po kroku.",
icon: PlayCircleIcon,
},
]
export default function NavigationMenuFeatured() {
return (
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Zasoby</NavigationMenuTrigger>
<NavigationMenuContent>
<div className="grid w-128 grid-cols-[1fr_13rem] gap-1.5">
<ul className="flex flex-col gap-1">
{resources.map(({ title, description, icon: Icon }) => (
<li key={title}>
<NavigationMenuLink
render={<Link href="/pl/docs" />}
className="items-start gap-3 p-3"
>
<Icon className="mt-0.5 text-muted-foreground" />
<span className="flex flex-col gap-0.5">
<span className="font-medium text-foreground">
{title}
</span>
<span className="text-body-xs text-muted-foreground">
{description}
</span>
</span>
</NavigationMenuLink>
</li>
))}
</ul>
<NavigationMenuLink
render={<Link href="/pl/docs/installation" />}
className="flex-col items-start justify-between gap-6 rounded-lg border bg-surface p-4 hover:bg-accent"
>
<Badge variant="brand" size="sm">
Nowość
</Badge>
<span className="flex flex-col gap-1">
<span className="font-medium text-foreground">
Zobacz, co nowego
</span>
<span className="text-body-xs text-muted-foreground">
Tokeny, ponad 50 komponentów i kreator motywów.
</span>
<span className="mt-2 inline-flex items-center gap-1 text-label-sm text-link">
Przeczytaj ogłoszenie
<ArrowRightIcon className="size-3.5" />
</span>
</span>
</NavigationMenuLink>
</div>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
)
}Same linki
Gdy nie ma paneli, menu jest ostylowaną listą linków. Każdy link pozostaje osobnym przystankiem klawisza Tab, a między linkami można też przechodzić strzałkami. Bieżącą sekcję oznacz propem active — ustawia on aria-current="page" i styl aktywnego elementu.
import Link from "next/link"
import {
NavigationMenu,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"
const links = [
{ title: "Przegląd", href: "/pl/docs" },
{ title: "Komponenty", href: "/pl/docs/components" },
{ title: "Motywy", href: "/pl/themes" },
{ title: "Przykłady", href: "/pl/examples" },
]
export default function NavigationMenuLinksOnly() {
const current = "/docs/components"
return (
<NavigationMenu>
<NavigationMenuList className="gap-1">
{links.map((link) => (
<NavigationMenuItem key={link.href}>
<NavigationMenuLink
active={link.href === current}
render={<Link href={link.href} />}
className={navigationMenuTriggerStyle({
className: "data-active:bg-accent data-active:text-foreground",
})}
>
{link.title}
</NavigationMenuLink>
</NavigationMenuItem>
))}
</NavigationMenuList>
</NavigationMenu>
)
}Wytyczne
Kiedy używać
- Jako główna nawigacja stron marketingowych i dokumentacji z kilkoma sekcjami najwyższego poziomu.
- Gdy sekcja potrzebuje drogowskazów — opisów, grupowania albo wyróżnionej pozycji — zanim użytkownik zdecyduje, dokąd przejść.
Kiedy nie używać
- Do nawigacji w aplikacji z wieloma miejscami docelowymi lub głęboką hierarchią — użyj Sidebar.
- Do poleceń i akcji — użyj Dropdown Menu lub Menubar. Menu nawigacyjne zawiera wyłącznie linki.
- Na małych ekranach — poniżej
mdzwiń nawigację do prostej listy w panelu Sheet.
Zawartość panelu
Trzymaj się pięciu–siedmiu pozycji najwyższego poziomu. Każdy link w panelu dostaje krótki tytuł i najlepiej jednowierszowy opis w formie zwykłego zdania — z tą samą interpunkcją w całym panelu. Długie panele podziel na kolumny powiązanych linków i nie chowaj niczego ważnego wyłącznie w panelu — to samo miejsce powinno być osiągalne także z samej strony albo ze stopki.
Najechanie i kliknięcie
Panele otwierają się po kliknięciu oraz po najechaniu, z krótkim opóźnieniem delay (50 ms). Popup i wyzwalacz łączy niewidoczny mostek, więc kursor może przejść po skosie do panelu, nie zamykając go. Nigdy nie umieszczaj celu nawigacji na samym wyzwalaczu — wyzwalacz tylko otwiera swój panel; strona przeglądowa sekcji należy do panelu jako jego pierwszy link.
Dostępność
Navigation Menu renderuje element <nav> z listą pozycji. Wyzwalacze to przyciski z aria-expanded; panele to rozwijana treść, a nie menu — do linków w środku przechodzi się klawiszem Tab, tak samo jak na stronie.
| Klawisz | Działanie |
|---|---|
Tab | Przechodzi między wyzwalaczami i linkami, a także do linków otwartego panelu. |
←→ | Przenosi fokus między wyzwalaczami i linkami najwyższego poziomu. |
EnterSpacja | Otwiera lub zamyka panel wyzwalacza, na którym jest fokus. Na linku tylko Enter przechodzi pod jego adres, jak przy każdym linku. |
↓ | Na wyzwalaczu otwiera jego panel i przenosi do niego fokus. |
Esc | Zamyka panel i przywraca fokus na jego wyzwalacz. |
- Bieżąca strona. Przekaż
activedo linku bieżącej strony lub sekcji — technologie wspomagające dostaną tę informację jakoaria-current="page". - Nazwij każdą nawigację. Jeśli strona ma więcej niż jeden
<nav>, nadaj każdemuaria-label(Główna, Stopka), aby użytkownicy czytników ekranu mogli je rozróżnić.
Dokumentacja API
NavigationMenu
Główny element <nav>. Przyjmuje wszystkie propsy NavigationMenu.Root z Base UI. Po swoich elementach potomnych renderuje NavigationMenuPositioner: portal, pozycjoner, popup i viewport wspólne dla wszystkich paneli. Nie umieszczasz go samodzielnie; jest eksportowany na potrzeby budowy własnego komponentu głównego i przyjmuje side ("bottom"), sideOffset (8), align ("start") oraz alignOffset (0).
NavigationMenuItem
NavigationMenuTrigger
Otwiera panel swojej pozycji. Renderuje strzałkę, która obraca się, gdy panel jest otwarty. Przyjmuje disabled.
NavigationMenuContent
Panel pozycji. Jego rozmiar ustal, nadając szerokość pierwszemu elementowi potomnemu; popup animuje przejścia między rozmiarami paneli.
NavigationMenuLink
navigationMenuTriggerStyle
Generator klas, dzięki któremu zwykły NavigationMenuLink wygląda dokładnie jak wyzwalacz. Przyjmuje { className }, aby rozszerzyć style.