beta

Sidebar — <mv-sidebar>

App sidebar: animated icon rail with tooltips, persisted state, groups, collapsible nested sections, counters, workspace switcher and user menu, slide-out panel (dialog) when the container is narrow, Ctrl/⌘ + B shortcut.

CategoryBlocks
TypeWeb Component (<mv-sidebar>)
Statusbeta
Also installsdialog, tooltip
Keywordssidebar, navigation, app-shell, drawer, rail, collapsible, layout

When to use

Avoid when

Install

node scripts/add.mjs sidebar --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["sidebar"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Files copied (dependencies included): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, core/motion.js, core/observe.js, core/position.js, components/sidebar/sidebar.js, components/sidebar/sidebar.css, components/dialog/dialog.js, components/dialog/dialog.css, components/tooltip/tooltip.js, components/tooltip/tooltip.css.

Usage

Canonical markup — start from it and customize with attributes, data-* and CSS variables:

<div class="mv-sidebar-layout" data-variant="inset" id="sb-demo" style="height:640px;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);overflow:hidden">
  <mv-sidebar label="Main navigation" storage-key="mv-demo-sidebar">
    <div class="mv-sidebar-header">
      <mv-menu placement="bottom-start">
        <button class="mv-sidebar-item" data-size="lg" type="button">
          <span class="mv-sidebar-logo" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M4 19V5l8 7 8-7v14"/></svg></span>
          <span class="mv-sidebar-label"><strong>Nova Studio</strong><small>Pro plan · 12 members</small></span>
          <svg class="mv-sidebar-item-end" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 15 5 5 5-5M7 9l5-5 5 5"/></svg>
        </button>
        <div data-content style="--mv-menu-width:15rem">
          <div class="mv-menu-label">Workspaces</div>
          <div role="group" data-name="workspace">
            <button role="menuitemradio" aria-checked="true" value="nova">Nova Studio <span class="mv-menu-shortcut">⌘1</span></button>
            <button role="menuitemradio" value="lumen">Lumen Labs <span class="mv-menu-shortcut">⌘2</span></button>
            <button role="menuitemradio" value="opal">Opal Coffee <span class="mv-menu-shortcut">⌘3</span></button>
          </div>
          <hr>
          <button role="menuitem" value="new-workspace"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M12 5v14M5 12h14"/></svg>New workspace</button>
        </div>
      </mv-menu>
    </div>

    <nav class="mv-sidebar-content" aria-label="Sections">
      <div class="mv-sidebar-group">
        <h2 class="mv-sidebar-group-label">Platform</h2>
        <ul class="mv-sidebar-menu">
          <li><a class="mv-sidebar-item" href="#sb-demo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg><span class="mv-sidebar-label">Dashboard</span></a></li>
          <li><a class="mv-sidebar-item" href="#sb-demo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 12h-6l-2 3h-4l-2-3H2"/><path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"/></svg><span class="mv-sidebar-label">Inbox</span><span class="mv-sidebar-badge" data-variant="accent" aria-label="4 unread">4</span></a></li>
          <li class="mv-sidebar-sub">
            <button class="mv-sidebar-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg><span class="mv-sidebar-label">Projects</span><span class="mv-sidebar-badge">12</span></button>
            <ul class="mv-sidebar-submenu">
              <li><a class="mv-sidebar-subitem" href="#sb-demo" aria-current="page">Customer portal redesign</a></li>
              <li><a class="mv-sidebar-subitem" href="#sb-demo">Mobile app</a></li>
              <li><a class="mv-sidebar-subitem" href="#sb-demo">Opal rebrand</a></li>
              <li><a class="mv-sidebar-subitem" href="#sb-demo">Archived</a></li>
            </ul>
          </li>
          <li><a class="mv-sidebar-item" href="#sb-demo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg><span class="mv-sidebar-label">Calendar</span></a></li>
          <li class="mv-sidebar-sub">
            <button class="mv-sidebar-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 3v18h18"/><path d="M18 17V9M13 17V5M8 17v-3"/></svg><span class="mv-sidebar-label">Reports</span></button>
            <ul class="mv-sidebar-submenu">
              <li><a class="mv-sidebar-subitem" href="#sb-demo">Sales</a></li>
              <li><a class="mv-sidebar-subitem" href="#sb-demo">Traffic</a></li>
              <li><a class="mv-sidebar-subitem" href="#sb-demo">Retention</a></li>
            </ul>
          </li>
        </ul>
      </div>

      <div class="mv-sidebar-group">
        <h2 class="mv-sidebar-group-label">Team</h2>
        <ul class="mv-sidebar-menu">
          <li><a class="mv-sidebar-item" href="#sb-demo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"/></svg><span class="mv-sidebar-label">Members</span></a></li>
          <li><a class="mv-sidebar-item" href="#sb-demo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"/><path d="M14 2v6h6M16 13H8M16 17H8"/></svg><span class="mv-sidebar-label">Documents</span></a></li>
        </ul>
      </div>

      <div class="mv-sidebar-group" data-push>
        <ul class="mv-sidebar-menu">
          <li><a class="mv-sidebar-item" href="#sb-demo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3M12 17h.01"/></svg><span class="mv-sidebar-label">Help &amp; support</span></a></li>
          <li><a class="mv-sidebar-item" href="#sb-demo"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 7h-9M14 17H5"/><circle cx="17" cy="17" r="3"/><circle cx="7" cy="7" r="3"/></svg><span class="mv-sidebar-label">Settings</span></a></li>
        </ul>
      </div>
    </nav>

    <div class="mv-sidebar-footer">
      <mv-menu placement="top-start">
        <button class="mv-sidebar-item" data-size="lg" type="button">
          <span class="mv-avatar" data-variant="tinted" style="--mv-avatar-hue:200" aria-hidden="true"><span class="mv-avatar-fallback">MP</span></span>
          <span class="mv-sidebar-label"><strong>Maya Patel</strong><small>[email protected]</small></span>
          <svg class="mv-sidebar-item-end" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 15 5 5 5-5M7 9l5-5 5 5"/></svg>
        </button>
        <div data-content style="--mv-menu-width:15rem">
          <div class="mv-menu-label">My account</div>
          <button role="menuitem" value="profile"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="8" r="4"/><path d="M4 21a8 8 0 0 1 16 0"/></svg>Profile <span class="mv-menu-shortcut">⇧⌘P</span></button>
          <button role="menuitem" value="billing"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M2 10h20"/></svg>Billing</button>
          <button role="menuitem" value="notifications"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9"/><path d="M10.3 21a1.94 1.94 0 0 0 3.4 0"/></svg>Notifications</button>
          <hr>
          <button role="menuitem" value="sign-out"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/></svg>Log out</button>
        </div>
      </mv-menu>
    </div>
  </mv-sidebar>

  <main class="mv-sidebar-inset" aria-label="Content">
    <style>@container mv-sidebar-inset (width < 34rem) { #sb-demo .sb-demo-hint { display: none } }</style>
    <header style="display:flex;align-items:center;gap:.5rem;height:3.5rem;padding:0 1rem;border-bottom:1px solid var(--mv-border);flex-shrink:0">
      <button class="mv-button" data-variant="ghost" data-size="icon" data-mv-sidebar-toggle aria-label="Show or hide the sidebar" title="Sidebar (Ctrl B)">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 3v18"/></svg>
      </button>
      <div class="mv-separator" role="separator" aria-orientation="vertical" data-orientation="vertical" style="height:1rem;min-height:1rem;align-self:center"></div>
      <nav class="mv-breadcrumb" aria-label="Breadcrumb" data-size="sm" style="min-width:0">
        <ol>
          <li><a href="#sb-demo">Projects</a></li>
          <li><span aria-current="page" class="mv-breadcrumb-truncate">Customer portal redesign</span></li>
        </ol>
      </nav>
      <span style="margin-inline-start:auto;font-size:.75rem;color:var(--mv-fg-muted);white-space:nowrap" class="sb-demo-hint">Collapse: <kbd class="mv-kbd-group"><kbd class="mv-kbd" data-size="sm">Ctrl</kbd><kbd class="mv-kbd" data-size="sm">B</kbd></kbd></span>
    </header>
    <div style="display:grid;gap:1rem;padding:1.25rem;align-content:start">
      <div>
        <h1 style="margin:0;font-size:1.25rem;font-weight:600;letter-spacing:-.01em">Customer portal redesign</h1>
        <p style="margin:.25rem 0 0;color:var(--mv-fg-muted);font-size:.875rem">Launching Oct 14 · 18 of 24 tasks done</p>
      </div>
      <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:.75rem">
        <article class="mv-card" data-size="sm">
          <div class="mv-card-content" style="display:grid;gap:.5rem">
            <span style="font-size:.8125rem;color:var(--mv-fg-muted)">Progress</span>
            <strong style="font-size:1.5rem;font-weight:600">75%</strong>
            <progress class="mv-progress" data-size="sm" max="100" value="75" aria-label="Project progress"></progress>
          </div>
        </article>
        <article class="mv-card" data-size="sm">
          <div class="mv-card-content" style="display:grid;gap:.5rem">
            <span style="font-size:.8125rem;color:var(--mv-fg-muted)">Budget used</span>
            <strong style="font-size:1.5rem;font-weight:600">$31,400</strong>
            <progress class="mv-progress" data-size="sm" data-color="warning" max="100" value="84" aria-label="Budget used"></progress>
          </div>
        </article>
        <article class="mv-card" data-size="sm">
          <div class="mv-card-content" style="display:grid;gap:.5rem">
            <span style="font-size:.8125rem;color:var(--mv-fg-muted)">Reviews to schedule</span>
            <strong style="font-size:1.5rem;font-weight:600">2</strong>
            <span style="font-size:.75rem;color:var(--mv-fg-muted)">Product design · Accessibility</span>
          </div>
        </article>
      </div>
      <div style="min-height:14rem;border:1px dashed var(--mv-border-strong);border-radius:var(--mv-radius-lg);display:grid;place-items:center;color:var(--mv-fg-subtle);font-size:.8125rem;text-align:center;padding:1rem">Page content — the sidebar collapses to an icon rail and becomes a slide-out panel below 768px.</div>
    </div>
  </main>
</div>

API

Attributes

NameTypeDefaultDescription
collapsedbooleanCollapsed state (reflected, persisted when storage-key is set).
collapsibleicon | offcanvas | noneiconicon: icon rail; offcanvas: hides completely; none: fixed.
storage-keystringlocalStorage key used to remember the state between visits.
labelstringSidebarAccessible name of the region and the mobile panel.
breakpointnumber (px)768Width of the .mv-sidebar-layout container below which the sidebar becomes a slide-out panel.
hotkeystringbKey combined with Ctrl/⌘ to toggle; empty to disable.
data-stateexpanded | collapsedSet automatically (also data-sidebar on the layout).
data-mobilebooleanSet automatically in panel mode.

Methods

NameDescription
toggle()Collapses/expands; in mobile mode, opens/closes the panel.
collapse() / expand()Forces the state.

Events

NameDescription
mv-toggledetail: { collapsed }.

Content structure

NameDescription
.mv-sidebar-header / -content / -footerRegions; -content scrolls (use a <nav aria-label>).
.mv-sidebar-group > .mv-sidebar-group-label + ul.mv-sidebar-menuTitled group (the list is named by its title). data-push on a group pushes it to the bottom.
a/button.mv-sidebar-itemItem: <svg> + .mv-sidebar-label (+ .mv-sidebar-badge). aria-current="page" for the current page. data-size="lg" for a switcher (logo/avatar + <strong>/<small>).
li.mv-sidebar-subCollapsible section: button.mv-sidebar-item + ul.mv-sidebar-submenu of a.mv-sidebar-subitem; data-open to open it (automatic when it contains the current page).
[data-mv-sidebar-toggle]Trigger button (inside the same .mv-sidebar-layout, or value = sidebar selector). aria-expanded/aria-controls handled.

CSS classes

NameDescription
mv-sidebar-layoutFlex container for sidebar + content; data-variant="inset": content as a floating card.
mv-sidebar-insetContent area (container query named mv-sidebar-inset).
mv-sidebar-logo / -badge / -item-end / -separatorLogo tile, counter (data-variant="accent"), trailing icon, divider.
mv-sidebar-hide-collapsedHides an element in rail mode.

CSS variables

NameDefaultDescription
--mv-sidebar-width16remExpanded width.
--mv-sidebar-width-icon3remRail width.
--mv-sidebar-bgvar(--mv-bg-subtle)Background.
--mv-sidebar-highlightHover background.

Accessibility

Named region; lists named by their group title; aria-current="page"; collapsible sections as <button aria-expanded aria-controls>. In rail mode, labels stay in the DOM (accessible names intact) and a tooltip appears on hover and keyboard focus. Trigger with aria-expanded. Mobile mode: modal dialog (focus trap, Escape, inert background) with a Close button. Ctrl/⌘ + B.