component

Navigation

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.

Primary

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>

Neutral

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>

Leading Icons

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>

Trailing Stamps

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>

Leading and Trailing Combined

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>

Size with Trailing Badge

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>

Compact: Icon + Label

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>

Compact: Icon Only

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>

Toggling Compact Mode

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>

Per-Item Tooltips

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>

Props

props · 8 total
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
props · 8 total
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.