A flexible navigation component for creating horizontal or vertical navigation menus. Supports active states, disabled items, and leading/trailing slots where you can place any element — icons, badges, stamps, etc. Badges placed in trailing automatically scale with the navigation size.
The primary navigation style with prominent active state indicators and full-width styling.
---
import Navigation from "../Navigation.astro";
import NavigationItem from "../NavigationItem.astro";
import Badge from "@pindoba/astro-badge";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "md",
direction: "column",
justify: "center",
flexWrap: "wrap",
})}
>
<Navigation>
<NavigationItem label="Home" href="#" isActive />
<NavigationItem label="Products" href="#">
<Badge slot="trailing" size="sm" emphasis="primary">New</Badge>
</NavigationItem>
<NavigationItem label="Special" href="#">
<Badge slot="trailing" size="sm" emphasis="secondary" feedback="success">
🔥 Hot
</Badge>
</NavigationItem>
<NavigationItem label="Disabled" href="#" disabled />
<NavigationItem label="logout" href="#" />
</Navigation>
<Navigation bordered>
<NavigationItem label="Home" href="#" bordered isActive />
<NavigationItem label="Products" href="#" bordered>
<Badge slot="trailing" size="sm" emphasis="primary">New</Badge>
</NavigationItem>
<NavigationItem label="Special" href="#" bordered>
<Badge slot="trailing" size="sm" emphasis="secondary" feedback="success">
🔥 Hot
</Badge>
</NavigationItem>
<NavigationItem label="Disabled" href="#" bordered disabled />
<NavigationItem label="logout" href="#" bordered />
</Navigation>
<Navigation direction="vertical">
<NavigationItem label="Home" href="#" direction="vertical" isActive />
<NavigationItem label="Products" href="#" direction="vertical">
<Badge slot="trailing" size="sm" emphasis="primary">New</Badge>
</NavigationItem>
<NavigationItem label="Special" href="#" direction="vertical">
<Badge slot="trailing" size="sm" emphasis="secondary" feedback="success">
🔥 Hot
</Badge>
</NavigationItem>
<NavigationItem label="Disabled" href="#" direction="vertical" disabled />
<NavigationItem label="logout" href="#" direction="vertical" />
</Navigation>
</div>The neutral navigation style with subtle styling, perfect for secondary navigation or sidebar menus.
---
import Navigation from "../Navigation.astro";
import NavigationItem from "../NavigationItem.astro";
import Badge from "@pindoba/astro-badge";
import { stack } from "@pindoba/styled-system/patterns";
---
<div
class={stack({
gap: "md",
direction: "column",
justify: "center",
flexWrap: "wrap",
})}
>
<Navigation emphasis="neutral">
<NavigationItem label="Home" href="#" emphasis="neutral" isActive />
<NavigationItem label="Products" href="#" emphasis="neutral">
<Badge slot="trailing" size="sm" emphasis="primary">New</Badge>
</NavigationItem>
<NavigationItem label="Special" href="#" emphasis="neutral">
<Badge slot="trailing" size="sm" emphasis="secondary" feedback="success">
🔥 Hot
</Badge>
</NavigationItem>
<NavigationItem label="Disabled" href="#" emphasis="neutral" disabled />
<NavigationItem label="logout" href="#" emphasis="neutral" />
</Navigation>
<Navigation emphasis="neutral" bordered>
<NavigationItem
label="Home"
href="#"
emphasis="neutral"
bordered
isActive
/>
<NavigationItem label="Products" href="#" emphasis="neutral" bordered>
<Badge slot="trailing" size="sm" emphasis="primary">New</Badge>
</NavigationItem>
<NavigationItem label="Special" href="#" emphasis="neutral" bordered>
<Badge slot="trailing" size="sm" emphasis="secondary" feedback="success">
🔥 Hot
</Badge>
</NavigationItem>
<NavigationItem
label="Disabled"
href="#"
emphasis="neutral"
bordered
disabled
/>
<NavigationItem label="logout" href="#" emphasis="neutral" bordered />
</Navigation>
<Navigation emphasis="neutral" direction="vertical">
<NavigationItem
label="Home"
href="#"
emphasis="neutral"
direction="vertical"
/>
<NavigationItem
label="Products"
href="#"
emphasis="neutral"
direction="vertical"
>
<Badge slot="trailing" size="sm" emphasis="primary">New</Badge>
</NavigationItem>
<NavigationItem
label="Special"
href="#"
emphasis="neutral"
direction="vertical"
isActive
>
<Badge slot="trailing" size="sm" emphasis="secondary" feedback="success">
🔥 Hot
</Badge>
</NavigationItem>
<NavigationItem
label="Disabled"
href="#"
emphasis="neutral"
direction="vertical"
disabled
/>
<NavigationItem
label="logout"
href="#"
emphasis="neutral"
direction="vertical"
/>
</Navigation>
</div>Place icons in the leading slot for a sidebar-style navigation. Any component works — lucide icons, custom SVGs, anything.
---
import Navigation from "../Navigation.astro";
import NavigationItem from "../NavigationItem.astro";
import Stamp from "@pindoba/astro-stamp";
import { Icon } from "astro-icon/components";
import { stack } from "@pindoba/styled-system/patterns";
const items = [
{ label: "Home", icon: "lucide:house" },
{ label: "Inbox", icon: "lucide:inbox" },
{ label: "Drafts", icon: "lucide:file-text" },
{ label: "Sent", icon: "lucide:send" },
{ label: "Logout", icon: "lucide:log-out" },
];
---
<div class={stack({ gap: "md", direction: "column" })}>
<Navigation direction="vertical">
{
items.map((item) => (
<NavigationItem
label={item.label}
href="#"
direction="vertical"
isActive={item.label === "Inbox"}
>
<Stamp slot="leading" emphasis="ghost">
<Icon name={item.icon} />
</Stamp>
</NavigationItem>
))
}
</Navigation>
</div>Use <Stamp> in the trailing slot for decorative indicators.
---
import Navigation from "../Navigation.astro";
import NavigationItem from "../NavigationItem.astro";
import Stamp from "@pindoba/astro-stamp";
import { Icon } from "astro-icon/components";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "md", direction: "column" })}>
<Navigation>
<NavigationItem label="Featured" href="#" isActive>
<Stamp slot="trailing" size="xs" emphasis="adaptive" shape="circle">
<Icon name="lucide:star" />
</Stamp>
</NavigationItem>
<NavigationItem label="Verified" href="#">
<Stamp
slot="trailing"
size="xs"
emphasis="secondary"
feedback="success"
shape="circle"
>
<Icon name="lucide:check" />
</Stamp>
</NavigationItem>
<NavigationItem label="Promoted" href="#">
<Stamp
slot="trailing"
size="xs"
emphasis="secondary"
feedback="warning"
shape="circle"
>
<Icon name="lucide:zap" />
</Stamp>
</NavigationItem>
<NavigationItem label="Subscribed" href="#">
<Stamp slot="trailing" size="xs" emphasis="secondary" feedback="primary">
<Icon name="lucide:bell" />
</Stamp>
</NavigationItem>
<NavigationItem label="Protected" href="#">
<Stamp slot="trailing" size="xs" emphasis="secondary" feedback="neutral">
<Icon name="lucide:shield" />
</Stamp>
</NavigationItem>
</Navigation>
</div>Mix icons, badges, stamps, chevrons — leading and trailing are independent and accept any element.
---
import Navigation from "../Navigation.astro";
import NavigationItem from "../NavigationItem.astro";
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import { Icon } from "astro-icon/components";
import { stack } from "@pindoba/styled-system/patterns";
---
<div class={stack({ gap: "md", direction: "column" })}>
<Navigation direction="vertical">
<NavigationItem label="Home" href="#" direction="vertical">
<Stamp slot="leading" emphasis="ghost">
<Icon name="lucide:house" />
</Stamp>
<Stamp slot="trailing" emphasis="ghost">
<Icon name="lucide:chevron-right" />
</Stamp>
</NavigationItem>
<NavigationItem label="Inbox" href="#" direction="vertical" isActive>
<Stamp slot="leading" emphasis="ghost">
<Icon name="lucide:inbox" />
</Stamp>
<Badge slot="trailing" emphasis="primary">12</Badge>
</NavigationItem>
<NavigationItem label="Notifications" href="#" direction="vertical">
<Stamp slot="leading" emphasis="ghost">
<Icon name="lucide:bell" />
</Stamp>
<Badge slot="trailing" emphasis="secondary" feedback="success">
New
</Badge>
</NavigationItem>
<NavigationItem label="Settings" href="#" direction="vertical">
<Stamp slot="leading" emphasis="ghost">
<Icon name="lucide:settings" />
</Stamp>
<Stamp slot="trailing" emphasis="primary" shape="circle">
<Icon name="lucide:star" />
</Stamp>
</NavigationItem>
</Navigation>
</div>Badges composed in the trailing slot scale with the navigation size — each size maps to a smaller badge font-size token.
---
import Navigation from "../Navigation.astro";
import NavigationItem from "../NavigationItem.astro";
import Badge from "@pindoba/astro-badge";
import Stamp from "@pindoba/astro-stamp";
import { Icon } from "astro-icon/components";
import { stack } from "@pindoba/styled-system/patterns";
const sizes = ["xs", "sm", "md", "lg"] as const;
---
<div class={stack({ gap: "xl", direction: "column" })}>
{
sizes.map((size) => (
<div>
<h3>{size}</h3>
<Navigation size={size}>
<NavigationItem label="Home" href="#" size={size}>
<Stamp slot="trailing" emphasis="primary" shape="circle">
<Icon name="lucide:star" />
</Stamp>
</NavigationItem>
<NavigationItem label="Inbox" href="#" size={size} isActive>
<Badge slot="trailing" emphasis="primary" feedback="primary">
12
</Badge>
</NavigationItem>
<NavigationItem label="Drafts" href="#" size={size}>
<Badge slot="trailing" emphasis="secondary" feedback="neutral">
3
</Badge>
</NavigationItem>
<NavigationItem label="Sent" href="#" size={size}>
<Stamp slot="trailing" emphasis="secondary" feedback="success">
<Icon name="lucide:check" />
</Stamp>
</NavigationItem>
</Navigation>
</div>
))
}
</div>Set compact="stack" to shrink each item to icon-over-label with ellipsis — useful for narrow rails where labels still need to read. The styled Tooltip is wired automatically from item.label, so hovering surfaces the full text without falling back to the native title tooltip.
---
import Navigation from "../Navigation.astro";
import NavigationItem from "../NavigationItem.astro";
import Stamp from "@pindoba/astro-stamp";
import { Icon } from "astro-icon/components";
import { stack } from "@pindoba/styled-system/patterns";
const items = [
{ label: "Home", icon: "lucide:house" },
{ label: "Inbox", icon: "lucide:inbox" },
{ label: "Drafts", icon: "lucide:file-text" },
{ label: "Sent", icon: "lucide:send" },
{ label: "Logout", icon: "lucide:log-out" },
];
---
<div class={stack({ gap: "md", direction: "column" })}>
<Navigation direction="vertical" compact="stack">
{
items.map((item) => (
<NavigationItem
label={item.label}
href="#"
direction="vertical"
compact="stack"
isActive={item.label === "Inbox"}
>
<Stamp slot="leading" emphasis="ghost">
<Icon name={item.icon} />
</Stamp>
</NavigationItem>
))
}
</Navigation>
</div>Set compact="icon" for a pure icon rail. Labels stay in the accessibility tree (visually hidden) and the auto-tooltip carries the name on hover, with placement derived from direction.
---
import Navigation from "../Navigation.astro";
import NavigationItem from "../NavigationItem.astro";
import Stamp from "@pindoba/astro-stamp";
import { Icon } from "astro-icon/components";
import { stack } from "@pindoba/styled-system/patterns";
const items = [
{ label: "Home", icon: "lucide:house" },
{ label: "Inbox", icon: "lucide:inbox" },
{ label: "Drafts", icon: "lucide:file-text" },
{ label: "Sent", icon: "lucide:send" },
{ label: "Logout", icon: "lucide:log-out" },
];
---
<div class={stack({ gap: "md", direction: "column" })}>
<Navigation direction="vertical" compact="icon">
{
items.map((item) => (
<NavigationItem
label={item.label}
href="#"
direction="vertical"
compact="icon"
isActive={item.label === "Inbox"}
>
<Stamp slot="leading" emphasis="ghost">
<Icon name={item.icon} />
</Stamp>
</NavigationItem>
))
}
</Navigation>
</div>Consumers drive the compact prop from layout state. Cycle between none, stack, and icon to see how the same items adapt.
<script lang="ts">
import Navigation from "../Navigation.svelte";
import Stamp from "@pindoba/svelte-stamp";
import { stack } from "@pindoba/styled-system/patterns";
import LucideHouse from "lucide-svelte/icons/house";
import LucideInbox from "lucide-svelte/icons/inbox";
import LucideFileText from "lucide-svelte/icons/file-text";
import LucideSend from "lucide-svelte/icons/send";
import LucideLogOut from "lucide-svelte/icons/log-out";
type CompactMode = "none" | "stack" | "icon";
const modes: CompactMode[] = ["none", "stack", "icon"];
let compact = $state<CompactMode>("none");
function cycle() {
const i = modes.indexOf(compact);
compact = modes[(i + 1) % modes.length] as CompactMode;
}
</script>
{#snippet home()}
<Stamp emphasis="ghost"><LucideHouse /></Stamp>
{/snippet}
{#snippet inbox()}
<Stamp emphasis="ghost"><LucideInbox /></Stamp>
{/snippet}
{#snippet drafts()}
<Stamp emphasis="ghost"><LucideFileText /></Stamp>
{/snippet}
{#snippet sent()}
<Stamp emphasis="ghost"><LucideSend /></Stamp>
{/snippet}
{#snippet logout()}
<Stamp emphasis="ghost"><LucideLogOut /></Stamp>
{/snippet}
<div class={stack({ gap: "md", direction: "column" })}>
<button type="button" onclick={cycle}>compact: {compact}</button>
<Navigation
items={[
{ label: "Home", href: "#", leading: home },
{ label: "Inbox", href: "#", leading: inbox },
{ label: "Drafts", href: "#", leading: drafts },
{ label: "Sent", href: "#", leading: sent },
{ label: "Logout", href: "#", leading: logout },
]}
activeItem="Inbox"
direction="vertical"
{compact}
/>
</div>Pass tooltip on any item to override the auto-derived tooltip. Accepts a string or { content, placement }. Items without tooltip show no tooltip outside compact mode.
---
import Navigation from "../Navigation.astro";
import NavigationItem from "../NavigationItem.astro";
import Stamp from "@pindoba/astro-stamp";
import { Icon } from "astro-icon/components";
import { stack } from "@pindoba/styled-system/patterns";
const items = [
{ label: "Home", icon: "lucide:house", tooltip: "Go home" },
{
label: "Inbox",
icon: "lucide:inbox",
tooltip: { content: "12 unread", placement: "right" as const },
},
{ label: "Drafts", icon: "lucide:file-text", tooltip: "Drafts (3)" },
{ label: "Sent", icon: "lucide:send" },
];
---
<div class={stack({ gap: "md", direction: "column" })}>
<Navigation direction="vertical">
{
items.map((item) => (
<NavigationItem
label={item.label}
href="#"
direction="vertical"
tooltip={item.tooltip}
isActive={item.label === "Inbox"}
>
<Stamp slot="leading" emphasis="ghost">
<Icon name={item.icon} />
</Stamp>
</NavigationItem>
))
}
</Navigation>
</div>| prop | type | default | req | description |
|---|---|---|---|---|
| items | NavigationItem[] | - | Array of navigation items to display | |
| emphasis | string | undefined | Visual emphasis level for the navigation | |
| direction | string | undefined | Layout direction for the navigation | |
| activeItem | string | undefined | ID or label of the currently active navigation item | |
| bordered | boolean | false | Whether to show borders around the navigation | |
| compact | "none""stack""icon" | "none" | Collapse the items to a narrow rail. `stack` shows icon over label with ellipsis; `icon` hides the label visually (kept for screen readers) and auto-wires the styled Tooltip from `item.label`. | |
| passThrough | { root?: { style?: SystemStyleObject; props?: HTMLAttributes<HTMLElement> }; itemsContainer?: { style?: SystemStyleObject; props?: HTMLAttributes<HTMLElement> }; item?: { style?: SystemStyleObject; props?: HTMLAttributes<HTMLElement> }; itemActive?: { style?: SystemStyleObject; props?: HTMLAttributes<HTMLElement> }; itemDisabled?: { style?: SystemStyleObject; props?: HTMLAttributes<HTMLElement> } } | undefined | Custom styling and props for navigation elements | |
| ...rest | HTMLAttributes<HTMLElement> | - | Standard HTML nav element attributes |
| prop | type | default | req | description |
|---|---|---|---|---|
| label | string | - | Display text for the navigation item | |
| id | string | undefined | Unique identifier for the navigation item | |
| href | string | undefined | URL for the navigation link | |
| onClick | () => void | undefined | Click handler function for the navigation item | |
| leading | SnippetReactNode | undefined | Element rendered before the label (icon, badge, stamp, etc.). Svelte: Snippet. React: ReactNode. Astro: use <NavigationItem slot="leading">. | |
| trailing | SnippetReactNode | undefined | Element rendered after the label. Badges placed here auto-size to the navigation size variant. | |
| disabled | boolean | false | Whether the navigation item is disabled | |
| tooltip | string{ content: string; placement?: Placement } | undefined | Per-item styled tooltip. String or `{ content, placement }`. When `compact` is active, the tooltip is auto-derived from `label` unless overridden here. |