Docs
Navigation Menu
Navigation Menu
A collection of links for navigating websites.
import { siteConfig } from "@/config/site";
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuDescription,
NavigationMenuItem,
NavigationMenuItemLabel,
NavigationMenuLink,
NavigationMenuTrigger,
} from "@repo/tailwindcss/ui/navigation-menu";
import type { ParentProps } from "solid-js";
import { For } from "solid-js";
const ListItem = (props: ParentProps<{ title: string; href: string }>) => {
return (
<NavigationMenuLink
href={props.href}
class="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-[box-shadow,background-color] duration-200 hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring"
>
<NavigationMenuItemLabel class="text-sm font-medium leading-none">
{props.title}
</NavigationMenuItemLabel>
<NavigationMenuDescription class="line-clamp-2 text-sm leading-snug text-muted-foreground">
{props.children}
</NavigationMenuDescription>
</NavigationMenuLink>
);
};
const components: { title: string; href: string; description: string }[] = [
{
title: "Data Table",
href: "/docs/components/data-table",
description: "Powerful table and datagrids built using TanStack Table.",
},
{
title: "Date Picker",
href: "/docs/components/date-picker",
description:
"A component that allows users to select a date from a calendar.",
},
{
title: "OTP Field",
href: "/docs/components/otp-field",
description: "An accessible and customizable OTP Input component.",
},
{
title: "Resizable",
href: "/docs/components/resizable",
description:
"A component that divides your interface into resizable sections.",
},
{
title: "Sonner",
href: "/docs/components/sonner",
description: "An opinionated toast component for Solid.",
},
{
title: "Toggle Group",
href: "/docs/components/toggle-group",
description:
"A set of two-state buttons that can be toggled on (pressed) or off (not pressed).",
},
];
const NavigationMenuDemo = () => {
return (
<NavigationMenu>
<NavigationMenuItem>
<NavigationMenuTrigger class="transition-[box-shadow,background-color] focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring data-[expanded]:bg-accent">
Learn
</NavigationMenuTrigger>
<NavigationMenuContent class="grid gap-3 p-4 w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr] [&>li:first-of-type]:row-span-3">
<NavigationMenuLink
href="/"
class="flex h-full w-full select-none flex-col justify-end rounded-md bg-gradient-to-b from-muted/50 to-muted p-6 no-underline outline-none transition-shadow duration-200 hover:shadow-md focus-visible:shadow-md focus-visible:ring-[1.5px] focus-visible:ring-ring"
>
<NavigationMenuItemLabel class="mb-2 mt-4 text-lg font-medium">
{siteConfig.title}
</NavigationMenuItemLabel>
<NavigationMenuDescription class="text-sm leading-tight text-muted-foreground">
{siteConfig.description}
</NavigationMenuDescription>
</NavigationMenuLink>
<ListItem href="/docs" title="Introduction">
{siteConfig.description}.
</ListItem>
<ListItem href="/docs/installation" title="Installation">
How to install dependencies and structure your app.
</ListItem>
<ListItem href="/docs/components/typography" title="Typography">
Styles for headings, paragraphs, lists...etc.
</ListItem>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger class="transition-[box-shadow,background-color] focus-visible:outline-none focus-visible:ring-1.5px focus-visible:ring-ring data-[expanded]:bg-accent">
Overview
</NavigationMenuTrigger>
<NavigationMenuContent class="grid w-[400px] gap-3 p-4 md:w-[500px] md:grid-cols-2 lg:w-[600px]">
<For each={components}>
{(item) => (
<ListItem href={item.href} title={item.title}>
{item.description}
</ListItem>
)}
</For>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuTrigger
as="a"
href="/docs"
class="transition-[box-shadow,background-color] focus-visible:outline-none focus-visible:ring-[1.5px] focus-visible:ring-ring data-[expanded]:bg-accent"
>
Documentation
</NavigationMenuTrigger>
</NavigationMenu>
);
};
export default NavigationMenuDemo;
Installation
npx shadcn-solid@latest add navigation-menu
Install the following dependencies:
npm install @kobalte/core
Copy and paste the following code into your project:
import { cn } from "@/libs/cn";
import type {
NavigationMenuContentProps,
NavigationMenuRootProps,
NavigationMenuTriggerProps,
} from "@kobalte/core/navigation-menu";
import { NavigationMenu as NavigationMenuPrimitive } from "@kobalte/core/navigation-menu";
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
import {
type ParentProps,
Show,
type ValidComponent,
mergeProps,
splitProps,
} from "solid-js";
export const NavigationMenuItem = NavigationMenuPrimitive.Menu;
export const NavigationMenuLink = NavigationMenuPrimitive.Item;
export const NavigationMenuItemLabel = NavigationMenuPrimitive.ItemLabel;
export const NavigationMenuDescription =
NavigationMenuPrimitive.ItemDescription;
export const NavigationMenuItemIndicator =
NavigationMenuPrimitive.ItemIndicator;
export const NavigationMenuSub = NavigationMenuPrimitive.Sub;
export const NavigationMenuSubTrigger = NavigationMenuPrimitive.SubTrigger;
export const NavigationMenuSubContent = NavigationMenuPrimitive.SubContent;
export const NavigationMenuRadioGroup = NavigationMenuPrimitive.RadioGroup;
export const NavigationMenuRadioItem = NavigationMenuPrimitive.RadioItem;
export const NavigationMenuCheckboxItem = NavigationMenuPrimitive.CheckboxItem;
export const NavigationMenuSeparator = NavigationMenuPrimitive.Separator;
type withArrow = {
withArrow?: boolean;
};
type navigationMenuProps<T extends ValidComponent = "ul"> = ParentProps<
NavigationMenuRootProps<T> &
withArrow & {
class?: string;
}
>;
export const NavigationMenu = <T extends ValidComponent = "ul">(
props: PolymorphicProps<T, navigationMenuProps<T>>,
) => {
const merge = mergeProps<navigationMenuProps<T>[]>(
{
get gutter() {
return props.withArrow ? props.gutter : 6;
},
withArrow: false,
flip: false,
},
props,
);
const [local, rest] = splitProps(merge as navigationMenuProps, [
"class",
"children",
"withArrow",
]);
return (
<NavigationMenuPrimitive
class={cn("flex w-max items-center justify-center gap-x-1", local.class)}
{...rest}
>
{local.children}
<NavigationMenuPrimitive.Viewport
class={cn(
"pointer-events-none z-50 overflow-x-clip overflow-y-visible rounded-md border bg-popover text-popover-foreground shadow",
"h-[--kb-navigation-menu-viewport-height] w-[--kb-navigation-menu-viewport-width] transition-[width,height] duration-300",
"origin-[--kb-menu-content-transform-origin]",
"data-[expanded]:duration-300 data-[expanded]:animate-in data-[expanded]:fade-in data-[expanded]:zoom-in-95",
"data-[closed]:duration-300 data-[closed]:animate-out data-[closed]:fade-out data-[closed]:zoom-out-95",
)}
>
<Show when={local.withArrow}>
<NavigationMenuPrimitive.Arrow class="transition-transform duration-300" />
</Show>
</NavigationMenuPrimitive.Viewport>
</NavigationMenuPrimitive>
);
};
type navigationMenuTriggerProps<T extends ValidComponent = "button"> =
ParentProps<
NavigationMenuTriggerProps<T> &
withArrow & {
class?: string;
}
>;
export const NavigationMenuTrigger = <T extends ValidComponent = "button">(
props: PolymorphicProps<T, navigationMenuTriggerProps<T>>,
) => {
const merge = mergeProps<navigationMenuTriggerProps<T>[]>(
{
get withArrow() {
return props.as === undefined ? true : props.withArrow;
},
},
props,
);
const [local, rest] = splitProps(merge as navigationMenuTriggerProps, [
"class",
"children",
"withArrow",
]);
return (
<NavigationMenuPrimitive.Trigger
class={cn(
"inline-flex w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium outline-none transition-colors duration-300 hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50",
local.class,
)}
{...rest}
>
{local.children}
<Show when={local.withArrow}>
<NavigationMenuPrimitive.Icon
class="ml-1 size-3 transition-transform duration-300 data-[expanded]:rotate-180"
as="svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m6 9l6 6l6-6"
/>
</NavigationMenuPrimitive.Icon>
</Show>
</NavigationMenuPrimitive.Trigger>
);
};
type navigationMenuContentProps<T extends ValidComponent = "ul"> = ParentProps<
NavigationMenuContentProps<T> & {
class?: string;
}
>;
export const NavigationMenuContent = <T extends ValidComponent = "ul">(
props: PolymorphicProps<T, navigationMenuContentProps<T>>,
) => {
const [local, rest] = splitProps(props as navigationMenuContentProps, [
"class",
"children",
]);
return (
<NavigationMenuPrimitive.Portal>
<NavigationMenuPrimitive.Content
class={cn(
"absolute left-0 top-0 p-4 outline-none",
"data-[motion^=from-]:duration-300 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52",
"data-[motion^=to-]:duration-300 data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52",
local.class,
)}
{...rest}
>
{local.children}
</NavigationMenuPrimitive.Content>
</NavigationMenuPrimitive.Portal>
);
};
Install the following dependencies:
npm install @kobalte/core
Copy and paste the following code into your project:
import { cn } from "@/libs/cn";
import type {
NavigationMenuContentProps,
NavigationMenuRootProps,
NavigationMenuTriggerProps,
} from "@kobalte/core/navigation-menu";
import { NavigationMenu as NavigationMenuPrimitive } from "@kobalte/core/navigation-menu";
import type { PolymorphicProps } from "@kobalte/core/polymorphic";
import {
type ParentProps,
Show,
type ValidComponent,
mergeProps,
splitProps,
} from "solid-js";
export const NavigationMenuItem = NavigationMenuPrimitive.Menu;
export const NavigationMenuLink = NavigationMenuPrimitive.Item;
export const NavigationMenuItemLabel = NavigationMenuPrimitive.ItemLabel;
export const NavigationMenuDescription =
NavigationMenuPrimitive.ItemDescription;
export const NavigationMenuItemIndicator =
NavigationMenuPrimitive.ItemIndicator;
export const NavigationMenuSub = NavigationMenuPrimitive.Sub;
export const NavigationMenuSubTrigger = NavigationMenuPrimitive.SubTrigger;
export const NavigationMenuSubContent = NavigationMenuPrimitive.SubContent;
export const NavigationMenuRadioGroup = NavigationMenuPrimitive.RadioGroup;
export const NavigationMenuRadioItem = NavigationMenuPrimitive.RadioItem;
export const NavigationMenuCheckboxItem = NavigationMenuPrimitive.CheckboxItem;
export const NavigationMenuSeparator = NavigationMenuPrimitive.Separator;
type withArrow = {
withArrow?: boolean;
};
type navigationMenuProps<T extends ValidComponent = "ul"> = ParentProps<
NavigationMenuRootProps<T> &
withArrow & {
class?: string;
}
>;
export const NavigationMenu = <T extends ValidComponent = "ul">(
props: PolymorphicProps<T, navigationMenuProps<T>>,
) => {
const merge = mergeProps<navigationMenuProps<T>[]>(
{
get gutter() {
return props.withArrow ? props.gutter : 6;
},
withArrow: false,
flip: false,
},
props,
);
const [local, rest] = splitProps(merge as navigationMenuProps, [
"class",
"children",
"withArrow",
]);
return (
<NavigationMenuPrimitive
class={cn("flex w-max items-center justify-center gap-x-1", local.class)}
{...rest}
>
{local.children}
<NavigationMenuPrimitive.Viewport
class={cn(
"pointer-events-none z-50 overflow-x-clip overflow-y-visible rounded-md border bg-popover text-popover-foreground shadow",
"h-[--kb-navigation-menu-viewport-height] w-[--kb-navigation-menu-viewport-width] transition-[width,height] duration-300",
"origin-[--kb-menu-content-transform-origin]",
"data-[expanded]:(animate-duration-300 animate-in fade-in zoom-in-95)",
"data-[closed]:(animate-duration-300 animate-out fade-out zoom-out-95)",
)}
>
<Show when={local.withArrow}>
<NavigationMenuPrimitive.Arrow class="transition-transform duration-300" />
</Show>
</NavigationMenuPrimitive.Viewport>
</NavigationMenuPrimitive>
);
};
type navigationMenuTriggerProps<T extends ValidComponent = "button"> =
ParentProps<
NavigationMenuTriggerProps<T> &
withArrow & {
class?: string;
}
>;
export const NavigationMenuTrigger = <T extends ValidComponent = "button">(
props: PolymorphicProps<T, navigationMenuTriggerProps<T>>,
) => {
const merge = mergeProps<navigationMenuTriggerProps<T>[]>(
{
get withArrow() {
return props.as === undefined ? true : props.withArrow;
},
},
props,
);
const [local, rest] = splitProps(merge as navigationMenuTriggerProps, [
"class",
"children",
"withArrow",
]);
return (
<NavigationMenuPrimitive.Trigger
class={cn(
"inline-flex w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium outline-none transition-colors duration-300 hover:(bg-accent text-accent-foreground) disabled:(pointer-events-none opacity-50)",
local.class,
)}
{...rest}
>
{local.children}
<Show when={local.withArrow}>
<NavigationMenuPrimitive.Icon
class="ml-1 size-3 transition-transform duration-300 data-[expanded]:rotate-180"
as="svg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m6 9l6 6l6-6"
/>
</NavigationMenuPrimitive.Icon>
</Show>
</NavigationMenuPrimitive.Trigger>
);
};
type navigationMenuContentProps<T extends ValidComponent = "ul"> = ParentProps<
NavigationMenuContentProps<T> & {
class?: string;
}
>;
export const NavigationMenuContent = <T extends ValidComponent = "ul">(
props: PolymorphicProps<T, navigationMenuContentProps<T>>,
) => {
const [local, rest] = splitProps(props as navigationMenuContentProps, [
"class",
"children",
]);
return (
<NavigationMenuPrimitive.Portal>
<NavigationMenuPrimitive.Content
class={cn(
"absolute left-0 top-0 p-4 outline-none",
"data-[motion^=from-]:(animate-duration-300 animate-in fade-in) data-[motion=from-end]:slide-in-r-52 data-[motion=from-start]:slide-in-l-52",
"data-[motion^=to-]:(animate-duration-300 animate-out fade-out) data-[motion=to-end]:slide-out-r-52 data-[motion=to-start]:slide-out-l-52",
local.class,
)}
{...rest}
>
{local.children}
</NavigationMenuPrimitive.Content>
</NavigationMenuPrimitive.Portal>
);
};
Usage
import {
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuTrigger,
} from "@/components/ui/navigation-menu"
<NavigationMenu>
<NavigationMenuItem>
<NavigationMenuTrigger>Item One</NavigationMenuTrigger>
<NavigationMenuContent>
<NavigationMenuLink>Link</NavigationMenuLink>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenu>