Skip to content

@vibe-labs/design-components-menus

Navigation menu component tokens, styles, and TypeScript types for the Vibe Design System. For persistent, structural navigation (sidebars, top bars, multi-level). Distinct from dropdowns (triggered popups).

Installation

bash
npm install @vibe-labs/design-components-menus
css
@import "@vibe-labs/design-components-menus";
ts
import { MenuFlyoutAlignments } from "@vibe-labs/design-components-menus/types";
import type { MenuStyleProps, MenuItemStyleProps, MenuFlyoutStyleProps } from "@vibe-labs/design-components-menus/types";

Contents

Tokens

CSS custom properties defined in @layer vibe.tokens (file: menu.css).

Item

TokenDefaultDescription
--menu-item-height2.25remMinimum item height
--menu-item-px--space-3Horizontal padding
--menu-item-radius--radius-mdBorder radius
--menu-item-font-size--text-smFont size
--menu-item-font-weight400Default weight
--menu-item-color--text-secondaryText color
--menu-item-hover-bg--surface-hover-overlayHover background
--menu-item-active-bg--surface-active-overlayActive/current background
--menu-item-active-color--text-primaryActive text color
--menu-item-active-font-weight--font-semiboldActive weight
--menu-item-active-indicator--color-accentActive indicator bar color

Icons

TokenDefault
--menu-icon-size1.125rem
--menu-icon-color--text-muted
--menu-icon-active-color--color-accent

Groups & Dividers

TokenDefault
--menu-group-label-font-size--text-xs
--menu-group-label-color--text-muted
--menu-group-label-font-weight--font-semibold
--menu-divider-color--border-subtle

Nesting

TokenDefaultDescription
--menu-indent--space-4Nested menu left padding
--menu-nested-indicator-width1pxVertical tree line width
--menu-nested-indicator-color--border-subtleTree line color
TokenDefault
--menu-submenu-bg--surface-elevated
--menu-submenu-border-color--border-subtle
--menu-submenu-radius--radius-lg
--menu-submenu-shadow--shadow-lg
--menu-submenu-z--z-dropdown
--menu-collapse-speed--duration-normal

Generated Styles

Component classes generated into @layer vibe.components (file: menu.g.css).

  • .menu — vertical flex column, resets list styles
  • data-horizontal — switches to row layout
  • Full-width interactive row with hover/focus/disabled states
  • aria-current sets active background, color, weight, and indicator bar
  • Vertical layout: left-side accent bar indicator (3px, 50% height)
  • Horizontal layout: bottom bar indicator (2px, 50% width)
  • Inset focus ring via box-shadow

Item Slots

ClassDescription
.menu-iconLeading icon (accent-colored when active)
.menu-labelFlex-grow text with ellipsis overflow
.menu-trailTrailing badge/count text
.menu-chevronRotates 90° when aria-expanded="true"

Groups & Dividers

ClassDescription
.menu-groupFlex column wrapper
.menu-group-labelUppercase section header
.menu-dividerHorizontal separator

Nested Menus

  • Indented with vertical tree-line indicator via ::before
  • .menu-collapse — smooth grid-template-rows expand/collapse animation
  • data-expanded on .menu-collapse — opens the collapsed section

Flyout Submenus

  • Absolutely positioned popup with fade-in animation
  • Alignment (data-align on .menu-flyout): right (default) · bottom
  • data-open on .menu-flyout — shows the flyout

Compact Mode

data-compact on .menu — hides labels, trails, chevrons, and group labels; centers items for icon-only sidebar navigation; hides nested tree-line indicators.

TypeScript Types

ts
MenuFlyoutAlignments // ["right", "bottom"]
type MenuFlyoutAlignment

interface MenuStyleProps { horizontal?: boolean; compact?: boolean }
interface MenuItemStyleProps { disabled?: boolean }
interface MenuIconStyleProps {}
interface MenuLabelStyleProps {}
interface MenuTrailStyleProps {}
interface MenuChevronStyleProps {}
interface MenuGroupStyleProps {}
interface MenuGroupLabelStyleProps {}
interface MenuDividerStyleProps {}
interface MenuCollapseStyleProps { expanded?: boolean }
interface MenuFlyoutStyleProps { align?: MenuFlyoutAlignment; open?: boolean }

Dist Structure

FileDescription
index.cssBarrel — imports tokens + generated styles
menu.cssToken definitions
menu.g.cssGenerated component styles
index.js / index.d.tsTypeScript types + runtime constants

Dependencies

Requires tokens from @vibe-labs/design (surfaces, borders, spacing, typography, colors, transitions, elevation, z-index).

Build

bash
npm run build

Usage Guide

Import

css
@import "@vibe-labs/design-components-menus";
ts
import type { MenuStyleProps, MenuItemStyleProps, MenuFlyoutStyleProps } from "@vibe-labs/design-components-menus/types";

