Skip to content
Kappa
Sidebar
@dicehub/kappav0.4.2

Sidebar

Compact application navigation with resizing, peeking, sliding views, and an accessible mobile drawer.

Installation

Sidebar is part of Kappa. It adds no dependencies. Icons in these examples come from the documentation site's existing icon package; the library accepts any Vue icon component or icon slot.

Barrel

import { Sidebar } from "@dicehub/kappa";

Granular

import { Sidebar } from "@dicehub/kappa/components/sidebar";

Usage

For complete application layouts, see Blocks → Sidebar: workspace, icon rail, inset, floating, two-column navigation, and top-header examples.

Put one Root and the application content inside a Provider. Give the layout a height, then let Content scroll between Header and Footer. Keep one Trigger in the application header so the mobile drawer and offcanvas Sidebar can be reopened. A second footer toggle is not needed. These demos align both desktop headers at 4rem. Include Close in the navigation header for mobile.

Use MenuItem around every top-level entry and MenuSubItem around nested entries. These explicit list items keep navigation semantics predictable when you compose a Collapsible.

Use icons for every top-level item when collapsible="icon". Labels remain accessible when visually hidden; tooltip adds a visible label on hover or focus. Custom header and footer content must also fit the rail. Wrap text in MenuLabel, or use Context to change the composition.

Composition

Typical part hierarchy
Sidebar.Provider
├── Sidebar.Root <nav> / Ark Drawer on mobile
│   ├── Sidebar.Header
│   │   ├── Dropdown + MenuButton (namespace, optional)
│   │   └── Sidebar.Close (mobile)
│   ├── Sidebar.Content
│   │   └── Sidebar.Group
│   │       ├── Sidebar.GroupLabel
│   │       └── Sidebar.Menu <ul>
│   │           └── Sidebar.MenuItem <li>
│   │               ├── Sidebar.MenuButton
│   │               └── Sidebar.Collapsible (optional)
│   │                   ├── Sidebar.CollapsibleTrigger
│   │                   └── Sidebar.CollapsibleContent
│   │                       └── Sidebar.MenuSub <ul>
│   ├── Sidebar.Loading (alternative to Content)
│   ├── Sidebar.SlidingViews (optional)
│   │   └── Sidebar.SlidingView → Sidebar.Content
│   ├── Sidebar.Footer
│   │   ├── Dropdown + MenuButton (profile, optional)
│   │   └── Sidebar.Trigger
│   └── Sidebar.ResizeHandle (optional, desktop)
└── application content
    └── Sidebar.Trigger

Sidebar and Sidebar.Root are the same navigation component. Provider supplies the state and flex layout; it does not provide an application header, router, or store.

Kappa owns desktop state, navigation markup, dimensions, and semantic-token styling. Ark owns mobile modality, scroll locking, focus trapping, Escape/outside dismissal, and section accessibility. Mobile panels teleport to body. Global Kappa theme tokens must reach the teleport target.

Resizing adapts Ark Splitter's two-panel size model to a pixel-width Sidebar and normal flex content. No extra application wrapper is required. The separator controls the navigation landmark. Ark's collapse threshold and keyboard behavior are preserved.

The resize scope uses physical left/right panel order, including RTL. This avoids an installed Ark Splitter inconsistency between RTL pointer and keyboard direction without replacing its interaction code. Enter targets the navigation panel through Ark's collapse/expand API when that panel follows the content. Navigation retains the application locale.

Namespace, profile, and Quick search examples compose Dropdown and CommandPalette. They are not application-specific Sidebar parts. The scroll-to-item example uses native scrolling scoped to Content; it does not add a Sidebar scrolling API or move the outer page.

Inside the mobile drawer, keep Dropdown.Content in the drawer with :teleport="false" and fixed positioning. This keeps popup focus inside the modal boundary. Desktop popups can teleport to body.

Nested Sections

Icon collapse hides nested content without resetting each section's open preference. Activating a collapsed section requests expansion of the Sidebar and section. Controlled parents must accept both updates when both levels are controlled.

Examples

Namespace Selector

A side-opening menu with framed icons, namespace shortcuts, and an Add namespace action. On mobile, the menu opens below the button inside the drawer. Ctrl/⌘ + 1–3 works only while this menu is open. Add namespace shows a placeholder page in this example; connect the action to your application.

Profile Selector

A footer menu with initials, name, email, profile switching, and account actions. All data is synthetic. Actions update this example only.

Quick search opens a real CommandPalette. Type a page name, use the arrow keys, then press Enter to navigate. Escape returns focus to the search button. No global shortcut is registered.

Resizable

Drag the separator to change the width. Drag toward the rail to collapse; drag outward to expand. Arrow keys resize, Shift increases the step, Home/End move to the edge limits, and Enter toggles collapse. Ark owns the drag threshold between the minimum width and the rail. The handle is desktop-only.

Controlled Width

