Sidebar
A composable, themeable app sidebar (shadcn-vue Sidebar). It docks beside the content when there is room and turns into a sheet when there is not — measured from its own container, no breakpoints. Collapse to an icon rail or off-canvas with an animated width.
Playground
Drag the width slider; try the variants and collapse modes.
SSidebarProvider({ variant: 'sidebar', collapsible: 'icon', value: this.page!! }) {
SSidebar() {
SSidebarHeader() { Brand() }
SSidebarContent() {
SSidebarGroup() {
SSidebarGroupLabel({ text: 'Platform' })
SSidebarMenu() {
SSidebarMenuItem() {
SSidebarMenuButton({ value: 'inbox', icon: 'inbox', text: 'Inbox', badge: '24' })
}
SSidebarMenuItem() {
SSidebarMenuButton({ value: 'calendar', icon: 'calendar', text: 'Calendar' })
}
}
}
}
SSidebarFooter() { UserRow() }
}
SSidebarInset() {
STopbar({ title: 'Inbox' }) // leading SSidebarTrigger appears automatically
SPage() { ... }
}
}
// Wide container: docked; the trigger folds it to an icon rail (animated width).
// Narrow container: the sidebar becomes a sheet opened by the trigger. No breakpoints to declare.Approximate web preview — the HarmonyOS rendering is the reference.
Anatomy
SSidebarProvider owns the state (open, openMobile, value) and lays out SSidebar + SSidebarInset. Parts: SSidebarHeader, SSidebarContent (scrolls), SSidebarFooter, SSidebarGroup, SSidebarGroupLabel, SSidebarMenu, SSidebarMenuItem, SSidebarMenuButton (icon, text, badge, isActive or value), SSidebarSeparator, SSidebarTrigger.
Tokens
The look is themeable through --sidebar-* component tokens (ADR D13) on top of the semantic --sidebar / --sidebar-accent colours: background, border, widths, menu radius / height / active / hover colours, group label colour, floating and inset frames. Per instance: SSidebarProvider({ tokens }). The switches write theme variables, so they restyle the playground above and this gallery's own sidebar.
// theme-wide (exported with the theme)
SparkUI.themes.setVariable('--sidebar-background', 'transparent');
SparkUI.themes.setVariable('--sidebar-menu-radius', '999px');
SparkUI.themes.setVariable('--sidebar-menu-active-background',
'color-mix(in oklab, var(--primary) 12%, transparent)');
SparkUI.themes.setVariable('--sidebar-menu-active-foreground', 'var(--primary)');
// one sidebar only
SSidebarProvider({ tokens: { background: 'transparent', menuRadius: 999, borderColor: 'transparent' } }) { ... }Approximate web preview — the HarmonyOS rendering is the reference.
API
Extracted from the @Param / @Event / @BuilderParam declarations in the library sources.
SSidebarProvider
shadcn-vue SidebarProvider: lays out an SSidebar and an SSidebarInset side by side and owns the sidebar state. Responsive without breakpoints: layoutPolicy decides from the provider's own width whether the sidebar is docked, an icon rail or an overlay sheet (default: a sheet when the expanded sidebar does not fit next to minInsetWidth of content; sparkSidebarRailPolicy() adds the rail step).
Props: variant 'sidebar' | 'floating' | 'inset', collapsible 'offcanvas' | 'icon' | 'none', side 'left' | 'right' (render order is flipped for you). open / defaultOpen / onOpenChange control the docked state (persist it by feeding a stored value to defaultOpen and saving in onOpenChange); value / defaultValue / onValueChange / onNavigate the active menu value (no routing built in: wire any router to value + onNavigate). controller lets you own the state object.
| Prop | Type | Default | Description |
|---|---|---|---|
controller | SparkSidebarState | undefined | undefined | your own state object (headless control); one is created when omitted |
open!! | boolean | undefined | undefined | |
defaultOpen | boolean | true | |
value!! | string | undefined | undefined | |
defaultValue | string | '' | |
variant | SidebarVariantSidebarVariant = 'sidebar' | 'floating' | 'inset' | 'sidebar' | |
collapsible | SidebarCollapsibleSidebarCollapsible = 'offcanvas' | 'icon' | 'none' | 'offcanvas' | |
side | SidebarSideSidebarSide = 'left' | 'right' | 'left' | |
sidebarWidth | number | 0 | docked width (vp); 0 = the --sidebar-width token, else 256 (shadcn 16rem) |
sidebarWidthMobile | number | 0 | sheet width (vp); 0 = the --sidebar-width-mobile token, else 288 |
sidebarWidthIcon | number | 0 | icon rail width (vp); 0 = the --sidebar-width-icon token, else 48 |
tokens | SidebarTokens | {} | per-instance look (ADR D13): instance → --sidebar-* component tokens → shadcn recipe |
minInsetWidth | number | 360 | the main area should keep at least this width (vp); input of layoutPolicy |
safeArea | boolean | false | pad the window safe area (immersive pages, see SparkProvider immersive) |
closeOnSelect | boolean | true | close the sheet when a menu button is activated |
motionKey | string | 'sidebar' | motion key of the collapse animation and the sheet |
| Event | Type | Default | Description |
|---|---|---|---|
onOpenChange | (open: boolean) => void | () => { } | |
onValueChange | (value: string) => void | () => { } | |
onNavigate | (value: string) => void | () => { } | every activation of a menu value, even the current one (router hook: re-tap = pop to root) |
onOpenMobileChange | (open: boolean) => void | () => { } | the overlay sheet opened / closed |
onPresentationChange | (presentation: SidebarPresentation) => voidSidebarPresentation = 'docked' | 'rail' | 'sheet' | () => { } | the layout policy switched between docked / rail / sheet |
layoutPolicy | SparkSidebarLayoutPolicy | sparkSidebarDefaultPolicy | decides docked / rail / sheet from the measured width (replaceable) |
Slots: children (trailing closure)
SSidebar
shadcn-vue Sidebar. Docked: a column whose width animates between expanded / icon rail / hidden (provider motionKey, default 'sidebar'). Rail: the icon rail, plus the full sidebar as a sheet when toggled. Sheet: an overlay sheet from side (the SparkUI overlay base, so back gesture, backdrop tap and focus work like SSheet).
Slots: children (trailing closure)
SSidebarHeader
Top section (brand, team switcher…): flex flex-col gap-2 p-2.
Slots: children (trailing closure)
SSidebarFooter
Slots: children (trailing closure)
SSidebarContent
Scrollable middle section; takes the remaining height between header and footer.
Slots: children (trailing closure)
SSidebarGroup
Slots: children (trailing closure)
SSidebarGroupContent
Slots: children (trailing closure)
SSidebarGroupLabel
h-8 px-2 text-xs font-medium text-sidebar-foreground/70; folds away on the icon rail.
| Prop | Type | Default | Description |
|---|---|---|---|
text | ResourceStr | '' |
SSidebarMenu
Slots: children (trailing closure)
SSidebarMenuItem
Slots: children (trailing closure)
SSidebarMenuButton
shadcn-vue SidebarMenuButton: icon + text (+ badge, + trailing slot). Active when isActive is true, or when value equals the provider's value. Activating calls onSelect, updates the provider value and closes the mobile sheet. On the icon rail only the icon stays visible (the label is clipped by the animating width).
Pressed feedback comes from the system pressed state (SparkRowPressModifier.applyPressedAttribute) and the active highlight switches instantly — never animated.
| Prop | Type | Default | Description |
|---|---|---|---|
text | ResourceStr | '' | |
icon | string | undefined | undefined | |
value | string | '' | identifies the button for the provider's value |
isActive | boolean | undefined | undefined | explicit active state (overrides value matching) |
badge | string | undefined | undefined | short count / label on the trailing edge (shadcn SidebarMenuBadge) |
sizing | SidebarMenuButtonSizeSidebarMenuButtonSize = 'default' | 'sm' | 'lg' | 'default' | |
variant | SidebarMenuButtonVariantSidebarMenuButtonVariant = 'default' | 'outline' | 'default' | |
disabled | boolean | false |
| Event | Type | Default | Description |
|---|---|---|---|
onSelect | () => void | () => { } |
Slots: children (trailing closure)
SSidebarSeparator
h-px mx-2 bg-sidebar-border
No props.
SSidebarTrigger
shadcn-vue SidebarTrigger: ghost icon button that toggles the sidebar (the sheet when narrow, the docked collapse otherwise). Works anywhere inside the provider (e.g. in STopbar).
| Prop | Type | Default | Description |
|---|---|---|---|
label | ResourceStr | $r('app.string.spark_toggle_sidebar') | |
icon | string | undefined | undefined | lucide icon; defaults to panel-left / panel-right by side |
controller | SparkSidebarState | undefined | undefined | toggle this controller instead of the nearest provider (a trigger outside the provider) |
SSidebarInset
shadcn-vue SidebarInset: the main area next to the sidebar. Takes the remaining width; with variant 'inset' it becomes a rounded, raised card inside the sidebar-coloured frame.
Slots: children (trailing closure)