---
title: "MazSidebar"
description: "MazSidebar is a composable sidebar system for dashboard, admin, and SaaS applications. It provides a fully accessible, collapsible navigation with multiple display modes."
package: "maz-ui"
url: "https://maz-ui.com/components/maz-sidebar"
---

# MazSidebar

> MazSidebar is a composable sidebar system for dashboard, admin, and SaaS applications. It provides a fully accessible, collapsible navigation with multiple display modes.

- Package: `maz-ui`
- Import: `import MazSidebar from 'maz-ui/components/MazSidebar'` or `import MazSidebarMenuButton from 'maz-ui/components/MazSidebarMenuButton'` or `import MazSidebarMenuSub from 'maz-ui/components/MazSidebarMenuSub'` or `import MazSidebarGroup from 'maz-ui/components/MazSidebarGroup'`
- Nuxt: auto-imported by `@maz-ui/nuxt`, no import needed in a Nuxt app
- Web: https://maz-ui.com/components/maz-sidebar

> Before use it, you should install the MazUi plugin in your project, follow instructions in [Getting Started](../guide/getting-started.md)

**Tip:**

`MazSidebar` shares its state with all sub-components via Vue's `provide`/`inject`. No separate Provider component is needed. All sub-components must be descendants of `MazSidebar`.

**Warning: Deferred features (v1.1)**

The following features are planned for a future release and are **not yet available**:
- `localStorage` state persistence
- `Cmd/Ctrl+B` keyboard shortcut
- Automatic mobile drawer under a breakpoint

## Sub-components

| Component              | Role                                                                  |
| ---------------------- | --------------------------------------------------------------------- |
| `MazSidebar`           | Root container - provides state context                               |
| `MazSidebarHeader`     | Fixed top area                                                        |
| `MazSidebarContent`    | Scrollable central area                                               |
| `MazSidebarFooter`     | Fixed bottom area                                                     |
| `MazSidebarGroup`      | Item grouping with optional label                                     |
| `MazSidebarSeparator`  | Visual divider (`<hr>`)                                               |
| `MazSidebarMenu`       | `<ul>` wrapper with `role="menu"`                                     |
| `MazSidebarMenuItem`   | `<li>` item wrapper                                                   |
| `MazSidebarMenuButton` | Clickable item (link or button), supports icon, label, badge, tooltip |
| `MazSidebarMenuSub`    | Collapsible sub-menu                                                  |
| `MazSidebarTrigger`    | Toggle button (must be a descendant of `MazSidebar`)                  |

## Basic usage

```html
<div class="flex h-screen">
  <MazSidebar v-model:open="isOpen">
    <MazSidebarHeader>
      <div class="p-4 font-bold">My App</div>
    </MazSidebarHeader>
    <MazSidebarContent>
      <MazSidebarMenu>
        <MazSidebarMenuItem>
          <MazSidebarMenuButton label="Dashboard" :active="true" />
        </MazSidebarMenuItem>
        <MazSidebarMenuItem>
          <MazSidebarMenuButton label="Settings" />
        </MazSidebarMenuItem>
      </MazSidebarMenu>
    </MazSidebarContent>
    <MazSidebarFooter>
      <div class="p-4">Footer</div>
    </MazSidebarFooter>
  </MazSidebar>
  <main class="flex-1 p-4">Main content</main>
</div>

<script setup>
const isOpen = ref(true)
</script>
```

## Groups & dividers

```html
<MazSidebar v-model:open="isOpen">
  <MazSidebarContent>
    <MazSidebarGroup label="Navigation">
      <MazSidebarMenu>
        <MazSidebarMenuItem>
          <MazSidebarMenuButton label="Dashboard" :active="true" />
        </MazSidebarMenuItem>
        <MazSidebarMenuItem>
          <MazSidebarMenuButton label="Projects" />
        </MazSidebarMenuItem>
      </MazSidebarMenu>
    </MazSidebarGroup>
    <MazSidebarSeparator />
    <MazSidebarGroup label="Settings">
      <MazSidebarMenu>
        <MazSidebarMenuItem>
          <MazSidebarMenuButton label="Profile" />
        </MazSidebarMenuItem>
      </MazSidebarMenu>
    </MazSidebarGroup>
  </MazSidebarContent>
</MazSidebar>
```

