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.
| Category | Blocks |
|---|---|
| Type | Web Component (<mv-sidebar>) |
| Status | beta |
| Also installs | dialog, tooltip |
| Keywords | sidebar, navigation, app-shell, drawer, rail, collapsible, layout |
When to use
- An app needs side navigation that collapses to an icon rail and remembers its state
- Navigation has groups, nested sections, counters, a workspace switcher and a user menu
- On narrow containers the navigation should slide out as a panel with a keyboard shortcut
Avoid when
- A whole dashboard layout with header, KPIs and search is needed → use App Shell instead
- The site is a public marketing site better served by a top bar → use Navbar instead
Install
node scripts/add.mjs sidebar --out ./src/marvelousAI 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 & 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
| Name | Type | Default | Description |
|---|---|---|---|
collapsed | boolean | Collapsed state (reflected, persisted when storage-key is set). | |
collapsible | icon | offcanvas | none | icon | icon: icon rail; offcanvas: hides completely; none: fixed. |
storage-key | string | localStorage key used to remember the state between visits. | |
label | string | Sidebar | Accessible name of the region and the mobile panel. |
breakpoint | number (px) | 768 | Width of the .mv-sidebar-layout container below which the sidebar becomes a slide-out panel. |
hotkey | string | b | Key combined with Ctrl/⌘ to toggle; empty to disable. |
data-state | expanded | collapsed | Set automatically (also data-sidebar on the layout). | |
data-mobile | boolean | Set automatically in panel mode. |
Methods
| Name | Description |
|---|---|
toggle() | Collapses/expands; in mobile mode, opens/closes the panel. |
collapse() / expand() | Forces the state. |
Events
| Name | Description |
|---|---|
mv-toggle | detail: { collapsed }. |
Content structure
| Name | Description |
|---|---|
.mv-sidebar-header / -content / -footer | Regions; -content scrolls (use a <nav aria-label>). |
.mv-sidebar-group > .mv-sidebar-group-label + ul.mv-sidebar-menu | Titled group (the list is named by its title). data-push on a group pushes it to the bottom. |
a/button.mv-sidebar-item | Item: <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-sub | Collapsible 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
| Name | Description |
|---|---|
mv-sidebar-layout | Flex container for sidebar + content; data-variant="inset": content as a floating card. |
mv-sidebar-inset | Content area (container query named mv-sidebar-inset). |
mv-sidebar-logo / -badge / -item-end / -separator | Logo tile, counter (data-variant="accent"), trailing icon, divider. |
mv-sidebar-hide-collapsed | Hides an element in rail mode. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-sidebar-width | 16rem | Expanded width. |
--mv-sidebar-width-icon | 3rem | Rail width. |
--mv-sidebar-bg | var(--mv-bg-subtle) | Background. |
--mv-sidebar-highlight | Hover 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.