Sidebar
Composable navigation sidebar for app shells with collapsible sections, search and filter, tree views with hover actions, and a mobile menu
import {
SidebarProvider,
SidebarLayout,
Sidebar,
SidebarHeader,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarInset,
SidebarTrigger,
} from "@plexui/vue/components/Sidebar";Examples
The examples read their navigation data (navGroups, docsSections, iconSections, consoleSections, ...) from the setup scope, where a real app defines it in <script setup> with icon components imported from @plexui/vue/components/Icon. The default slot of SidebarProvider receives the sidebar state and controls (v-slot="sidebar"), the same object useSidebar() returns.
App Shell
Basic
Collapsible Sections
Search and Filter
Tree with Hover Actions
Mobile Menu
Search Input
API
Structure
A Sidebar is composed of the following parts:
SidebarProvider: Handles collapsible state and provides context to all child components.SidebarLayout: Wrapper for sidebar and main content.Sidebar: The sidebar container.SidebarHeader/SidebarFooter: Sticky at the top and bottom of the sidebar.SidebarContent: Scrollable content area.SidebarGroup: Section within the content, with optional label.SidebarMenu/SidebarMenuItem/SidebarMenuButton: Navigation menu items.SidebarMenuSub/SidebarMenuSubButton: Nested submenu items.SidebarInset: Main content area next to the sidebar.SidebarTrigger: Toggle button for collapsing the sidebar.
Use Cmd+B (Mac) or Ctrl+B (Windows/Linux) to toggle the sidebar with keyboard.
SidebarProvider
Handles sidebar state and provides context to all child components. Wrap your application layout in this provider.
| Prop | Type | Default | Description |
|---|---|---|---|
collapsible | "offcanvas" | "icon" | "none" | "offcanvas" | Collapse behavior: offcanvas (drawer on mobile, full collapse on desktop), icon (icon rail), or none |
default-open | boolean | true | Initial open state (uncontrolled) |
open | boolean | — | Controlled open state. Bind with v-model:open |
@update:open | (open: boolean) => void | — | Emitted when the open state changes; powers v-model:open. Without open the provider keeps its own state and still emits |
class | string | — | Class of the provider's wrapper |
style | StyleValue | — | Style of the provider's wrapper, e.g. --sidebar-width |
default slot | { state, open, setOpen, openMobile, setOpenMobile, isMobile, collapsible, toggleSidebar } | — | Receives the sidebar context, the object useSidebar() returns |
Sidebar
The main sidebar container. Renders as an <aside> element.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "sidebar" | "docs" | "sidebar" | Visual variant |
side | "left" | "right" | "left" | Which side the sidebar is on |
SidebarGroup
A section within SidebarContent.
| Prop | Type | Default | Description |
|---|---|---|---|
default-open | boolean | true | Whether the group is open initially. A closed group hides its SidebarGroupContent |
open | boolean | — | Controlled open state, also exposed as data-state. The group has no toggle of its own: change open from your own control |
SidebarGroupLabel
Renders a section label within a SidebarGroup. Supports two size variants.
| Prop | Type | Default | Description |
|---|---|---|---|
size | "sm" | "lg" | "lg" | sm: compact, tertiary. lg: larger, primary, more spacing |
as-child | boolean | false | Render the single child element as the label |
SidebarMenuItem
| Prop | Type | Default | Description |
|---|---|---|---|
expanded | boolean | — | Marks the item expanded, which turns its SidebarMenuChevron. Pair it with open on its SidebarMenuSub |
SidebarMenuButton
Renders a menu item button. Use is-active to highlight the current page.
| Prop | Type | Default | Description |
|---|---|---|---|
is-active | boolean | false | Highlight the current page (aria-current="page") |
tooltip | string | — | Label shown on hover when collapsed (icon mode). Hidden on mobile. The tooltip slot takes rich content |
as-child | boolean | false | Render the single child element (e.g. a RouterLink) as the button. The button attributes go on the child and its content is wrapped like the button's |
SidebarMenuSub
Renders a collapsible submenu container for nested navigation.
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | true | Whether submenu is expanded. A closed submenu is inert, so its links and buttons leave the tab order and the accessibility tree |
has-icons | boolean | false | Align child items with parent text that has icons |
SidebarMenuSubButton
| Prop | Type | Default | Description |
|---|---|---|---|
is-active | boolean | false | Highlight the current page (aria-current="page") |
indent | 0 | 1 | 2 | 3 | 0 | Nesting level; each level adds 12px of left margin |
as-child | boolean | false | Render the single child element (e.g. a RouterLink) as the button |
SidebarTrigger
Collapse button. Renders nothing when collapsible="none". The default slot replaces the default icon. A @click listener runs before the toggle; call event.preventDefault() in it to cancel the toggle. SidebarMobileMenuButton, the animated hamburger for the mobile menu, handles @click the same way.
SidebarInput
A search field: Input with a search icon as its start adornment. Every attribute, listener and slot goes to the Input (v-model:value, @clear, ...).
SidebarCard
Renders a promotional card in the sidebar footer for CTAs, upgrades, or announcements.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "elevated" | "default" | Visual variant |
dismissible | boolean | false | Shows dismiss button |
@dismiss | (event: MouseEvent) => void | — | Emitted when the dismiss button is clicked |
useSidebar
Composable to access sidebar state and controls from the setup() of any component within SidebarProvider. It returns a reactive object: read its fields (sidebar.state) rather than destructuring them outside a render.
state: Current state:"expanded"or"collapsed"open/setOpen: Desktop sidebar open stateopenMobile/setOpenMobile: Mobile menu open stateisMobile: Whether the viewport is mobilecollapsible: Current collapsible modetoggleSidebar: Toggle function for both desktop and mobile
Styling
Sidebar reads these component tokens. Override them at any scope to restyle it. SidebarProvider sets the three width tokens on its own element, so change those through its style (or on an element inside it):
--sidebar-accent: accent (defaultlight-dark(var(--alpha-08), var(--alpha-12)))--sidebar-background: background (defaultnone)--sidebar-border: border (defaultvar(--color-border))--sidebar-collapse-duration: collapse duration (default0.3s)--sidebar-footer-shadow: footer shadow--sidebar-foreground: foreground (defaultvar(--color-text))--sidebar-group-label-color: group label color (defaultvar(--color-text-tertiary))--sidebar-gutter: gutter (default12px)--sidebar-gutter-sm: gutter sm (defaultvar(--control-gutter-2xs))--sidebar-header-height: header height (defaultvar(--control-size-3xl))--sidebar-icon-margin: icon margin (default0 2px 0 4px)--sidebar-icon-size: icon size (default20px)--sidebar-icon-wrapper-size: icon wrapper size (default32px)--sidebar-inset-shadow: inset shadow (defaultnone)--sidebar-menu-item-font-size: menu item font size (default14px)--sidebar-menu-item-height: menu item height (default38px)--sidebar-menu-item-height-desktop: menu item height desktop (default32px)--sidebar-menu-item-line-height: menu item line height (default20px)--sidebar-menu-item-margin: menu item margin (default0 0 2px)--sidebar-menu-item-margin-desktop: menu item margin desktop (default0 0 2px)--sidebar-menu-item-padding: menu item padding (default0)--sidebar-menu-item-radius: menu item radius (default8px)--sidebar-menu-text: menu text (defaultlight-dark(var(--gray-750), var(--gray-850)))--sidebar-menu-text-active: menu text active (defaultvar(--gray-900))--sidebar-mobile-backdrop-color: mobile backdrop color (defaultlight-dark(rgb(0 0 0 / 15%), rgb(0 0 0 / 35%)))--sidebar-mobile-button-radius: mobile button radius (default8px)--sidebar-mobile-button-size: mobile button size (default32px)--sidebar-mobile-enter-delay: mobile enter delay (default0.33s)--sidebar-mobile-enter-opacity-duration: mobile enter opacity duration (default0.8s)--sidebar-mobile-enter-transform-duration: mobile enter transform duration (default0.7s)--sidebar-mobile-exit-duration: mobile exit duration (default0.3s)--sidebar-mobile-gutter: mobile gutter (default12px)--sidebar-mobile-header-height: mobile header height (default48px)--sidebar-mobile-icon-size: mobile icon size (default18px)--sidebar-mobile-line-color: mobile line color (defaultvar(--gray-900))--sidebar-mobile-line-height: mobile line height (default2px)--sidebar-mobile-line-transition-duration: mobile line transition duration (default0.2s)--sidebar-mobile-z-index: mobile z index (default301)--sidebar-transition-duration: transition duration (default0.3s)--sidebar-width: width (SidebarProvidersets240px)--sidebar-width-icon: width icon (SidebarProvidersets64px)--sidebar-width-mobile: width mobile (SidebarProvidersets288px)--skeleton-background: background (defaultlight-dark(var(--alpha-08), var(--alpha-12)))--skeleton-radius: radius (defaultvar(--radius-md))