## Sub-menus

```html
<MazSidebar v-model:open="isOpen">
  <MazSidebarContent>
    <MazSidebarMenu>
      <MazSidebarMenuItem>
        <MazSidebarMenuSub label="Products" :default-open="true">
          <MazSidebarMenuItem>
            <MazSidebarMenuButton label="All Products" />
          </MazSidebarMenuItem>
          <MazSidebarMenuItem>
            <MazSidebarMenuButton label="Add Product" />
          </MazSidebarMenuItem>
        </MazSidebarMenuSub>
      </MazSidebarMenuItem>
    </MazSidebarMenu>
  </MazSidebarContent>
</MazSidebar>
```

## Icon mode with tooltips

When `collapsible="icon"` and the sidebar is collapsed, labels fade out and only the icons stay visible. To help users identify items in collapsed state, provide a `tooltip` prop on each `MazSidebarMenuButton`: it uses the `v-tooltip` directive and is only shown on hover when the sidebar is collapsed by default.

### Controlling when the tooltip appears

Both `MazSidebar` and `MazSidebarMenuButton` accept a `tooltip-mode` prop:

- `closed` (default on the sidebar) - tooltip only shows on hover when the sidebar is collapsed
- `always` - tooltip shows on hover regardless of the sidebar state

Set it on the sidebar to apply to every descendant button, or override per-button:

```html
<!-- Every button: tooltip visible only when collapsed -->
<MazSidebar tooltip-mode="closed">
  <MazSidebarMenuButton :icon="MazHome" label="Dashboard" tooltip="Go to Dashboard" />
</MazSidebar>

<!-- This button overrides: tooltip always shown -->
<MazSidebar tooltip-mode="closed">
  <MazSidebarMenuButton
    :icon="MazCog6Tooth"
    label="Settings"
    tooltip="Open Settings"
    tooltip-mode="always"
  />
</MazSidebar>
```

**Tip: Performance**

When no `tooltip` prop is provided on a button, the `v-tooltip` directive is not attached at all - no listeners, no popover instance.

```html
<script setup>
import { MazHome } from '@maz-ui/icons/lazy/MazHome'
import { MazCog6Tooth } from '@maz-ui/icons/lazy/MazCog6Tooth'

const isOpen = ref(true)
</script>

<template>
  <MazSidebar v-model:open="isOpen" collapsible="icon">
    <MazSidebarContent>
      <MazSidebarMenu>
        <MazSidebarMenuItem>
          <MazSidebarMenuButton :icon="MazHome" label="Dashboard" tooltip="Go to Dashboard" />
        </MazSidebarMenuItem>
      </MazSidebarMenu>
    </MazSidebarContent>
    <MazSidebarFooter>
      <MazSidebarTrigger />
    </MazSidebarFooter>
  </MazSidebar>
</template>
```

## Hover mode

`collapsible="hover"` displays the sidebar in icon-only mode and **automatically expands it when the mouse enters the sidebar (or any descendant) and collapses it when the mouse leaves**. Keyboard focus also expands the sidebar, so users navigating with `Tab` see the full labels.

**Tip:**

This is a **purely visual** state. The `v-model:open` value is not updated and the persistence cookie is not written when the sidebar expands or collapses through hover. Use this mode when you want the rail-style sidebar of `collapsible="icon"` without exposing a toggle.

```html
<MazSidebar collapsible="hover">
  <MazSidebarContent>
    <MazSidebarMenu>
      <MazSidebarMenuItem>
        <MazSidebarMenuButton :icon="MazHome" label="Dashboard" />
      </MazSidebarMenuItem>
      <MazSidebarMenuItem>
        <MazSidebarMenuButton :icon="MazCog6Tooth" label="Settings" />
      </MazSidebarMenuItem>
    </MazSidebarMenu>
  </MazSidebarContent>
</MazSidebar>
```

