Sidebar
A vertical sidebar navigation with collapsible submenus, search, drag & drop, resize, groups, badges, collapsed mode, mini mode, and more.
For horizontal navigation, see Menu.
Basic
<script setup>
import { ref } from 'vue'
import { VxSidebar } from 'veloxui'
const active = ref('inbox')
const items = [
{ key: 'inbox', label: 'Inbox', icon: 'mail' },
{ key: 'starred', label: 'Starred', icon: 'star' },
{ key: 'drafts', label: 'Drafts', icon: 'file' },
{ key: 'trash', label: 'Trash', icon: 'trash' },
]
</script>
<template>
<VxSidebar v-model="active" :items="items" />
</template>With Badges
<VxSidebar v-model="active" :items="[
{ key: 'inbox', label: 'Inbox', icon: 'mail', badge: 12, badgeType: 'primary' },
{ key: 'starred', label: 'Starred', icon: 'star', badge: 'New', badgeType: 'success' },
{ key: 'alerts', label: 'Alerts', icon: 'heart', badge: 3, badgeType: 'danger' },
]" />Badge truncation ^0.1.10
Numeric badges greater than 9 collapse to +9 and reveal the full value in a tooltip on hover. Opt out per item with fullBadge: true.
<VxSidebar v-model="active" :items="[
{ key: 'inbox', label: 'Inbox', icon: 'mail', badge: 3, badgeType: 'primary' },
{ key: 'alerts', label: 'Alerts', icon: 'heart', badge: 42, badgeType: 'danger' }, // "+9" → hover shows 42
{ key: 'updates', label: 'Updates', icon: 'refresh-cw', badge: 128, badgeType: 'warning' }, // "+9" → hover shows 128
{ key: 'spam', label: 'Spam', icon: 'trash', badge: 500, fullBadge: true }, // "500" (no truncation)
]" />Nested Submenus
Click a parent item to expand/collapse its children.
<VxSidebar v-model="active" :items="[
{
key: 'projects',
label: 'Projects',
icon: 'folder',
children: [
{ key: 'design', label: 'Design System' },
{ key: 'api', label: 'API Platform' },
],
},
]" />Grouped
Group items under section headers with the group property.
<VxSidebar v-model="active" :items="[
{ key: 'inbox', label: 'Inbox', icon: 'mail', group: 'Communication' },
{ key: 'chat', label: 'Chat', icon: 'heart', group: 'Communication' },
{ key: 'analytics', label: 'Analytics', icon: 'search', group: 'Workspace' },
]" />Notification Dots
Small colored indicators on items. Default red, customizable with dotColor.
<VxSidebar v-model="active" :items="[
{ key: 'inbox', label: 'Inbox', icon: 'mail', dot: true },
{ key: 'chat', label: 'Chat', icon: 'heart', dot: true, dotColor: '#22c55e' },
]" />Searchable
Enable searchable to add a search input that filters items by label.
<VxSidebar v-model="active" :items="items" searchable />
<VxSidebar v-model="active" :items="items" searchable search-placeholder="Find..." />Collapsed
Toggle collapsed to show only icons with tooltips. Submenus appear as popouts.
<script setup>
import { ref } from 'vue'
const active = ref('inbox')
const collapsed = ref(false)
const items = [
{ key: 'home', label: 'Home', icon: 'home' },
{ key: 'inbox', label: 'Inbox', icon: 'mail', badge: 5, badgeType: 'primary' },
{
key: 'projects',
label: 'Projects',
icon: 'folder',
children: [
{ key: 'design', label: 'Design System' },
{ key: 'api', label: 'API Platform' },
{ key: 'mobile', label: 'Mobile App' },
],
},
{
key: 'settings',
label: 'Settings',
icon: 'settings',
children: [
{ key: 'profile', label: 'Profile', icon: 'user' },
{ key: 'security', label: 'Security', icon: 'link' },
{ key: 'billing', label: 'Billing' },
],
},
{ key: 'logout', label: 'Log Out', icon: 'logout', divider: true },
]
</script>
<template>
<VxButton @click="collapsed = !collapsed">
{{ collapsed ? 'Expand' : 'Collapse' }}
</VxButton>
<VxSidebar v-model="active" :items="items" :collapsed="collapsed" />
</template>Mini Mode
When collapsed with mini, shows the first letter of each label instead of icons.
<VxSidebar v-model="active" :items="items" collapsed mini />Resizable
Enable resizable to allow dragging the sidebar edge to resize. Drag the right edge of the sidebar.
<script setup>
import { ref } from 'vue'
import { VxSidebar } from 'veloxui'
const active = ref('inbox')
const collapsed = ref(false)
const items = [
{ key: 'inbox', label: 'Inbox', icon: 'mail' },
{ key: 'starred', label: 'Starred', icon: 'star' },
{ key: 'drafts', label: 'Drafts', icon: 'file' },
{ key: 'trash', label: 'Trash', icon: 'trash' },
]
</script>
<template>
<VxSidebar
v-model="active"
v-model:collapsed="collapsed"
:items="items"
resizable
:min-width="160"
:max-width="360"
/>
</template>Draggable
Enable draggable to allow reordering top-level items by drag and drop.
<script setup>
import { ref } from 'vue'
import { VxSidebar } from 'veloxui'
const active = ref('inbox')
const items = ref([
{ key: 'inbox', label: 'Inbox', icon: 'mail' },
{ key: 'starred', label: 'Starred', icon: 'star' },
{ key: 'drafts', label: 'Drafts', icon: 'file' },
{ key: 'trash', label: 'Trash', icon: 'trash' },
{ key: 'settings', label: 'Settings', icon: 'settings' },
])
function onReorder(newItems) {
items.value = newItems
}
</script>
<template>
<VxSidebar v-model="active" :items="items" draggable @reorder="onReorder" />
</template>Router Integration
Use the to property on items for SPA navigation.
<VxSidebar v-model="active" :items="[
{ key: 'home', label: 'Home', icon: 'home', to: '/' },
{ key: 'about', label: 'About', icon: 'user', to: '/about' },
]" />Context Menu
Right-click on "Inbox" or "Starred" to see the context menu.
<script setup>
import { ref } from 'vue'
import { VxSidebar } from 'veloxui'
const active = ref('inbox')
const items = [
{
key: 'inbox',
label: 'Inbox',
icon: 'mail',
badge: 5,
badgeType: 'primary',
contextMenu: [
{ label: 'Mark all read', icon: 'check', action: (item) => {} },
{ label: 'Settings', icon: 'settings', action: (item) => {} },
{ label: 'Delete all', icon: 'trash', destructive: true, action: (item) => {} },
],
},
{
key: 'starred',
label: 'Starred',
icon: 'star',
contextMenu: [
{ label: 'Clear starred', icon: 'x', action: (item) => {} },
],
},
{ key: 'drafts', label: 'Drafts', icon: 'file' },
{ key: 'settings', label: 'Settings', icon: 'settings' },
]
</script>
<template>
<VxSidebar v-model="active" :items="items" />
</template>Loading
Show skeleton placeholders while data loads.
<VxSidebar loading :loading-count="6" />Dark Theme
<VxSidebar v-model="active" :items="items" theme="dark" />Accordion
Set accordion to only allow one submenu open at a time. Click "Projects" then "Settings" — only one stays open.
<script setup>
import { ref } from 'vue'
import { VxSidebar } from 'veloxui'
const active = ref('home')
const items = [
{ key: 'home', label: 'Home', icon: 'home' },
{
key: 'projects',
label: 'Projects',
icon: 'folder',
children: [
{ key: 'design', label: 'Design System' },
{ key: 'api', label: 'API Platform' },
],
},
{
key: 'settings',
label: 'Settings',
icon: 'settings',
children: [
{ key: 'profile', label: 'Profile', icon: 'user' },
{ key: 'security', label: 'Security', icon: 'link' },
],
},
{ key: 'logout', label: 'Log Out', icon: 'logout' },
]
</script>
<template>
<VxSidebar v-model="active" :items="items" accordion />
</template>API
VxSidebar Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | MenuItem[] | [] | Sidebar items |
v-model | string | '' | Active item key |
collapsed | boolean | false | Collapse to icons only |
theme | 'light' | 'dark' | 'light' | Color theme |
accordion | boolean | false | Only one submenu open at a time |
indent | number | 24 | Child item indent in px |
collapsed-width | number | 48 | Width when collapsed |
width | number | string | 240 | Sidebar width |
rounded | boolean | true | Rounded item corners |
searchable | boolean | false | Show search input |
search-placeholder | string | 'Search...' | Search input placeholder |
draggable | boolean | false | Enable drag & drop reorder |
mini | boolean | false | Show first letter when collapsed |
resizable | boolean | false | Enable resize handle |
min-width | number | 160 | Min width when resizing |
max-width | number | 400 | Max width when resizing |
scroll-shadow | boolean | true | Show scroll shadows |
loading | boolean | false | Show skeleton placeholders |
loading-count | number | 6 | Number of skeleton items |
root-class | string | string[] | object | — | Class on root |
MenuItem
| Property | Type | Required | Description |
|---|---|---|---|
key | string | Yes | Unique identifier |
label | string | Yes | Display text |
icon | string | Component | No | Built-in icon name or Vue component |
disabled | boolean | No | Disable this item |
badge | string | number | No | Badge content. Numeric values > 9 collapse to +9; full value appears in a tooltip on hover |
badgeType | 'default' | 'primary' | 'success' | 'warning' | 'danger' | No | Badge color |
fullBadge | boolean | No | Always show the full badge value without +9 truncation |
group | string | No | Group header label |
divider | boolean | No | Show divider above |
action | (item) => void | No | Click callback |
children | MenuItem[] | No | Nested submenu items |
to | string | object | No | Vue Router navigation target |
dot | boolean | No | Show notification dot |
dotColor | string | No | Dot color (default red) |
pinned | boolean | No | Pin item to top |
contextMenu | ContextMenuItem[] | No | Right-click menu items |
Slots
| Slot | Props | Description |
|---|---|---|
header | — | Content above navigation |
footer | — | Content below navigation |
item | { item, active } | Custom item rendering |
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | (key: string) | Active item changed |
update:collapsed | (collapsed: boolean) | Auto-collapse when resized below min-width |
select | (key, item) | Item selected |
open-change | (openKeys: string[]) | Submenu opened/closed |
search | (query: string) | Search input changed |
reorder | (items: MenuItem[]) | Items reordered via drag |
resize | (width: number) | Sidebar resized |
context-menu | (item, event) | Right-click on item |
pin | (item) | Item pinned/unpinned |
Exposed
| Name | Type | Description |
|---|---|---|
openKeys() | () => string[] | Get currently open submenu keys |
toggleOpen(key) | (key: string) => void | Toggle a submenu |