<script lang="ts">
import * as NavigationMenu from "$lib/components/ui/navigation-menu/index.js";
import { cn } from "$lib/utils.js";
import { navigationMenuTriggerStyle } from "$lib/components/ui/navigation-menu/navigation-menu-trigger.svelte";
import type { HTMLAttributes } from "svelte/elements";
import CircleHelpIcon from "@lucide/svelte/icons/circle-help";
import CircleIcon from "@lucide/svelte/icons/circle";
import CircleCheckIcon from "@lucide/svelte/icons/circle-check";
import { IsMobile } from "$lib/hooks/is-mobile.svelte.js";
const isMobile = new IsMobile();
const components: { title: string; href: string; description: string }[] = [
{
title: "Alert Dialog",
href: "/docs/components/alert-dialog",
description:
"A modal dialog that interrupts the user with important content and expects a response."
},
{
title: "Hover Card",
href: "/docs/components/hover-card",
description:
"For sighted users to preview content available behind a link."
},
{
title: "Progress",
href: "/docs/components/progress",
description:
"Displays an indicator showing the completion progress of a task, typically displayed as a progress bar."
},
{
title: "Scroll-area",
href: "/docs/components/scroll-area",
description: "Visually or semantically separates content."
},
{
title: "Tabs",
href: "/docs/components/tabs",
description:
"A set of layered sections of content—known as tab panels—that are displayed one at a time."
},
{
title: "Tooltip",
href: "/docs/components/tooltip",
description:
"A popup that displays information related to an element when the element receives keyboard focus or the mouse hovers over it."
}
];
type ListItemProps = HTMLAttributes<HTMLAnchorElement> & {
title: string;
href: string;
content: string;
};
</script>
{#snippet ListItem({
title,
content,
href,
class: className,
...restProps
}: ListItemProps)}
<li>
<NavigationMenu.Link>
{#snippet child()}
<a
{href}
class={cn(
"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground block space-y-1 rounded-md p-3 leading-none no-underline transition-colors outline-none select-none",
className
)}
{...restProps}
>
<div class="text-sm leading-none font-medium">{title}</div>
<p class="text-muted-foreground line-clamp-2 text-sm leading-snug">
{content}
</p>
</a>
{/snippet}
</NavigationMenu.Link>
</li>
{/snippet}
<NavigationMenu.Root viewport={isMobile.current}>
<NavigationMenu.List class="flex-wrap">
<NavigationMenu.Item>
<NavigationMenu.Trigger>Home</NavigationMenu.Trigger>
<NavigationMenu.Content>
<ul
class="grid gap-2 p-2 md:w-[400px] lg:w-[500px] lg:grid-cols-[.75fr_1fr]"
>
<li class="row-span-3">
<NavigationMenu.Link
class="from-muted/50 to-muted flex h-full w-full flex-col justify-end rounded-md bg-linear-to-b p-4 no-underline outline-hidden select-none focus:shadow-md md:p-6"
>
{#snippet child({ props })}
<a {...props} href="/">
<div class="mt-4 mb-2 text-lg font-medium">shadcn-svelte</div>
<p class="text-muted-foreground text-sm leading-tight">
Beautifully designed components built with Tailwind CSS.
</p>
</a>
{/snippet}
</NavigationMenu.Link>
</li>
{@render ListItem({
href: "/docs",
title: "Introduction",
content:
"Re-usable components built using Bits UI and Tailwind CSS."
})}
{@render ListItem({
href: "/docs/installation",
title: "Installation",
content: "How to install dependencies and structure your app."
})}
{@render ListItem({
href: "/docs/components",
title: "Components",
content: "All documented registry components in one place."
})}
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Components</NavigationMenu.Trigger>
<NavigationMenu.Content>
<ul
class="grid w-[300px] gap-2 p-2 sm:w-[400px] md:w-[500px] md:grid-cols-2 lg:w-[600px]"
>
{#each components as component, i (i)}
{@render ListItem({
href: component.href,
title: component.title,
content: component.description
})}
{/each}
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Link>
{#snippet child()}
<a href="/docs" class={navigationMenuTriggerStyle()}>Docs</a>
{/snippet}
</NavigationMenu.Link>
</NavigationMenu.Item>
<NavigationMenu.Item class="hidden md:block">
<NavigationMenu.Trigger>List</NavigationMenu.Trigger>
<NavigationMenu.Content>
<ul class="grid w-[300px] gap-4 p-2">
<li>
<NavigationMenu.Link href="##">
<div class="font-medium">Components</div>
<div class="text-muted-foreground">
Browse all components in the library.
</div>
</NavigationMenu.Link>
<NavigationMenu.Link href="##">
<div class="font-medium">Documentation</div>
<div class="text-muted-foreground">
Learn how to use the library.
</div>
</NavigationMenu.Link>
<NavigationMenu.Link href="##">
<div class="font-medium">Blog</div>
<div class="text-muted-foreground">
Read our latest blog posts.
</div>
</NavigationMenu.Link>
</li>
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item class="hidden md:block">
<NavigationMenu.Trigger>Simple</NavigationMenu.Trigger>
<NavigationMenu.Content>
<ul class="grid w-[200px] gap-4 p-2">
<li>
<NavigationMenu.Link href="##">Components</NavigationMenu.Link>
<NavigationMenu.Link href="##">Documentation</NavigationMenu.Link>
<NavigationMenu.Link href="##">Blocks</NavigationMenu.Link>
</li>
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item class="hidden md:block">
<NavigationMenu.Trigger>With Icon</NavigationMenu.Trigger>
<NavigationMenu.Content>
<ul class="grid w-[200px] gap-4 p-2">
<li>
<NavigationMenu.Link href="##" class="flex-row items-center gap-2">
<CircleHelpIcon />
Backlog
</NavigationMenu.Link>
<NavigationMenu.Link href="##" class="flex-row items-center gap-2">
<CircleIcon />
To Do
</NavigationMenu.Link>
<NavigationMenu.Link href="##" class="flex-row items-center gap-2">
<CircleCheckIcon />
Done
</NavigationMenu.Link>
</li>
</ul>
</NavigationMenu.Content>
</NavigationMenu.Item>
</NavigationMenu.List>
</NavigationMenu.Root> Installation
pnpm dlx shadcn-svelte@latest add navigation-menu Install bits-ui:
pnpm add bits-ui -D Copy and paste the following code into your project.
import Root from './navigation-menu.svelte';
import Content from './navigation-menu-content.svelte';
import Indicator from './navigation-menu-indicator.svelte';
import Item from './navigation-menu-item.svelte';
import Link from './navigation-menu-link.svelte';
import List from './navigation-menu-list.svelte';
import Trigger from './navigation-menu-trigger.svelte';
import Viewport from './navigation-menu-viewport.svelte';
export {
Root,
Content,
Indicator,
Item,
Link,
List,
Trigger,
Viewport,
//
Root as NavigationMenuRoot,
Content as NavigationMenuContent,
Indicator as NavigationMenuIndicator,
Item as NavigationMenuItem,
Link as NavigationMenuLink,
List as NavigationMenuList,
Trigger as NavigationMenuTrigger,
Viewport as NavigationMenuViewport
};
<script lang="ts">
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: NavigationMenuPrimitive.ContentProps = $props();
</script>
<NavigationMenuPrimitive.Content
bind:ref
data-slot="navigation-menu-content"
class={cn(
'top-0 left-0 w-full p-2.5 pr-3 ease-[cubic-bezier(0.22,1,0.36,1)] data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:border-[#222225] group-data-[viewport=false]/navigation-menu:bg-[#09090b] group-data-[viewport=false]/navigation-menu:text-zinc-50 group-data-[viewport=false]/navigation-menu:shadow-none group-data-[viewport=false]/navigation-menu:duration-300 group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none md:absolute md:w-auto',
className
)}
{...restProps}
/>
<script lang="ts">
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: NavigationMenuPrimitive.IndicatorProps = $props();
</script>
<NavigationMenuPrimitive.Indicator
bind:ref
data-slot="navigation-menu-indicator"
class={cn(
'top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in',
className
)}
{...restProps}
>
<div
class="relative top-[60%] h-2 w-2 rotate-45 border-t border-l border-[#222225] bg-[#09090b] shadow-none"
></div>
</NavigationMenuPrimitive.Indicator>
<script lang="ts">
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: NavigationMenuPrimitive.ItemProps = $props();
</script>
<NavigationMenuPrimitive.Item
bind:ref
data-slot="navigation-menu-item"
class={cn('cn-navigation-menu-item relative isolation-auto', className)}
{...restProps}
/>
<script lang="ts">
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: NavigationMenuPrimitive.LinkProps = $props();
</script>
<NavigationMenuPrimitive.Link
bind:ref
data-slot="navigation-menu-link"
class={cn(
"flex items-center gap-1.5 border border-transparent p-3 font-mono text-xs uppercase tracking-[0.04em] text-zinc-400 outline-none transition-colors hover:border-[#b9d765]/50 hover:bg-[#18181b] hover:text-zinc-50 focus:border-[#b9d765]/50 focus:bg-[#18181b] focus:text-zinc-50 focus-visible:ring-2 focus-visible:ring-zinc-300/60 focus-visible:outline-1 data-[active=true]:border-[#b9d765]/50 data-[active=true]:bg-[#18181b] data-[active=true]:text-[#d0e891] data-[active=true]:focus:bg-[#18181b] data-[active=true]:hover:bg-[#18181b] in-data-[slot=navigation-menu-content]:rounded-none [&_svg:not([class*='size-'])]:size-3.5",
className
)}
{...restProps}
/>
<script lang="ts">
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: NavigationMenuPrimitive.ListProps = $props();
</script>
<NavigationMenuPrimitive.List
bind:ref
data-slot="navigation-menu-list"
class={cn('group flex flex-1 list-none items-center justify-center gap-1', className)}
{...restProps}
/>
<script lang="ts" module>
import { cn } from '$UTILS$.js';
import { tv } from 'tailwind-variants';
export const navigationMenuTriggerStyle = tv({
base: 'inline-flex h-9 w-max items-center justify-center rounded-full px-4.5 py-2.5 font-mono text-xs font-medium uppercase tracking-[0.04em] text-zinc-400 outline-none transition-colors hover:bg-zinc-50 hover:text-zinc-950 focus:bg-zinc-50 focus:text-zinc-950 focus-visible:ring-2 focus-visible:ring-zinc-300/60 focus-visible:outline-1 data-open:bg-zinc-50 data-open:text-zinc-950 data-open:hover:bg-zinc-50 data-open:focus:bg-zinc-50 data-popup-open:bg-zinc-50 data-popup-open:text-zinc-950 data-popup-open:hover:bg-zinc-50 disabled:pointer-events-none disabled:opacity-50 group/navigation-menu-trigger'
});
</script>
<script lang="ts">
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
import CaretDownIcon from 'phosphor-svelte/lib/CaretDown';
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: NavigationMenuPrimitive.TriggerProps = $props();
</script>
<NavigationMenuPrimitive.Trigger
bind:ref
data-slot="navigation-menu-trigger"
class={cn(navigationMenuTriggerStyle(), 'group', className)}
{...restProps}
>
{@render children?.()}
<CaretDownIcon
class="relative top-px ml-1 size-3 transition duration-300 group-data-open/navigation-menu-trigger:rotate-180 group-data-popup-open/navigation-menu-trigger:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
<script lang="ts">
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
let {
ref = $bindable(null),
class: className,
...restProps
}: NavigationMenuPrimitive.ViewportProps = $props();
</script>
<div class={cn('absolute start-0 top-full isolate z-50 flex justify-center')}>
<NavigationMenuPrimitive.Viewport
bind:ref
data-slot="navigation-menu-viewport"
class={cn(
'relative mt-1.5 h-[calc(var(--bits-navigation-menu-viewport-height)+1rem)] w-full origin-top-center overflow-hidden border border-[#222225] bg-[#09090b] text-zinc-50 shadow-none duration-100 data-open:animate-in data-closed:animate-out data-closed:zoom-out-90 data-open:zoom-in-90 md:w-[calc(var(--bits-navigation-menu-viewport-width)+1rem)]',
className
)}
{...restProps}
/>
</div>
<script lang="ts">
import { NavigationMenu as NavigationMenuPrimitive } from 'bits-ui';
import { cn } from '$UTILS$.js';
import NavigationMenuViewport from './navigation-menu-viewport.svelte';
let {
ref = $bindable(null),
class: className,
viewport = true,
children,
...restProps
}: NavigationMenuPrimitive.RootProps & {
viewport?: boolean;
} = $props();
</script>
<NavigationMenuPrimitive.Root
bind:ref
data-slot="navigation-menu"
data-viewport={viewport}
class={cn(
'group/navigation-menu relative flex max-w-max flex-1 items-center justify-center',
className
)}
{...restProps}
>
{@render children?.()}
{#if viewport}
<NavigationMenuViewport />
{/if}
</NavigationMenuPrimitive.Root>
Usage
<script lang="ts">
import * as NavigationMenu from '$lib/components/ui/navigation-menu/index.js';
</script> <NavigationMenu.Root>
<NavigationMenu.List>
<NavigationMenu.Item>
<NavigationMenu.Trigger>Item One</NavigationMenu.Trigger>
<NavigationMenu.Content>
<NavigationMenu.Link>Link</NavigationMenu.Link>
</NavigationMenu.Content>
</NavigationMenu.Item>
</NavigationMenu.List>
</NavigationMenu.Root>