---
import CircleAlertIcon from "@lucide/astro/icons/circle-alert"
import CircleCheckIcon from "@lucide/astro/icons/circle-check"
import CircleDashedIcon from "@lucide/astro/icons/circle-dashed"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"
const gettingStarted = [
{
title: "Introduction",
href: "/docs",
description: "Re-usable components built with Tailwind CSS.",
},
{
title: "Installation",
href: "/docs/installation",
description: "How to install dependencies and structure your app.",
},
{
title: "Typography",
href: "https://ui.shadcn.com/docs/typeset",
description: "Styles for headings, paragraphs, lists...etc",
},
]
const components: { title: string; href: string; description: string }[] = [
{
title: "Alert Dialog",
href: "https://ui.shadcn.com/docs/components/base/alert-dialog",
description:
"A modal dialog that interrupts the user with important content and expects a response.",
},
{
title: "Hover Card",
href: "https://ui.shadcn.com/docs/components/base/hover-card",
description:
"For sighted users to preview content available behind a link.",
},
{
title: "Progress",
href: "https://ui.shadcn.com/docs/components/base/progress",
description:
"Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.",
},
{
title: "Scroll-area",
href: "https://ui.shadcn.com/docs/components/base/scroll-area",
description: "Visually or semantically separates content.",
},
{
title: "Tabs",
href: "https://ui.shadcn.com/docs/components/base/tabs",
description:
"A set of layered sections of content, known as tab panels, that are displayed one at a time.",
},
{
title: "Tooltip",
href: "https://ui.shadcn.com/docs/components/base/tooltip",
description:
"A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.",
},
]
---
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem value="getting-started">
<NavigationMenuTrigger>Getting started</NavigationMenuTrigger>
<NavigationMenuContent>
<ul class="w-96">
{gettingStarted.map((item) => (
<li>
<NavigationMenuLink href={item.href}>
<div class="flex flex-col gap-1 text-sm">
<div class="leading-none font-medium">{item.title}</div>
<div class="line-clamp-2 text-muted-foreground">
{item.description}
</div>
</div>
</NavigationMenuLink>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem value="components" class="hidden md:flex">
<NavigationMenuTrigger>Components</NavigationMenuTrigger>
<NavigationMenuContent>
<ul class="grid w-[400px] gap-2 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
{components.map((component) => (
<li>
<NavigationMenuLink href={component.href}>
<div class="flex flex-col gap-1 text-sm">
<div class="leading-none font-medium">{component.title}</div>
<div class="line-clamp-2 text-muted-foreground">
{component.description}
</div>
</div>
</NavigationMenuLink>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem value="with-icon">
<NavigationMenuTrigger>With Icon</NavigationMenuTrigger>
<NavigationMenuContent>
<ul class="grid w-[200px]">
<li>
<NavigationMenuLink href="#" class="flex-row items-center gap-2">
<CircleAlertIcon />
Backlog
</NavigationMenuLink>
<NavigationMenuLink href="#" class="flex-row items-center gap-2">
<CircleDashedIcon />
To Do
</NavigationMenuLink>
<NavigationMenuLink href="#" class="flex-row items-center gap-2">
<CircleCheckIcon />
Done
</NavigationMenuLink>
</li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem value="docs">
<NavigationMenuLink href="/docs" class={navigationMenuTriggerStyle()}>
Docs
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>Installation
npx shadcn@latest add @fulldev/navigation-menu
Usage
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"
<NavigationMenu>
<NavigationMenuList>
<NavigationMenuItem value="item-one">
<NavigationMenuTrigger>Item One</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuLink href="/docs">Link</NavigationMenuLink>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
Every NavigationMenuItem needs a unique value. NavigationMenuLink renders
an a; pass href and, for the current route, active. Use
navigationMenuTriggerStyle() to give a top-level link the trigger look.
Composition
NavigationMenu
├── NavigationMenuList
│ ├── NavigationMenuItem
│ │ ├── NavigationMenuTrigger
│ │ └── NavigationMenuContent
│ │ ├── NavigationMenuLink
│ │ └── NavigationMenuLink
│ └── NavigationMenuItem
│ └── NavigationMenuLink
└── NavigationMenuIndicator
RTL
Set dir on NavigationMenu and NavigationMenuContent, and align="end" to
open panels from the start edge in RTL.
---
import CircleAlertIcon from "@lucide/astro/icons/circle-alert"
import CircleCheckIcon from "@lucide/astro/icons/circle-check"
import CircleDashedIcon from "@lucide/astro/icons/circle-dashed"
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
navigationMenuTriggerStyle,
} from "@/components/ui/navigation-menu"
const translations = {
en: {
dir: "ltr",
values: {
gettingStarted: "Getting started",
introduction: "Introduction",
introductionDesc: "Re-usable components built with Tailwind CSS.",
installation: "Installation",
installationDesc: "How to install dependencies and structure your app.",
typography: "Typography",
typographyDesc: "Styles for headings, paragraphs, lists...etc",
components: "Components",
alertDialog: "Alert Dialog",
alertDialogDesc:
"A modal dialog that interrupts the user with important content and expects a response.",
hoverCard: "Hover Card",
hoverCardDesc:
"For sighted users to preview content available behind a link.",
progress: "Progress",
progressDesc:
"Displays an indicator showing the completion progress of a task, typically displayed as a progress bar.",
scrollArea: "Scroll-area",
scrollAreaDesc: "Visually or semantically separates content.",
tabs: "Tabs",
tabsDesc:
"A set of layered sections of content, known as tab panels, that are displayed one at a time.",
tooltip: "Tooltip",
tooltipDesc:
"A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it.",
withIcon: "With Icon",
backlog: "Backlog",
toDo: "To Do",
done: "Done",
docs: "Docs",
},
},
ar: {
dir: "rtl",
values: {
gettingStarted: "البدء",
introduction: "مقدمة",
introductionDesc:
"مكونات قابلة لإعادة الاستخدام مبنية باستخدام Tailwind CSS.",
installation: "التثبيت",
installationDesc: "كيفية تثبيت التبعيات وتنظيم تطبيقك.",
typography: "الطباعة",
typographyDesc: "أنماط للعناوين والفقرات والقوائم...إلخ",
components: "المكونات",
alertDialog: "حوار التنبيه",
alertDialogDesc: "حوار نافذة يقطع المستخدم بمحتوى مهم ويتوقع استجابة.",
hoverCard: "بطاقة التحويم",
hoverCardDesc: "للمستخدمين المبصرين لمعاينة المحتوى المتاح خلف الرابط.",
progress: "التقدم",
progressDesc:
"يعرض مؤشرًا يوضح تقدم إتمام المهمة، عادةً يتم عرضه كشريط تقدم.",
scrollArea: "منطقة التمرير",
scrollAreaDesc: "يفصل المحتوى بصريًا أو دلاليًا.",
tabs: "التبويبات",
tabsDesc:
"مجموعة من أقسام المحتوى المتعددة الطبقات، المعروفة بألواح التبويب، التي يتم عرضها واحدة في كل مرة.",
tooltip: "تلميح",
tooltipDesc:
"نافذة منبثقة تعرض معلومات متعلقة بعنصر عندما يتلقى العنصر التركيز على لوحة المفاتيح أو عند تحويم الماوس فوقه.",
withIcon: "مع أيقونة",
backlog: "قائمة الانتظار",
toDo: "المهام",
done: "منجز",
docs: "الوثائق",
},
},
he: {
dir: "rtl",
values: {
gettingStarted: "התחלה",
introduction: "הקדמה",
introductionDesc: "רכיבים לשימוש חוזר שנבנו עם Tailwind CSS.",
installation: "התקנה",
installationDesc: "כיצד להתקין תלויות ולבנות את האפליקציה שלך.",
typography: "טיפוגרפיה",
typographyDesc: "סגנונות לכותרות, פסקאות, רשימות...וכו'",
components: "רכיבים",
alertDialog: "דיאלוג התראה",
alertDialogDesc: "דיאלוג מודאלי שמפריע למשתמש עם תוכן חשוב ומצפה לתגובה.",
hoverCard: "כרטיס ריחוף",
hoverCardDesc:
"למשתמשים רואים כדי להציג תצוגה מקדימה של תוכן זמין מאחורי קישור.",
progress: "התקדמות",
progressDesc:
"מציג אינדיקטור המציג את התקדמות ההשלמה של משימה, בדרך כלל מוצג כסרגל התקדמות.",
scrollArea: "אזור גלילה",
scrollAreaDesc: "מפריד תוכן חזותית או סמנטית.",
tabs: "כרטיסיות",
tabsDesc:
"קבוצה של חלקי תוכן מרובדים, המכונים לוחות כרטיסיות, המוצגים אחד בכל פעם.",
tooltip: "טולטיפ",
tooltipDesc:
"חלון קופץ המציג מידע הקשור לאלמנט כאשר האלמנט מקבל מיקוד מקלדת או כאשר העכבר מרחף מעליו.",
withIcon: "עם אייקון",
backlog: "רשימת המתנה",
toDo: "לעשות",
done: "הושלם",
docs: "תיעוד",
},
},
} as const
const components = [
{
titleKey: "alertDialog",
descriptionKey: "alertDialogDesc",
href: "https://ui.shadcn.com/docs/components/base/alert-dialog",
},
{
titleKey: "hoverCard",
descriptionKey: "hoverCardDesc",
href: "https://ui.shadcn.com/docs/components/base/hover-card",
},
{
titleKey: "progress",
descriptionKey: "progressDesc",
href: "https://ui.shadcn.com/docs/components/base/progress",
},
{
titleKey: "scrollArea",
descriptionKey: "scrollAreaDesc",
href: "https://ui.shadcn.com/docs/components/base/scroll-area",
},
{
titleKey: "tabs",
descriptionKey: "tabsDesc",
href: "https://ui.shadcn.com/docs/components/base/tabs",
},
{
titleKey: "tooltip",
descriptionKey: "tooltipDesc",
href: "https://ui.shadcn.com/docs/components/base/tooltip",
},
] as const
const language = "ar"
const { dir, values: t } = translations[language]
const gettingStarted = [
{ title: t.introduction, href: "/docs", description: t.introductionDesc },
{
title: t.installation,
href: "/docs/installation",
description: t.installationDesc,
},
{
title: t.typography,
href: "https://ui.shadcn.com/docs/typeset",
description: t.typographyDesc,
},
]
---
<NavigationMenu dir={dir} align={dir === "rtl" ? "end" : "start"}>
<NavigationMenuList>
<NavigationMenuItem value="getting-started">
<NavigationMenuTrigger>{t.gettingStarted}</NavigationMenuTrigger>
<NavigationMenuContent
dir={dir}
data-lang={dir === "rtl" ? language : undefined}
>
<ul class="w-96">
{gettingStarted.map((item) => (
<li>
<NavigationMenuLink href={item.href}>
<div class="flex flex-col gap-1 text-sm">
<div class="leading-none font-medium">{item.title}</div>
<div class="line-clamp-2 text-muted-foreground">
{item.description}
</div>
</div>
</NavigationMenuLink>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem value="components" class="hidden md:flex">
<NavigationMenuTrigger>{t.components}</NavigationMenuTrigger>
<NavigationMenuContent
dir={dir}
data-lang={dir === "rtl" ? language : undefined}
>
<ul class="grid w-[400px] gap-2 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
{components.map((component) => (
<li>
<NavigationMenuLink href={component.href}>
<div class="flex flex-col gap-1 text-sm">
<div class="leading-none font-medium">
{t[component.titleKey]}
</div>
<div class="line-clamp-2 text-muted-foreground">
{t[component.descriptionKey]}
</div>
</div>
</NavigationMenuLink>
</li>
))}
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem value="with-icon">
<NavigationMenuTrigger>{t.withIcon}</NavigationMenuTrigger>
<NavigationMenuContent
dir={dir}
data-lang={dir === "rtl" ? language : undefined}
>
<ul class="grid w-[200px]">
<li>
<NavigationMenuLink href="#" class="flex-row items-center gap-2">
<CircleAlertIcon />
{t.backlog}
</NavigationMenuLink>
<NavigationMenuLink href="#" class="flex-row items-center gap-2">
<CircleDashedIcon />
{t.toDo}
</NavigationMenuLink>
<NavigationMenuLink href="#" class="flex-row items-center gap-2">
<CircleCheckIcon />
{t.done}
</NavigationMenuLink>
</li>
</ul>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem value="docs">
<NavigationMenuLink
href="/docs"
class={navigationMenuTriggerStyle()}
data-lang={dir === "rtl" ? language : undefined}
>
{t.docs}
</NavigationMenuLink>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>Notes
- Closed panels start hidden in the rendered HTML to prevent a flash before JavaScript initializes. The controller manages visibility after initialization.
- Place
NavigationMenuIndicatorinsideNavigationMenuListso its position is relative to the list whose measurements Data Slot uses. - The menu opens below its trigger, aligned to the start, with an 8px gap. Set
side,align,sideOffsetandalignOffsetonNavigationMenuto change this placement. Data Slot reads the root configuration and positions the shared popup.
Content mounting
Data Slot’s recommended "lazy" strategy is the default: closed content is
retained but detached from the document. Fulldev initializes nested Data Slot
components inside that retained content too.
Use mountStrategy="eager" when an integration needs to query closed content
through the document. Both strategies include content in server-rendered HTML;
neither adds a no-JavaScript fallback.
Responsive activation
Pass a media query to NavigationMenu to import and initialize its controller only
while that query matches. Leaving the query destroys the controller and releases
its portals and scroll lock. Returning initializes it again. A trigger click
during the first download is performed once loading finishes, provided the query
still matches.
The default has no breakpoint restriction. Keep the query and CSS visibility in
the owning header block or site. Header blocks use fold="lg" by default and
accept fold="xl"; that choice controls both visibility and activation.
API Reference
Source: src/components/ui/navigation-menu
See the Data Slot docs for the interactive behavior.