Skip to content

MazSidebar ​

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

Before use it, you should install the MazUi plugin in your project, follow instructions in Getting Started

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.

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 ​

ComponentRole
MazSidebarRoot container — provides state context
MazSidebarHeaderFixed top area
MazSidebarContentScrollable central area
MazSidebarFooterFixed bottom area
MazSidebarGroupItem grouping with optional label
MazSidebarSeparatorVisual divider (<hr>)
MazSidebarMenu<ul> wrapper with role="menu"
MazSidebarMenuItem<li> item wrapper
MazSidebarMenuButtonClickable item (link or button), supports icon, label, badge, tooltip
MazSidebarMenuSubCollapsible sub-menu
MazSidebarTriggerToggle button (must be a descendant of MazSidebar)

Basic usage ​

Main content

Groups & dividers ​

Main content
Main content

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>

Performance

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

Click the trigger to collapse/expand

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.

Move your cursor over the sidebar to expand it.

Offcanvas mode ​

Main content — sidebar disappears completely when closed

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.

Side end (right) ​

Main content

Badges ​

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

Badges are hidden in icon-collapsed mode.
Main content

Mobile (static rendering) ​

INFO

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="maz:flex maz:h-screen">
    <MazSidebar v-model:open="isOpen" :mode="isMobile ? 'overlay' : 'push'">
      <!-- content -->
    </MazSidebar>
    <main class="maz: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.

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 ​

Props ​

NameDescriptionTypeRequiredDefault
idUnique identifier for the sidebar (auto-generated if not provided)stringNoundefined
openWhether the sidebar is openbooleanNoundefined
sideSide of the sidebar"start" | "end"Noundefined
collapsibleHow 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"offcanvas" | "icon" | "hover" | "none"Noundefined
modeHow the sidebar affects layout - push: sidebar is in document flow - overlay: sidebar floats over content with a backdrop"push" | "overlay"Noundefined
widthWidth of the expanded sidebarstringNoundefined
icon-widthWidth of the collapsed sidebar in icon modestringNoundefined
tooltip-modeWhen 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.MazSidebarTooltipModeNoclosed
persistPersist 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.booleanNotrue
persist-keyCookie key used when persist is enabled. Set a unique key per sidebar instance if you mount several sidebars on the same site.stringNomaz-sidebar-open

Events ​

Event namePropertiesDescription
update:openEmitted when open state changes

Slots ​

NameDescriptionBindings
default

Expose ​

toggle ​

setOpen ​

MazSidebarMenuButton ​

Props ​

NameDescriptionTypeRequired
toRoute for router-link navigationstring | RecordNo
hrefURL for anchor navigationstringNo
iconIcon 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, …).MazIconLikeNo
labelLabel textstringNo
badgeBadge content. Pass a string/number for a simple primary badge, or a full MazBadgeProps object (with optional text) to customise color, size, outlined, …MazSidebarMenuButtonBadgeNo
tooltipTooltip text. When provided, shows a tooltip on hover via the v-tooltip directive.stringNo
tooltip-modeControls 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.MazSidebarTooltipModeNo
activeWhether the item is active (highlights with aria-current="page")booleanNo
disabledWhether the item is disabledbooleanNo
sizeSize of the button"sm" | "md" | "lg"No

Events ​

Event namePropertiesDescription
click

Slots ​

NameDescriptionBindings
icon
default

MazSidebarMenuSub ​

Props ​

NameDescriptionTypeRequired
labelLabel for the sub-menu trigger buttonstringNo
iconIcon 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.MazIconLikeNo
default-openWhether the sub-menu is open by defaultbooleanNo

Slots ​

NameDescriptionBindings
icon
label
chevron
default

MazSidebarGroup ​

Props ​

NameDescriptionTypeRequired
labelOptional label for the groupstringNo

Slots ​

NameDescriptionBindings
default