## Offcanvas mode

```html
<MazSidebar v-model:open="isOpen" collapsible="offcanvas">
  <!-- content -->
</MazSidebar>
```

## Overlay vs push mode

In `mode="push"` (default), the sidebar is part of the document flow and pushes the main content.
In `mode="overlay"`, the sidebar floats over the content with a backdrop, escape key support and focus trapping.

```html
<!-- Overlay mode: sidebar floats over content -->
<MazSidebar v-model:open="isOpen" mode="overlay">
  <!-- content -->
</MazSidebar>
```

## Side end (right)

```html
<div class="flex h-screen">
  <main class="flex-1">Main content</main>
  <MazSidebar v-model:open="isOpen" side="end">
    <!-- content -->
  </MazSidebar>
</div>
```

## Badges

`badge` accepts a `string`, a `number`, or a full `MazBadgeProps` object (with an optional `text`) to fine-tune color, size, outlined, etc.

```html
<!-- Simple: pass a string or number -->
<MazSidebarMenuButton label="Inbox" :badge="12" />

<!-- Full customisation: pass a MazBadgeProps object -->
<MazSidebarMenuButton
  label="Errors"
  :badge="{ text: 3, color: 'destructive' }"
/>
<MazSidebarMenuButton
  label="Beta"
  :badge="{ text: 'NEW', color: 'success', outlined: true }"
/>
```

## User menu in footer

```html
<MazSidebar v-model:open="isOpen">
  <MazSidebarContent><!-- ... --></MazSidebarContent>
  <MazSidebarFooter>
    <div class="flex items-center gap-3 p-3">
      <MazAvatar src="..." size="0.8rem" />
      <div class="flex-1 min-w-0">
        <p class="m-0 text-md font-semibold">Jane Doe</p>
        <p class="m-0 text-xs text-muted">jane@example.com</p>
      </div>
    </div>
  </MazSidebarFooter>
</MazSidebar>
```

## Mobile (static rendering)

**Note:**

Automatic mobile drawer (drawer auto under a breakpoint with backdrop) is a **deferred v1.1 feature**. For now, control the sidebar visibility programmatically based on viewport.

```html
<script setup>
import { useWindowSize } from 'maz-ui'

const { width } = useWindowSize()
const isMobile = computed(() => width.value < 768)
const isOpen = ref(!isMobile.value)

watch(isMobile, (mobile) => {
  isOpen.value = !mobile
})
</script>

<template>
  <div class="flex h-screen">
    <MazSidebar v-model:open="isOpen" :mode="isMobile ? 'overlay' : 'push'">
      <!-- content -->
    </MazSidebar>
    <main class="flex-1">
      <MazSidebarTrigger />
      <!-- page content -->
    </main>
  </div>
</template>
```

## Programmatic control (v-model + composable)

Use `v-model:open` for two-way binding, or `useSidebar()` inside any descendant component.

```html
<script setup>
import { useSidebar } from 'maz-ui'

// Inside a child component that is a descendant of MazSidebar:
const sidebar = useSidebar()

// sidebar.open.value  → boolean
// sidebar.state.value → 'expanded' | 'collapsed'
// sidebar.toggle()    → toggle open state
// sidebar.setOpen(true/false) → set explicitly
</script>

<template>
  <MazSidebar v-model:open="isOpen">
    <MazSidebarContent>
      <MazSidebarMenu>
        <MazSidebarMenuItem>
          <MazSidebarMenuButton label="Dashboard" />
        </MazSidebarMenuItem>
      </MazSidebarMenu>
    </MazSidebarContent>
    <!-- MazSidebarTrigger uses useSidebar() internally -->
    <MazSidebarTrigger />
  </MazSidebar>
</template>
```

## Persistent open state

The sidebar persists its open/collapsed state in a cookie (`maz-sidebar-open`) so it survives page reloads. This is **enabled by default**.

```html
<!-- Default: persistence enabled, cookie name "maz-sidebar-open" -->
<MazSidebar v-model:open="isOpen" />

<!-- Disable persistence -->
<MazSidebar v-model:open="isOpen" :persist="false" />

<!-- Custom cookie key (useful when multiple sidebars coexist) -->
<MazSidebar v-model:open="isOpen" persist-key="admin-sidebar-open" />
```

### How it works

- **In setup (server + client)**: the cookie is read and used to initialize the open state. On the server, the value is taken from the request `Cookie` header via Vue's `useSSRContext()`; on the client it comes from `document.cookie`.
- **On client mount**: `update:open` is emitted if the persisted value differs from the `:open` prop, so your parent `v-model` stays in sync.
- **On state change**: every transition between expanded/collapsed writes the new value to the cookie (1-year expiry, `SameSite=Lax`).

### SSR considerations

Persistence is **SSR-native** - the server reads the request cookie and renders the sidebar in its persisted state immediately, so there is no expand → collapse flash on hydration. This works out of the box in Nuxt and any Vue 3 SSR runtime that exposes the request headers through `useSSRContext()`.

If you prefer to manage the state yourself (e.g. to share it with other parts of your app), disable persistence and feed the cookie via your own ref:

```html
<script setup>
const sidebarOpen = useCookie<boolean>('maz-sidebar-open', { default: () => true })
</script>

<template>
  <MazSidebar v-model:open="sidebarOpen" :persist="false">
    <!-- ... -->
  </MazSidebar>
</template>
```

`:persist="false"` prevents the sidebar from also writing the cookie, leaving full control to your `useCookie` ref.

## With router (vue-router / NuxtLink)

`MazSidebarMenuButton` uses `resolveLinkComponent()` internally - it detects `RouterLink` (vue-router) or a provided `NuxtLink`. Pass the `to` prop for router navigation, `href` for plain anchor links.

```html
<!-- With vue-router -->
<MazSidebarMenuButton :to="{ name: 'home' }" label="Home" />
<MazSidebarMenuButton to="/about" label="About" />

<!-- With plain href -->
<MazSidebarMenuButton href="https://maz-ui.com" label="Docs" />

<!-- Mark as active manually (useful when router is not available) -->
<MazSidebarMenuButton label="Dashboard" :active="$route.name === 'dashboard'" />
```

## CSS Variables

Customize the sidebar width using these CSS custom properties (also settable via `width` / `iconWidth` props):

```css
:root {
  --maz-sidebar-width: 16rem;      /* expanded width */
  --maz-sidebar-icon-width: 3rem;  /* icon-mode collapsed width */
}
```

## Accessibility

- The sidebar root element renders as `<aside aria-label="Sidebar">`
- `MazSidebarMenu` renders as `<ul role="menu">`
- `MazSidebarMenuItem` renders as `<li role="none">`
- `MazSidebarTrigger` has `aria-expanded` and `aria-controls` referencing the sidebar `id`
- `MazSidebarMenuButton` applies `aria-current="page"` on active items and exposes the label via `aria-label`
- `MazSidebarMenuSub` trigger has `aria-expanded` and `aria-controls`
- In `mode="overlay"`, pressing `Escape` closes the sidebar and focus is restored to the trigger element
- In `mode="overlay"`, `Tab`/`Shift+Tab` are trapped within the sidebar
- All transitions respect `prefers-reduced-motion`

## MazSidebar

## MazSidebar API

`import MazSidebar from 'maz-ui/components/MazSidebar'`

### Props

