Sidebar
A collapsible side bar: on desktop, collapsed narrows it to an icon strip; on mobile (narrower than mobile-breakpoint, default 768px) it automatically becomes an overlay drawer with a backdrop. Clicking outside, the close button or Esc collapses it.
Basic usage
Pass menu JSON via the items attribute ([{label, value, icon?}]); the default slot holds custom content.
Default sidebar
Collapsing
Clicking the bottom「«」button toggles collapsed (a controlled attribute; fires oas-collapse). In the collapsed state only icons are shown, and menu items without an icon are hidden automatically. hide-toggle hides the button (host opt-out, e.g. for static sidebars).
Nested submenus use a flyout panel in the collapsed state: a tree parent renders as an icon item; clicking (or hovering with a short delay) opens a submenu panel anchored to the item's inline-end (mirrored to the left in RTL) with collision avoidance. Clicking a leaf fires oas-select and closes the panel; nested subtrees expand inline inside the panel; Esc closes and refocuses the parent; clicking outside closes; only one flyout is open at a time. Note (contract change): clicking a collapsed-state parent only opens the flyout and no longer fires oas-select for the parent itself (a parent is not a navigable page) — if you previously relied on the parent's oas-select in the collapsed state, listen for oas-select from the flyout's leaf items instead.
hide-toggle (no collapse button, static sidebar)
collapsed icon state
Mobile drawer
On narrow screens the sidebar automatically becomes an overlay drawer: the floating ☰ button at the top-left opens it, and the backdrop / close button / Esc collapses it. Tune the breakpoint with mobile-breakpoint (this demo uses 700px so it can be observed on a wide screen).
Mobile drawer (breakpoint 700)
Controlled mobile drawer
drawer-open is a controlled attribute: setting/removing it externally opens/closes the mobile drawer (no need to click the floating ☰). This demo raises the breakpoint to force the mobile form so it can be observed on a wide screen.
Controlled drawer-open
drawer-open to control the drawer; after it is closed via the mask / ✕ / Esc, the attribute is removed. Controlled highlight
active is a controlled attribute: setting/removing it externally migrates the currently highlighted item (no click needed).
Controlled active
active to control the current highlighted item; clearing it leaves no item highlighted. Groups
Menu items support an optional group field: consecutive items in the same group get a group title rendered before the group's first item (display-only, non-clickable). Group titles are hidden in the collapsed icon strip and shown in the mobile drawer.
Groups (items.group)
Nested submenus
Items support children nesting: a parent item toggles expansion on click (without firing oas-select); a parent containing the active child auto-expands. Subtrees are hidden in the collapsed icon strip. Expand/collapse runs a smooth height animation (degrades automatically under prefers-reduced-motion); accordion makes siblings mutually exclusive — expanding one parent collapses the others at the same level (great for long navs that keep only one section open).
Nesting depth: mechanically unlimited (recursive rendering; level 3 verified — per-level expansion, active-cascade auto-expansion and child-selected all work). But each level adds ~28px of indentation, so keep it to ≤3 levels in a 220px sidebar — use
oas-treefor deeper trees (built for arbitrary depth: connecting lines / virtual scroll).
Three-level nesting (Business → Users → User groups)
Accordion (siblings mutually exclusive)
Each click also demos the smooth expand/collapse animation (grid height transition + chevron rotation; degrades automatically under `prefers-reduced-motion`).
Nested submenus (items.children)
Badges and item actions
Items support badge count badges (colors via --oas-sidebar-badge-bg/-color variables) and actions hover action buttons (clicking fires oas-action without selecting).
Badges and item actions
Hover “Projects” to reveal action buttons; clicking fires an oas-action event.
Custom icons and coloring
Item icon defaults to the built-in @oas-ui/icons registry; app-level custom icons go through the official registerIcon(name, svg) path (exported by @oas-ui/ui) — register once, and <oas-icon>, the sidebar and any future consumers all see it (do not mutate the built-in icon data; on-demand mode using built-in names needs a one-time import '@oas-ui/icons/register'); a same-name registration overrides the built-in icon.
iconColor sets a per-item target color: when present it is fixed, taking precedence over the default disabled/active-state colors; when absent it falls back to currentColor and follows the state (active uses the primary color). Registered colored SVGs (paths with their own stroke/fill) keep their own color naturally — the outer stroke never overrides them.
Custom icons & coloring (registerIcon + iconColor)
iconColor present → does not follow the active state); “Team feed” has no iconColor so it follows the state, and its custom SVG keeps its own red stroke. Built-in icons take iconColor too: “Built-in star” uses the registry name star + a forced red (the built-in monochrome path’s currentColor is also replaced); “Built-in heart” without one follows the state. The tooltip icon in the collapsed strip shares the same color. Dividers and skeleton
A {type:"divider"} entry renders a divider line; the loading attribute shows a pulsing skeleton (the value sets the row count, default 4).
Divider + loading
Shortcut and keyboard navigation
The shortcut attribute enables Ctrl/Cmd+B collapse toggling (off by default to avoid hijacking a global key); the menu supports ↑/↓ focus movement, Home/End jumps, and Enter/Space activation.
Shortcut + keyboard navigation
Hover expansion (expand-on-hover)
The expand-on-hover attribute temporarily expands the collapsed icon strip on hover (pure visual state — does not change the controlled collapsed attribute).
expand-on-hover
Variants
The variant attribute: sidebar (default, flush) / floating (margin + radius + shadow) / inset (margin + radius + background contrast).
variant: floating / inset
Right side and dual sidebars
side="right": the mobile drawer slides in from the right with the trigger on the right; multiple sidebars can coexist (one on each side, with independent state).
side=right + dual sidebars
Resizable width (resizable)
The resizable attribute shows a drag rail on the host edge for real-time resizing (writes back to the width attribute); resize-min/resize-max clamp the range (default 160–480); the rail supports arrow keys (±8px, Home/End jumps to min/max); oas-resize fires on drag end or key adjustments. Desktop non-collapsed only (hidden when collapsed or on mobile).
resizable edge drag-resize
Resizable width (oas-splitter)
For resizable width, compose with oas-splitter instead of a built-in rail: place the sidebar in the left pane and drag the splitter handle to resize.
oas-splitter + sidebar
Custom width
The width attribute overrides the expanded width (defaults to the --oas-sidebar-width token); the collapsed state still narrows to an icon strip.
width attribute
Combining with oas-layout
oas-sidebar can be used directly as the sider of oas-layout (slot="sider").
sidebar as the sider
Main content area.
Declarative child channel
Besides the items JSON, items can be declared with <oas-sidebar-item> / <oas-sidebar-divider> child elements (the items attribute takes priority when explicitly set; otherwise children are parsed and converge to the same rendering path). The default slot text becomes the label; attributes map to the SidebarItem fields: value / icon / group / badge (all-digit strings convert to number, otherwise stay string); nesting child elements directly inside <oas-sidebar-item> recursively becomes the nested children (dividers allowed). Child additions/removals, attribute and text changes re-render automatically (MutationObserver). Boundary: actions (hover action buttons) are an object array that does not fit scalar attribute mapping — use the items JSON when you need them.
Declarative children (group / divider / nested / badge)
Dynamic add/remove (MutationObserver auto-refresh)
API
Methods
| Method | Description |
|---|---|
openDrawer() | Opens the mobile drawer programmatically (same as clicking the built-in trigger part="trigger") |
closeDrawer() | Closes the mobile drawer programmatically (same as mask click / close button / Esc) |
- Usage:
const el = document.querySelector('oas-sidebar') as OASSidebar; el.openDrawer(). - Equivalent to the controlled
drawer-openattribute: the methods are sugar for setting/removing it (openDrawersets,closeDrawerremoves) and can be mixed; repeated calls are idempotent. - Mobile only (viewport narrower than
mobile-breakpoint); on desktop the attribute is reclaimed byupdateimmediately, leaving no floating layer. - Drawer open/close emits no event: observe the
drawer-openattribute (MutationObserver) or keep host state; for a two-state toggle, callopenDrawer()/closeDrawer()based on your own state.
oas-sidebar
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
accordion | Accordion mode (when present: expanding one parent collapses the other parents at the same level; multiple parents can be open by default) | boolean | — |
active | Value of the currently-highlighted menu item (controlled: set/clear immediately re-renders highlight) | string | — |
collapsed | Controlled collapse to an icon strip (present means collapsed) | boolean | — |
drawer-open | Mobile drawer open state (controlled: set opens, clear closes; auto-removed when breakpoint returns to desktop) | boolean | — |
expand-on-hover | Temporarily expand the collapsed icon strip on hover (visual only; does not change controlled collapsed) | — | — |
hide-toggle | Hide the bottom collapse button (a built-in toggle always visible on desktop; presence hides it — host opt-out for static sidebars) | boolean | — |
items | Menu items JSON [{label, value, icon?, iconColor?, group?, badge?, children?, actions?}] (supports divider entries {type:"divider"}; children for nested submenus) | SidebarEntry[] | string | [] |
loading | Skeleton loading state (shows a pulsing skeleton when present; value sets row count, default 4) | string | 4 |
mobile-breakpoint | Mobile breakpoint (px); narrower than this becomes an overlay drawer | — | — |
resizable | Edge drag-resize (shows a drag rail on the host edge; desktop non-collapsed only) | boolean | — |
resize-max | Maximum resize width (px, default 480) | string | 480 |
resize-min | Minimum resize width (px, default 160) | string | 160 |
shortcut | Enable Ctrl/Cmd+B collapse toggling (off by default to avoid hijacking a global key) | boolean | — |
side | Drawer side: left (default) / right (mobile drawer slides from the right, trigger on the right) | — | — |
variant | Variant: sidebar (default flush) / floating (radius + shadow) / inset (radius + background contrast) | — | — |
width | Expanded width; defaults to the --oas-sidebar-width token | string | 0 |
Events
| Event | Description |
|---|---|
oas-action | detail: { value: string, action: string, label: string }; When fired: an item hover action button is clicked (does not fire oas-select) |
oas-collapse | detail: { collapsed: boolean }; When fired: Desktop collapse button toggled |
oas-resize | detail: { width: number }; When fired: drag-resize ends / arrow keys adjust the width |
oas-select | detail: { value: string, label: string }; When fired: A menu item was selected (also collapses the drawer on mobile) |
Slots
| Name | Description |
|---|---|
| default | Menu item content (default slot text; direct <oas-sidebar-item> children recursively become nested children) |
footer | Custom sidebar footer content |
header | Custom sidebar header content |
CSS Variables
| CSS Variable | Description | Default |
|---|---|---|
--oas-sidebar-badge-bg | Badge background | var(--oas-color-primary) |
--oas-sidebar-badge-color | Badge text color | #fff |
--oas-sidebar-bg | Sidebar background; defaults stay anchored to base tokens (theme / dark / brand flow through automatically), host override recolors the whole bar | var(--oas-color-bg-hover) |
--oas-sidebar-collapsed-width | Collapsed icon-rail width | 64px |
--oas-sidebar-item-hover-bg | Item hover background (defaults to a text-primary 6% mix) | color-mix(in srgb, var(--oas-color-text-primary) 6%, transparent) |
--oas-sidebar-width | Expanded width (the width attribute takes precedence) | 220px |
oas-sidebar-item
Attributes
| Attribute | Description | Type | Default |
|---|---|---|---|
badge | Count badge (all-digit strings convert to number, otherwise stay string; matches SidebarItem.badge's string | number) | — | — |
group | Group name (consecutive items in the same group get a group title before the group's first item) | — | — |
icon | Leading icon (@oas-ui/icons registry icon name; shown in the collapsed icon strip) | — | — |
icon-color | Icon color: when set, fixes the icon color (replaces the built-in mono path's currentColor, taking precedence over disabled/active state colors); defaults to currentColor so the color follows states | — | — |
value | Selection value (data-carrier field of the declarative child channel) | — | — |
Slots
| Name | Description |
|---|---|
| default | Sidebar item label content (default slot text); direct child <oas-sidebar-item> (and <oas-sidebar-divider>) elements recursively become the nested children |
oas-sidebar-divider
Slots
| Name | Description |
|---|---|
| default | Divider data carrier (no attributes; the host parses it as {type:"divider"}) |
Parts
root / panel / head / close / nav / body / foot / toggle (desktop collapse) / trigger (mobile trigger) / mask / item; the header and footer content are injected via slot="header", the default slot and slot="footer" respectively.