Keep the expanded width in application state. Lock width to reject resize requests. Collapse and mobile state remain separate.

Peeking

Hover or focus the collapsed rail to peek. The live state distinguishes a temporary peek from a pinned sidebar. Pin it open, then collapse it to try again. Namespace and profile menus keep the peek visible. Escape or leaving both the Sidebar and its popup closes it without moving the page.

Scroll to Item

Jump to an item in a long navigation list without moving the documentation page or keyboard focus. Keep Settings visible does nothing when that row is already fully visible. This composition uses native Content scrolling; reduced motion disables smooth scrolling.

Sliding Views

Use the navigation header or the page button to switch between workspace and project views. You can also open Rotor study from the list and go back. The active view is shown beside the navigation. Inactive views stay mounted, hidden, and inert. Motion respects reduced-motion settings.

Compact

28px desktop rows for dense navigation. Mobile rows keep 44px touch targets.

Icon Collapse

Start with an icon rail. Tooltips appear on hover and keyboard focus. Activating a nested section expands the rail.

Offcanvas

Hide the entire desktop Sidebar. Keep a Trigger outside it so users can reopen it.

Non-collapsible

Keep desktop navigation expanded. Small viewports still use the accessible mobile drawer.

Controlled State

Control desktop and mobile state separately. Lock state to test a parent that rejects a desktop change request.

End Placement

Logical end placement follows the text direction. Drag the separator left to grow this Sidebar. Home grows it; End collapses it, following Ark panel order.

Right-to-left

Use DirectionProvider for Ark behavior and dir for text and layout. Start becomes the right edge.

Mobile Drawer

Open the drawer inside this resizable viewport. It starts at the preview's left edge, not at the edge of the documentation page. Tab stays inside the drawer. Escape, Close, and the backdrop dismiss it. Open example tests the same composition in a full browser tab.

Full-screen Mobile

Resize the viewport, then open the full-screen navigation. The drawer fills only that viewport, so the documentation stays visible. Try namespace and profile menus, Quick search, and a nested page such as Refinement. Its breadcrumb remains visible after the drawer closes.

Long Navigation

Header and Footer stay visible while Content scrolls. Long labels do not change the width.

Loading

Deterministic placeholder rows with an accessible loading status. Motion stops under reduced-motion preferences.

Routing and State

Supply href for normal links, or compose a router link with as-child. The library does not read the URL or select routes. Set active from your route state. For custom children, put the icon and MenuLabel inside the link.

MenuButton does not close the mobile drawer automatically. Call setMobileOpen(false) after your router accepts the navigation. This keeps rejected and asynchronous navigation under application control.

Provider supports v-model:open and v-model:mobile-open. Uncontrolled defaults are read once. Leaving mobile mode requests that mobile state close; the desktop preference is unchanged. Controlled parents must handle update events. No cookies, storage, global keyboard shortcuts, or application state are added.

SSR renders the desktop structure, then checks the viewport after hydration. Navigation content remounts when switching between desktop and mobile. Keep persistent form state and controlled section state outside Root when they must survive that transition.

Accessibility

  • Root is a named navigation landmark, not an ARIA menu. Use Tab and Shift+Tab to move between links; Enter activates links, and Enter or Space activates buttons and disclosures.
  • Active entries use aria-current="page". Disabled entries cannot navigate or activate and leave the tab order.
  • Mobile uses a named modal dialog. Focus stays inside while open. Escape, Close, and backdrop interaction dismiss it; focus returns to the opening Trigger.
  • Use a visible Close in mobile headers. Do not rely only on the backdrop.
  • Offcanvas content is hidden and inert. Icon labels remain in the accessibility tree; nested hidden links leave the tab order.
  • ResizeHandle is a focusable vertical separator. Arrow keys, Shift+Arrow, Home, End, and Enter use Ark's resize behavior. End placement reverses which edge grows the Sidebar. The handle is not rendered on mobile.
  • Peeking keeps the desktop layout width fixed. Escape dismisses it without moving the pointer. It also closes when both pointer and focus leave. Popups linked through aria-controls remain part of the interaction area; their first Escape closes the popup.
  • Sliding views keep inactive content mounted, hidden, and inert. In-view navigation transfers focus; an external view change does not take focus from its external control.
  • Content is a keyboard-focusable scrolling region. Use a descriptive aria-label if its default is not suitable.
  • Pass translated Root, Trigger, Close, and Loading labels. Pair DirectionProvider with a DOM dir attribute for RTL.
  • Motion respects reduced-motion preferences. Mobile navigation controls keep a 44px minimum height, including compact mode.

API Reference

Sidebar.Provider