- `id` (`string`): Unique identifier for the sidebar (auto-generated if not provided).
- `open` (`boolean`, default: `true`, `v-model:open`): Whether the sidebar is open.
- `side` (`"end" | "start"`, default: `"start"`): Side of the sidebar.
- `collapsible` (`"icon" | "none" | "hover" | "offcanvas"`, default: `"offcanvas"`): How the sidebar collapses - offcanvas: completely hidden - icon: only icons visible - hover: only icons visible by default, expands on mouse hover or keyboard focus (transient visual state - does not update `v-model:open` nor the persistence cookie) - none: always fully visible.
- `mode` (`"push" | "overlay"`, default: `"push"`): How the sidebar affects layout - push: sidebar is in document flow - overlay: sidebar floats over content with a backdrop.
- `width` (`string`, default: `"16rem"`): Width of the expanded sidebar.
- `icon-width` (`string`, default: `"3rem"`): Width of the collapsed sidebar in icon mode.
- `tooltip-mode` (`MazSidebarTooltipMode`, default: `"closed"`): When descendant `MazSidebarMenuButton` components should display their tooltip. - `always`: tooltip shows on hover regardless of the sidebar state - `closed`: tooltip only shows on hover when the sidebar is collapsed Individual buttons can override this via their own `tooltipMode` prop.
- `persist` (`boolean`, default: `true`): Persist the open/collapsed state in a cookie so it survives reloads. Restoration happens on client mount (SSR-safe). For zero-flash SSR, read the cookie server-side and forward it via `v-model:open`.
- `persist-key` (`string`, default: `"maz-sidebar-open"`): Cookie key used when `persist` is enabled. Set a unique key per sidebar instance if you mount several sidebars on the same site.

### Events

- `@update:open` (payload: `value: boolean`): Emitted when open state changes

### Slots

- `#default`

### Exposed (template ref)

- `toggle`
- `setOpen`

## MazSidebarMenuButton

## MazSidebarMenuButton API

`import MazSidebarMenuButton from 'maz-ui/components/MazSidebarMenuButton'`

### Props

- `to` (`string | Record<string, unknown>`): Route for router-link navigation.
- `href` (`string`): URL for anchor navigation.
- `icon` (`MazIconLike`): Icon to display before the label. Accepts a bare icon value (Vue component, raw SVG string, URL) or a full `MazIconProps` object for fine-grained control (size, title, fallback, …).
- `label` (`string`): Label text.
- `badge` (`MazSidebarMenuButtonBadge`): Badge content. Pass a `string`/`number` for a simple primary badge, or a full `MazBadgeProps` object (with optional `text`) to customise color, size, outlined, ….
- `tooltip` (`string`): Tooltip text. When provided, shows a tooltip on hover via the `v-tooltip` directive.
- `tooltip-mode` (`MazSidebarTooltipMode`): Controls when the tooltip should be displayed. - `always`: tooltip shows on hover regardless of the sidebar state - `closed`: tooltip only shows on hover when the sidebar is collapsed Overrides the parent `MazSidebar`'s `tooltipMode`. Falls back to the sidebar value (which defaults to `always`) when not provided.
- `active` (`boolean`): Whether the item is active (highlights with `aria-current="page"`).
- `disabled` (`boolean`): Whether the item is disabled.
- `size` (`"sm" | "md" | "lg"`, default: `"md"`): Size of the button.

### Events

- `@click` (payload: `event: MouseEvent`)

### Slots

- `#icon`
- `#default`

## MazSidebarMenuSub

## MazSidebarMenuSub API

`import MazSidebarMenuSub from 'maz-ui/components/MazSidebarMenuSub'`

### Props

- `label` (`string`): Label for the sub-menu trigger button.
- `icon` (`MazIconLike`): Icon to display before the label. Accepts a bare icon value (Vue component, raw SVG string, URL) or a full `MazIconProps` object for fine-grained control.
- `default-open` (`boolean`, default: `false`): Whether the sub-menu is open by default.

### Slots

- `#icon`
- `#label`
- `#chevron`
- `#default`

## MazSidebarGroup

## MazSidebarGroup API

`import MazSidebarGroup from 'maz-ui/components/MazSidebarGroup'`

### Props

- `label` (`string`): Optional label for the group.

### Slots

- `#default`