Variants

Container modifiers

  • data-horizontal on .menu — switches to row layout (top navigation bar)
  • data-compact on .menu — icon-only sidebar (hides labels, trails, chevrons)

Active item

aria-current on .menu-item — applies active background, text color, weight, and accent indicator bar.

Chevron expanded

aria-expanded="true" on .menu-chevron — rotates the chevron 90°.

Collapsed section

data-expanded on .menu-collapse — opens the animated expand/collapse section.

Flyout alignment

data-align on .menu-flyout: right (default) · bottom

data-open on .menu-flyout — shows the flyout with fade-in animation.

Disabled item

data-disabled on .menu-item — reduces opacity, disables pointer events.

Examples

Vertical sidebar navigation

html
<!-- Standard vertical menu for a sidebar -->
<nav>
  <ul class="menu" role="list">
    <li>
      <a class="menu-item" href="/dashboard" aria-current="page">
        <span class="menu-icon"><svg><!-- icon --></svg></span>
        <span class="menu-label">Dashboard</span>
      </a>
    </li>
    <li>
      <a class="menu-item" href="/projects">
        <span class="menu-icon"><svg><!-- icon --></svg></span>
        <span class="menu-label">Projects</span>
        <span class="menu-trail">12</span>
      </a>
    </li>
    <li>
      <a class="menu-item" href="/settings">
        <span class="menu-icon"><svg><!-- icon --></svg></span>
        <span class="menu-label">Settings</span>
      </a>
    </li>
  </ul>
</nav>

Horizontal top navigation bar

html
<!-- Horizontal menu — indicator bar appears at the bottom of each item -->
<nav>
  <ul class="menu" data-horizontal role="list">
    <li><a class="menu-item" href="/" aria-current="page"><span class="menu-label">Home</span></a></li>
    <li><a class="menu-item" href="/docs"><span class="menu-label">Docs</span></a></li>
    <li><a class="menu-item" href="/blog"><span class="menu-label">Blog</span></a></li>
  </ul>
</nav>

Compact (icon-only) sidebar

html
<!-- Compact mode — only icons visible, labels hidden -->
<nav>
  <ul class="menu" data-compact role="list">
    <li>
      <a class="menu-item" href="/dashboard" aria-current="page" aria-label="Dashboard">
        <span class="menu-icon"><svg><!-- icon --></svg></span>
      </a>
    </li>
    <li>
      <a class="menu-item" href="/projects" aria-label="Projects">
        <span class="menu-icon"><svg><!-- icon --></svg></span>
      </a>
    </li>
  </ul>
</nav>

Grouped menu with dividers

html
<nav>
  <ul class="menu">
    <li class="menu-group">
      <div class="menu-group-label">Main</div>
      <ul>
        <li><a class="menu-item" href="/dashboard" aria-current="page"><span class="menu-label">Dashboard</span></a></li>
        <li><a class="menu-item" href="/analytics"><span class="menu-label">Analytics</span></a></li>
      </ul>
    </li>
    <li class="menu-divider"></li>
    <li class="menu-group">
      <div class="menu-group-label">Settings</div>
      <ul>
        <li><a class="menu-item" href="/profile"><span class="menu-label">Profile</span></a></li>
      </ul>
    </li>
  </ul>
</nav>

Collapsible nested section

html
<!-- Toggle data-expanded to expand/collapse the child menu -->
<ul class="menu">
  <li>
    <button class="menu-item" aria-expanded="true">
      <span class="menu-icon"><svg><!-- icon --></svg></span>
      <span class="menu-label">Components</span>
      <span class="menu-chevron" aria-expanded="true"><svg><!-- chevron --></svg></span>
    </button>
    <div class="menu-collapse" data-expanded>
      <ul class="menu">
        <li><a class="menu-item" href="/components/buttons"><span class="menu-label">Buttons</span></a></li>
        <li><a class="menu-item" href="/components/inputs"><span class="menu-label">Inputs</span></a></li>
      </ul>
    </div>
  </li>
</ul>

Flyout submenu

html
<!-- Flyout opens to the right of the parent item by default -->
<ul class="menu">
  <li style="position: relative">
    <button class="menu-item" aria-haspopup="true">
      <span class="menu-label">More</span>
      <span class="menu-chevron"><svg><!-- › --></svg></span>
    </button>
    <div class="menu-flyout" data-align="right" data-open>
      <a class="menu-item" href="/changelog"><span class="menu-label">Changelog</span></a>
      <a class="menu-item" href="/roadmap"><span class="menu-label">Roadmap</span></a>
    </div>
  </li>
</ul>

With Vue

Use @vibe-labs/design-vue-menus for <SbMenu>, <SbMenuItem>, <SbMenuGroup>, <SbMenuCollapse>, and <SbMenuFlyout> which manage aria-current, expand/collapse state, and flyout positioning automatically.

Vibe