PropTypeDefaultDescription
open / defaultOpenbooleanundefined / trueControlled or initial desktop expanded state.
mobileOpen / defaultMobileOpenbooleanundefined / falseIndependent controlled or initial mobile drawer state.
collapsible"icon" | "offcanvas" | "none""icon"Desktop collapse mode. none ignores desktop collapse requests.
side"start" | "end""start"Logical edge. End placement orders the desktop Sidebar after the application content.
compactbooleanfalse28px instead of 32px minimum desktop rows. Mobile remains 44px.
mobileBreakpointnumber768Viewport widths below this many pixels use a drawer. Zero disables mobile mode.
width / collapsedWidth / mobileWidthCSS length"16.25rem" / "3.25rem" / "18rem"Expanded desktop, icon rail, and mobile widths. Mobile width leaves at least 3rem for the backdrop.
resizablebooleanfalseEnable desktop resizing through ResizeHandle. The numeric resize width replaces the fixed width prop.
resizeWidth / defaultWidthnumber (px)undefined / 260Controlled or initial expanded resize width. Resize emits requests; a controlled parent can reject them.
minWidth / maxWidthnumber (px)180 / 400Expanded resize bounds. A collapsed icon rail can be smaller than minWidth.
peekablebooleanfalseHover or focus temporarily expands a collapsed icon rail. Not used on mobile or in offcanvas/none modes.
idstringVue useId()Stable ID prefix for navigation and dialog relationships. Set IDs here, not on Root.

Parts

PartPropsDefaultDescription
Rootlabel"Main navigation"Accessible navigation and mobile dialog name. Give multiple Sidebars distinct names.
RootfullScreenOnMobilefalseMobile dialog covers the viewport.
TriggerexpandLabel / collapseLabel / openLabel / closeLabelEnglish action labelsOptional localized labels for the built-in icon. Custom content must provide its own accessible name.
Triggerdisabled / asChildfalseDisable the action or compose it with a custom button.
Closelabel / asChild"Close sidebar" / falseMobile-only close action. Hidden on desktop.
MenuButtonhref / asChildundefined / falseA native link when href is set; otherwise a button. asChild accepts a router link or native element.
MenuButtonactive / disabledfalseCurrent-page styling and aria-current; disabled controls block activation and leave the tab order.
MenuButtonicon / tooltipundefinedVue icon component (or #icon slot), and optional collapsed-rail tooltip.
Collapsibleopen / defaultOpen / disabled / idundefined / false / false / generatedArk disclosure behavior with controlled and uncontrolled state. Sections retain their open preference during icon collapse.
CollapsibleTrigger / CollapsibleContentasChildfalseCompose Ark parts with Sidebar.MenuButton or a custom element.
Loadingrows / label5 / "Loading navigation"1–20 placeholder rows and a localized accessible status name.
ResizeHandlelabel / disabled"Resize sidebar" / falseFocus-visible desktop separator. Ark handles pointer and keyboard resizing. In offcanvas mode, reopen with an external Trigger.
SlidingViewsactiveKey / directionrequired / "left"Controlled active view key and physical transition direction (left or right). Reverse direction for back navigation.
SlidingViewvalue / labelrequired / valueMatching view key and accessible group name. Inactive views stay mounted but are hidden and inert.
Structural partsasChildfalseHeader, Footer, Content, Group, GroupLabel, Menu, MenuItem, MenuSub, MenuSubItem, MenuLabel, MenuBadge, and Separator forward attributes to their semantic element.

Events and Context

Provider emits update:open(boolean), openChange({ open }), update:mobileOpen(boolean), and mobileOpenChange({ open }). Collapsible emits update:open and openChange. Controlled components emit requests without changing the supplied value.

Resizing emits update:resizeWidth(number), resize({ width }), and resizeEnd({ width }). Widths are in pixels. The expanded width is retained during collapse and mobile mode. Use v-model:resize-width to control it.

Sidebar.Context exposes unwrapped open, mobileOpen, isMobile, state, iconCollapsed, side, compact, and collapsible, plus setOpen, setMobileOpen, and toggle. useSidebarContext() exposes the same values as Vue refs in a descendant setup function.

Context also exposes resizable, width, isResizing, setWidth, peekable, and isPeeking. State can be expanded, collapsed, or peeking. Peeking never emits a desktop open change.

Styling and Exports

Structural parts forward attributes, classes, styles, and listeners. Root and Provider use reserved generated IDs; supply the Provider id prop to customize their relationships. Root exposes data-state, data-side, data-collapsible, data-mobile, and data-compact. MenuButton exposes data-active and data-disabled; Ark parts retain their state attributes.

Colors inherit Kappa control, default, subtle, line, tint, overlay, and focus tokens. Dimensions use --kappa-sidebar-width, --kappa-sidebar-collapsed-width, and --kappa-sidebar-mobile-width; prefer the matching Provider props so teleported mobile content receives them too.

Every compound part has a named export, such as SidebarProvider, SidebarRoot, and SidebarMenuButton. Public props, slots, state, events, SidebarContextValue, and SIDEBAR_DEFAULTS are exported from the same subpath.