Navigation Menu
A collection of links for navigating websites.
Installation
Install the following dependencies:
pnpm add @radix-ui/react-navigation-menu
Copy and paste the following code into your project.
import Image from "next/image";
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu";
import type { VariantProps } from "tailwind-variants";
import { ChevronDownIcon } from "lucide-react";
import { tv, cn } from "~/utils/tailwind";
import _Link from "~/components/ui/_Link";
function NavigationMenu({
className,
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root>) {
return (
<NavigationMenuPrimitive.Root
className={cn(
"relative z-10 flex max-w-max flex-1 items-center justify-center",
className,
)}
{...props}
>
{children}
<NavigationMenuViewport
className={cn(
"data-[state=open]:fade-in-0",
"data-[state=closed]:fade-out-0",
)}
/>
</NavigationMenuPrimitive.Root>
);
}
function NavigationMenuList({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
return (
<NavigationMenuPrimitive.List
className={cn(
"group flex flex-1 list-none items-center justify-center gap-x-2",
className,
)}
{...props}
/>
);
}
const NavigationMenuItem = NavigationMenuPrimitive.Item;
const navigationMenuTriggerStyle = tv({
base: [
"group inline-flex h-9 w-max items-center justify-center gap-x-1.5 rounded-2 px-2 text-3.5 font-medium transition-colors",
"hover:bg-accent hover:text-accent-foreground",
"focus:bg-accent focus:text-accent-foreground focus:outline-none",
"disabled:pointer-events-none disabled:opacity-50",
"data-[active]:bg-accent/50 data-[state=open]:bg-accent/50",
],
});
function NavigationMenuTrigger({
className,
children,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
return (
<NavigationMenuPrimitive.Trigger
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDownIcon
aria-hidden="true"
className="size-3.5 transition-transform group-data-[state=open]:rotate-180"
/>
</NavigationMenuPrimitive.Trigger>
);
}
function NavigationMenuContent({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
return (
<NavigationMenuPrimitive.Content
className={cn(
"left-0 top-0 w-full",
"md:absolute md:w-auto",
"data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in",
"data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out",
"data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-start]:slide-out-to-left-52",
"data-[motion=from-end]:slide-in-from-right-52 data-[motion=to-end]:slide-out-to-right-52",
className,
)}
{...props}
/>
);
}
const NavigationMenuLink = NavigationMenuPrimitive.Link;
function NavigationMenuViewport({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
return (
<div className="absolute left-0 top-full flex justify-center">
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-3 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-3 border bg-popover text-popover-foreground",
"md:w-[var(--radix-navigation-menu-viewport-width)]",
"data-[state=open]:animate-scale-in data-[state=open]:zoom-in-90",
"data-[state=closed]:animate-scale-out data-[state=closed]:zoom-out-95",
className,
)}
{...props}
/>
</div>
);
}
function NavigationMenuIndicator({
className,
...props
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
return (
<NavigationMenuPrimitive.Indicator
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
"data-[state=visible]:animate-in data-[state=visible]:fade-in",
"data-[state=hidden]:animate-out data-[state=hidden]:fade-out",
className,
)}
{...props}
>
<div className="relative top-[60%] size-2 rotate-45 rounded-tl-0.5 bg-border" />
</NavigationMenuPrimitive.Indicator>
);
}
const navigationMenuDropdownListVariants = tv({
base: "grid w-80 gap-x-3 gap-y-1 p-4 lg:w-128",
variants: {
variant: {
default: "lg:grid-cols-2",
card: "lg:grid-cols-[.85fr_1fr]",
},
},
defaultVariants: {
variant: "default",
},
});
interface NavigationMenuDropdownListProps
extends VariantProps<typeof navigationMenuDropdownListVariants>,
React.ComponentProps<"div"> {}
function NavigationMenuDropdownList({
className,
variant,
...props
}: NavigationMenuDropdownListProps) {
return (
<div
className={cn(navigationMenuDropdownListVariants({ variant, className }))}
{...props}
/>
);
}
const navigationMenuDropdownItemVariants = tv({
base: "flex select-none flex-col rounded-2 no-underline outline-none",
variants: {
variant: {
default: cn(
"block p-3 transition-colors",
"hover:bg-accent hover:text-accent-foreground",
"focus:bg-accent focus:text-accent-foreground",
),
card: "row-span-3 size-full flex-col justify-end bg-gradient-to-b from-muted/50 to-muted p-6",
},
},
defaultVariants: {
variant: "default",
},
});
type NavigationMenuDropdownItemProps = Omit<
VariantProps<typeof navigationMenuDropdownItemVariants>,
"variant"
> & {
title: string;
description: string;
href: string;
} & (
| {
variant: "card";
cardImgSrc: string;
cardImgAlt: string;
}
| { variant: "default"; cardImgSrc?: never; cardImgAlt?: never }
) &
(React.ComponentProps<typeof _Link> | React.ComponentProps<"a">);
function NavigationMenuDropdownItem({
title,
description,
href,
cardImgSrc,
cardImgAlt,
variant,
className,
...props
}: NavigationMenuDropdownItemProps) {
const listItemContent =
variant === "card" ? (
<>
<div className="relative mb-4 flex-grow">
<Image
src={cardImgSrc}
alt={cardImgAlt}
layout="fill"
objectFit="cover"
className="rounded-3 bg-cover"
/>
</div>
<h4 className="mb-1 text-4 font-medium">{title}</h4>
<p className="text-3.5 leading-6 text-muted-foreground">
{description}
</p>
</>
) : (
<>
<p className="mb-1 text-3.5 font-medium text-foreground">{title}</p>
<p className="line-clamp-2 text-3.5 leading-6 text-muted-foreground">
{description}
</p>
</>
);
return (
<NavigationMenuLink
className={navigationMenuDropdownItemVariants({ variant, className })}
asChild
>
<_Link href={href} {...props}>
{listItemContent}
</_Link>
</NavigationMenuLink>
);
}
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
NavigationMenuDropdownList,
NavigationMenuDropdownItem,
};
Update the import paths to match your project setup.
Usage
"use client";
import Link from "next/link";
import {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuContent,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuDropdownList,
NavigationMenuDropdownItem,
} from "~/components/ui/NavigationMenu";
<NavigationMenu className="hidden md:block">
<NavigationMenuList>
<NavigationMenuItem>
<NavigationMenuTrigger>Getting Started</NavigationMenuTrigger>
<NavigationMenuContent className="font-inter">
<NavigationMenuDropdownList variant="card">
<NavigationMenuDropdownItem
variant="card"
title="Quickstart"
href="/docs/getting-started/quickstart"
isRoute
cardImg="https://images.unsplash.com/photo-1513569771920-c9e1d31714af?q=80&w=3387&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
cardImgAlt="dunes texture"
>
Follow a quick and easy installation to get you building ASAP.
</NavigationMenuDropdownItem>
<NavigationMenuDropdownItem
href="/docs/getting-started/introduction"
title="Introduction"
isRoute
>
Read about quadratic/ui's vision and its core principles.
</NavigationMenuDropdownItem>
<NavigationMenuDropdownItem
href="https://www.figma.com/community/file/1351315753275186770/quadratic-ui"
title="Figma Design File"
>
Design with quadratic/ui's components in Figma.
</NavigationMenuDropdownItem>
<NavigationMenuDropdownItem
href="/docs/getting-started/credits"
title="Credits"
isRoute
>
The inspiration that made quadratic/ui possible.
</NavigationMenuDropdownItem>
</NavigationMenuDropdownList>
</NavigationMenuContent>
</NavigationMenuItem>
<NavigationMenuItem>
<Link
href="/docs/components/primitives/accordion"
legacyBehavior
passHref
>
<NavigationMenuLink className={navigationMenuTriggerStyle()}>
Components
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
<NavigationMenuItem>
<NavigationMenuTrigger>Resources</NavigationMenuTrigger>
<NavigationMenuContent className="font-inter">
<NavigationMenuDropdownList variant="card">
<NavigationMenuDropdownItem
variant="card"
title="shadcn/ui"
href="https://ui.shadcn.com/"
cardImg="https://images.unsplash.com/photo-1608447714925-599deeb5a682?q=80&w=3272&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D"
cardImgAlt="black pattern"
>
The component library quadratic/ui is inspired by and built on
top of.
</NavigationMenuDropdownItem>
<NavigationMenuDropdownItem
title="Radix UI"
href="https://www.radix-ui.com/"
>
The component library shadcn/ui was built on top of.
</NavigationMenuDropdownItem>
<NavigationMenuDropdownItem
href="https://tailwindcss.com/"
title="Tailwind CSS"
>
The easiest and fastest way to write CSS. (in my opinion)
</NavigationMenuDropdownItem>
<NavigationMenuDropdownItem
title="Next.js"
href="https://nextjs.org/"
>
A React framework that everyone should use. (also in my opinion)
</NavigationMenuDropdownItem>
</NavigationMenuDropdownList>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>