Sidebar
Vertical navigation panel for app layouts. No fixed positioning, width, or background by default. The layout decides where it sits.
Basic
One content slot, one group, one list. The current page is marked with aria-current="page" on the matching button.
<aside data-stisla-sidebar class="sidebar" style="width: 16rem;">
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<ul class="sidebar__list">
<li class="sidebar__item">
<a class="sidebar__button" href="#" aria-current="page">
<i data-lucide="home"></i>
Dashboard
</a>
</li>
<li class="sidebar__item">
<a class="sidebar__button" href="#">
<i data-lucide="shopping-bag"></i>
Products
</a>
</li>
<li class="sidebar__item">
<a class="sidebar__button" href="#">
<i data-lucide="tags"></i>
Categories
</a>
</li>
<li class="sidebar__item">
<a class="sidebar__button" href="#">
<i data-lucide="users"></i>
Customers
</a>
</li>
</ul>
</div>
</nav>
</div>
</aside>With header + footer
Use .sidebar__header for the brand mark. The .sidebar__brand helper lines up an icon and a wordmark. .sidebar__footer pins to the bottom via margin-top: auto.
<aside data-stisla-sidebar class="sidebar" style="width: 16rem; height: 22rem;">
<header class="sidebar__header">
<a class="sidebar__brand" href="#">
<i data-lucide="hexagon"></i>
<span>Stisla</span>
</a>
</header>
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#" aria-current="page"><i data-lucide="home"></i>Dashboard</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="bar-chart-3"></i>Analytics</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="inbox"></i>Inbox</a></li>
</ul>
</div>
</nav>
</div>
<footer class="sidebar__footer">
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="settings"></i>Settings</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="log-out"></i>Log out</a></li>
</ul>
</footer>
</aside>Sizes
Three sizes are available. .sidebar--sm, default, and .sidebar--lg. The modifier retunes button height, padding, and group gap. Outer panel padding stays the same so gutters read identically across sizes.
<div class="d-flex flex-wrap gap-3 align-items-start">
<aside data-stisla-sidebar class="sidebar sidebar--sm" style="width: 14rem;">
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<span class="sidebar__group-title">Small</span>
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#" aria-current="page"><i data-lucide="home"></i>Dashboard</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="inbox"></i>Inbox</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="users"></i>Customers</a></li>
</ul>
</div>
</nav>
</div>
</aside>
<aside data-stisla-sidebar class="sidebar" style="width: 14rem;">
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<span class="sidebar__group-title">Default</span>
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#" aria-current="page"><i data-lucide="home"></i>Dashboard</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="inbox"></i>Inbox</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="users"></i>Customers</a></li>
</ul>
</div>
</nav>
</div>
</aside>
<aside data-stisla-sidebar class="sidebar sidebar--lg" style="width: 14rem;">
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<span class="sidebar__group-title">Large</span>
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#" aria-current="page"><i data-lucide="home"></i>Dashboard</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="inbox"></i>Inbox</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="users"></i>Customers</a></li>
</ul>
</div>
</nav>
</div>
</aside>
</div>Groups + group action
.sidebar__group-title labels each list. An optional .sidebar__group-action sits to the right of the title, useful for an add or filter button. Its contents collapse to a uniform 24×24 regardless of the button modifiers passed in. Pick a tone via .btn--ghost.btn--neutral (or similar) and the slot handles the chrome.
<aside data-stisla-sidebar class="sidebar" style="width: 17rem;">
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<span class="sidebar__group-title">Workspaces</span>
<div class="sidebar__group-action">
<button type="button" class="btn btn--ghost btn--neutral btn--icon-only" aria-label="Add workspace">
<i data-lucide="plus"></i>
</button>
</div>
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#" aria-current="page"><i data-lucide="briefcase"></i>Acme Co.</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="briefcase"></i>Side Project</a></li>
</ul>
</div>
<div class="sidebar__group">
<span class="sidebar__group-title">Settings</span>
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="settings"></i>General</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="user"></i>Profile</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="credit-card"></i>Billing</a></li>
</ul>
</div>
</nav>
</div>
</aside>Active state
Two hooks. aria-current="page" marks the current page on a navigation link, the standard a11y attribute. data-state="active" covers non-link rows (a button toggling a panel, for example). Both paint the highlight chip. The difference is semantic. It looks the same.
<aside data-stisla-sidebar class="sidebar" style="width: 16rem;">
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="home"></i>Dashboard</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#" aria-current="page"><i data-lucide="inbox"></i>Inbox</a></li>
<li class="sidebar__item"><button type="button" class="sidebar__button" data-state="active"><i data-lucide="filter"></i>Filters open</button></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="users"></i>Customers</a></li>
</ul>
</div>
</nav>
</div>
</aside>Item actions
Place a .sidebar__item-action after the button to drop a badge or a quiet button into the right edge of the row. The button keeps its full click area; the action overlays only its own pixels. Add the --reveal modifier to show the action only on row hover or keyboard focus. This works well in dense lists where always-visible actions feel noisy.
<aside data-stisla-sidebar class="sidebar" style="width: 18rem;">
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<span class="sidebar__group-title">Always visible</span>
<ul class="sidebar__list">
<li class="sidebar__item">
<a class="sidebar__button" href="#"><i data-lucide="bell"></i>Notifications</a>
<span class="sidebar__item-action">
<span class="badge badge--primary">3</span>
</span>
</li>
<li class="sidebar__item">
<a class="sidebar__button" href="#"><i data-lucide="inbox"></i>Inbox</a>
<span class="sidebar__item-action">
<span class="badge">12</span>
</span>
</li>
</ul>
</div>
<div class="sidebar__group">
<span class="sidebar__group-title">Hover-reveal</span>
<ul class="sidebar__list">
<li class="sidebar__item">
<a class="sidebar__button" href="#"><i data-lucide="folder"></i>Documents</a>
<span class="sidebar__item-action sidebar__item-action--reveal">
<button type="button" class="btn btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</span>
</li>
<li class="sidebar__item">
<a class="sidebar__button" href="#"><i data-lucide="folder"></i>Projects</a>
<span class="sidebar__item-action sidebar__item-action--reveal">
<button type="button" class="btn btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</span>
</li>
</ul>
</div>
</nav>
</div>
</aside>Nested submenu
Add data-stisla-sidebar-submenu-toggle to the parent button. Wrap the child .sidebar__list in a .sidebar__submenu inside the same .sidebar__item. The item carries data-state="open" or data-state="closed". The framework JS flips both this and aria-expanded on the trigger when the toggle is clicked, with an animated height transition.
Drop an empty <span class="sidebar__caret"></span> inside the trigger button to render a chevron indicator. The span defaults to the right edge via margin-left: auto and rotates 180° when the parent reports aria-expanded="true". Placement is up to you. Put it after the label for the common right-aligned look, or use your own positioning if you want it elsewhere in the row.
<aside data-stisla-sidebar class="sidebar" style="width: 18rem;">
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<ul class="sidebar__list">
<li class="sidebar__item">
<a class="sidebar__button" href="#" aria-current="page">
<i data-lucide="home"></i>Dashboard
</a>
</li>
<li class="sidebar__item" data-state="open">
<button type="button" class="sidebar__button" data-stisla-sidebar-submenu-toggle aria-expanded="true" aria-controls="demo-reports">
<i data-lucide="bar-chart-3"></i>Reports
<span class="sidebar__caret"></span>
</button>
<div class="sidebar__submenu" id="demo-reports">
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#">Sales</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#">Traffic</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#">Conversion</a></li>
</ul>
</div>
</li>
<li class="sidebar__item" data-state="closed">
<button type="button" class="sidebar__button" data-stisla-sidebar-submenu-toggle aria-expanded="false" aria-controls="demo-billing">
<i data-lucide="credit-card"></i>Billing
<span class="sidebar__caret"></span>
</button>
<div class="sidebar__submenu" id="demo-billing">
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#">Invoices</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#">Payment methods</a></li>
</ul>
</div>
</li>
</ul>
</div>
</nav>
</div>
</aside>As a panel
The sidebar background is transparent by default. To frame it as a standalone panel, set --sidebar-bg to a surface token, add a border, and round the corners.
<aside data-stisla-sidebar class="sidebar" style="width: 17rem; border: 1px solid var(--st-border); border-radius: var(--st-radius); --sidebar-bg: var(--st-surface);">
<header class="sidebar__header">
<a class="sidebar__brand" href="#">
<i data-lucide="hexagon"></i>
<span>Stisla</span>
</a>
</header>
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<span class="sidebar__group-title">Prologue</span>
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#" aria-current="page">Introduction</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#">Installation</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#">Customization</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#">Upgrade guide</a></li>
</ul>
</div>
<div class="sidebar__group">
<span class="sidebar__group-title">Components</span>
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#">Button</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#">Card</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#">Input</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#">Sidebar</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#">Table</a></li>
</ul>
</div>
</nav>
</div>
</aside>Recolor
Every visible part wires to a --sidebar-* CSS variable. Override on the panel itself, on a scoped class, or on :root. The example below tints the sidebar with the primary brand color, paired with --st-primary-foreground for the chip text.
<aside data-stisla-sidebar class="sidebar" style="
width: 17rem;
border-radius: var(--st-radius);
--sidebar-bg: var(--st-primary);
--sidebar-color: var(--st-primary-foreground);
--sidebar-button-hover-bg: color-mix(in oklch, var(--st-primary-foreground) 12%, transparent);
--sidebar-button-hover-color: var(--st-primary-foreground);
--sidebar-button-active-bg: color-mix(in oklch, var(--st-primary-foreground) 20%, transparent);
--sidebar-button-active-color: var(--st-primary-foreground);
--sidebar-button-icon-color: color-mix(in oklch, var(--st-primary-foreground) 75%, transparent);
--sidebar-group-title-color: color-mix(in oklch, var(--st-primary-foreground) 70%, transparent);
--sidebar-submenu-border-color: color-mix(in oklch, var(--st-primary-foreground) 25%, transparent);
">
<header class="sidebar__header">
<a class="sidebar__brand" href="#">
<i data-lucide="hexagon"></i>
<span>Stisla</span>
</a>
</header>
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<span class="sidebar__group-title">Navigation</span>
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#" aria-current="page"><i data-lucide="home"></i>Dashboard</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="inbox"></i>Inbox</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="users"></i>Customers</a></li>
<li class="sidebar__item" data-state="open">
<button type="button" class="sidebar__button" data-stisla-sidebar-submenu-toggle aria-expanded="true" aria-controls="demo-recolor-reports">
<i data-lucide="bar-chart-3"></i>Reports
<span class="sidebar__caret"></span>
</button>
<div class="sidebar__submenu" id="demo-recolor-reports">
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#">Sales</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#">Traffic</a></li>
</ul>
</div>
</li>
</ul>
</div>
<div class="sidebar__group">
<span class="sidebar__group-title">Settings</span>
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="settings"></i>General</a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="user"></i>Profile</a></li>
</ul>
</div>
</nav>
</div>
</aside>Use the same approach for a dark sidebar on a light page, a muted tint, or a per-team brand. Pick the variables you need; the rest stay on defaults.
Rail / mini mode
Add .is-collapsed to the panel to shrink it to icons only. Each button becomes a square with its icon centered. Labels, submenus, and chevrons fade out coordinated with whatever width transition your layout runs. Wrap label text in a <span> so it can be hidden cleanly. Bare text nodes get clipped as a fallback but produce a less smooth transition.
The button size follows .sidebar--sm and .sidebar--lg. The panel width is the layout's job. Match it to the button size for a clean square cell. The recipe is (button-height + 2 × --sidebar-padding-x) × --st-density, which gives 4rem (sm), 4.25rem (default), 4.5rem (lg) at density 1, and scales proportionally at 0.875 or 1.125. Add 2 × border-width on top when the panel carries a border (box-sizing: border-box trims the interior by the border, squeezing the icon cell). The demos below use calc(4rem * var(--st-density) + 2px) for a 1px border at the sm size.
<div class="d-flex flex-wrap gap-3 align-items-start">
<aside data-stisla-sidebar class="sidebar sidebar--sm is-collapsed" style="width: calc(4rem * var(--st-density) + 2px); border: 1px solid var(--st-border); border-radius: var(--st-radius); --sidebar-bg: var(--st-surface);">
<header class="sidebar__header">
<a class="sidebar__brand" href="#" aria-label="Stisla"><i data-lucide="hexagon"></i><span>Stisla</span></a>
</header>
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#" aria-current="page"><i data-lucide="home"></i><span>Dashboard</span></a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="inbox"></i><span>Inbox</span></a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="users"></i><span>Customers</span></a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="settings"></i><span>Settings</span></a></li>
</ul>
</div>
</nav>
</div>
</aside>
<aside data-stisla-sidebar class="sidebar is-collapsed" style="width: calc(4.25rem * var(--st-density) + 2px); border: 1px solid var(--st-border); border-radius: var(--st-radius); --sidebar-bg: var(--st-surface);">
<header class="sidebar__header">
<a class="sidebar__brand" href="#" aria-label="Stisla"><i data-lucide="hexagon"></i><span>Stisla</span></a>
</header>
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#" aria-current="page"><i data-lucide="home"></i><span>Dashboard</span></a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="inbox"></i><span>Inbox</span></a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="users"></i><span>Customers</span></a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="settings"></i><span>Settings</span></a></li>
</ul>
</div>
</nav>
</div>
</aside>
<aside data-stisla-sidebar class="sidebar sidebar--lg is-collapsed" style="width: calc(4.5rem * var(--st-density) + 2px); border: 1px solid var(--st-border); border-radius: var(--st-radius); --sidebar-bg: var(--st-surface);">
<header class="sidebar__header">
<a class="sidebar__brand" href="#" aria-label="Stisla"><i data-lucide="hexagon"></i><span>Stisla</span></a>
</header>
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#" aria-current="page"><i data-lucide="home"></i><span>Dashboard</span></a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="inbox"></i><span>Inbox</span></a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="users"></i><span>Customers</span></a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><i data-lucide="settings"></i><span>Settings</span></a></li>
</ul>
</div>
</nav>
</div>
</aside>
</div>Collapse toggle
Add data-stisla-sidebar-toggle="collapse" to any element to flip the panel between expanded and rail. The framework JS toggles .is-collapsed on the closest [data-stisla-sidebar], or, when the trigger lives outside the sidebar, on the panel referenced by aria-controls. The trigger's aria-expanded stays in sync; bubble up stisla:sidebar:collapse-change to react to the state in your own code.
Pair the toggle with the opt-in --sidebar-width and --sidebar-width-collapsed variables and the width animates pure-CSS, with no inline width changes and no script. The width transition rides --sidebar-transition.
Inside an .app-shell, prefer data-stisla-app-shell-toggle="collapse" instead. Stisla.AppShell flips .is-sidebar-collapsed on the shell and delegates to the sidebar's collapse()/expand() API in one click, keeping the panel's .is-collapsed + submenu close + ARIA all in sync.
<aside id="demo-toggleable-sidebar" data-stisla-sidebar class="sidebar position-absolute" style="inset: 0 auto 0 0; --sidebar-width: 18rem; --sidebar-width-collapsed: calc(4.25rem * var(--st-density) + 1px); border-right: 1px solid var(--st-border); --sidebar-bg: var(--st-surface);">
<header class="sidebar__header">
<a class="sidebar__brand" href="#">
<i data-lucide="hexagon"></i>
<span>Acme</span>
</a>
</header>
<div class="sidebar__content">
<nav class="sidebar__menu">
<div class="sidebar__group">
<span class="sidebar__group-title">Workspace</span>
<div class="sidebar__group-action">
<button type="button" class="btn btn--ghost btn--neutral btn--icon-only" aria-label="Add workspace">
<i data-lucide="plus"></i>
</button>
</div>
<ul class="sidebar__list">
<li class="sidebar__item">
<a class="sidebar__button" href="#" aria-current="page">
<i data-lucide="home"></i><span>Dashboard</span>
</a>
</li>
<li class="sidebar__item" data-state="open">
<button type="button" class="sidebar__button" data-stisla-sidebar-submenu-toggle aria-expanded="true" aria-controls="demo-toggleable-analytics">
<i data-lucide="bar-chart-3"></i><span>Analytics</span>
<span class="sidebar__caret"></span>
</button>
<div class="sidebar__submenu" id="demo-toggleable-analytics">
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#"><span>Sales</span></a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><span>Traffic</span></a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><span>Conversion</span></a></li>
</ul>
</div>
</li>
<li class="sidebar__item">
<a class="sidebar__button" href="#">
<i data-lucide="inbox"></i><span>Inbox</span>
</a>
<span class="sidebar__item-action">
<span class="badge badge--primary">12</span>
</span>
</li>
</ul>
</div>
<div class="sidebar__group">
<span class="sidebar__group-title">Library</span>
<ul class="sidebar__list">
<li class="sidebar__item">
<a class="sidebar__button" href="#">
<i data-lucide="folder"></i><span>Documents</span>
</a>
</li>
<li class="sidebar__item">
<a class="sidebar__button" href="#">
<i data-lucide="folder-open"></i><span>Projects</span>
</a>
<span class="sidebar__item-action sidebar__item-action--reveal">
<button type="button" class="btn btn--ghost btn--neutral btn--icon-only" aria-label="More">
<i data-lucide="more-horizontal"></i>
</button>
</span>
</li>
<li class="sidebar__item">
<a class="sidebar__button" href="#">
<i data-lucide="layout-template"></i><span>Templates</span>
</a>
</li>
</ul>
</div>
<div class="sidebar__group">
<span class="sidebar__group-title">Team</span>
<ul class="sidebar__list">
<li class="sidebar__item">
<a class="sidebar__button" href="#">
<i data-lucide="users"></i><span>Members</span>
</a>
</li>
<li class="sidebar__item" data-state="closed">
<button type="button" class="sidebar__button" data-stisla-sidebar-submenu-toggle aria-expanded="false" aria-controls="demo-toggleable-settings">
<i data-lucide="settings"></i><span>Settings</span>
<span class="sidebar__caret"></span>
</button>
<div class="sidebar__submenu" id="demo-toggleable-settings">
<ul class="sidebar__list">
<li class="sidebar__item"><a class="sidebar__button" href="#"><span>General</span></a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><span>Profile</span></a></li>
<li class="sidebar__item"><a class="sidebar__button" href="#"><span>Billing</span></a></li>
</ul>
</div>
</li>
</ul>
</div>
</nav>
</div>
<footer class="sidebar__footer">
<ul class="sidebar__list">
<li class="sidebar__item">
<a class="sidebar__button" href="#">
<i data-lucide="life-buoy"></i><span>Help & support</span>
</a>
</li>
<li class="sidebar__item">
<button type="button" class="sidebar__button" data-stisla-sidebar-toggle="collapse" aria-expanded="true">
<i data-lucide="panel-left-close"></i><span>Collapse</span>
</button>
</li>
</ul>
</footer>
</aside>
<div style="height: 500px"></div>Any open submenus close in sync with the width transition. The framework calls closeAllSubmenus() as part of the collapse so each height animation runs alongside the rail shrink. Expanding back doesn't reopen them; the user clicks back in to whichever menu they want. To drive the toggle from outside the sidebar (a topbar button, a keyboard shortcut), point at it with aria-controls.
<button data-stisla-sidebar-toggle="collapse"
aria-controls="my-sidebar"
aria-expanded="true">Toggle sidebar</button>
Or call the API directly with Stisla.get(sidebarEl).toggleCollapsed(), .collapse(), .expand(), .isCollapsed(). Persist the state to localStorage by listening for stisla:sidebar:collapse-change.
Customization
Thirty-one variables retune .sidebar without touching component CSS. Override on the panel, a parent scope, or :root. Density-tuned values multiply --st-density automatically.
Shell
| Variable | Default | Use |
|---|---|---|
--sidebar-bg |
transparent | Panel background |
--sidebar-color |
var(--st-foreground) | Panel foreground (brand + buttons inherit) |
--sidebar-padding-y |
calc(1rem * var(--st-density)) | Top + bottom padding on header / footer |
--sidebar-padding-x |
calc(1rem * var(--st-density)) | Panel outer gutter. Feeds the rail-width recipe. |
--sidebar-gap |
calc(0.75rem * var(--st-density)) | Space between header, content, footer |
--sidebar-width |
auto | Expanded panel width. Opt-in. Set it when you want the sidebar to own its width and animate the collapse toggle. Loses to inline width and to a parent that sizes the panel externally. |
--sidebar-width-collapsed |
auto | Rail width applied when .is-collapsed is present. Pair with --sidebar-width; the class flip animates pure-CSS via --sidebar-transition. |
Brand
| Variable | Default | Use |
|---|---|---|
--sidebar-brand-color |
var(--sidebar-color) | Brand text color |
--sidebar-brand-icon-size |
1.5rem | Brand icon dimensions; load-bearing for rail re-center math |
--sidebar-brand-gap |
0.625rem | Space between brand icon and wordmark |
Button (item)
| Variable | Default | Use |
|---|---|---|
--sidebar-button-height |
2.25rem | Hard height; multiplied by --st-density |
--sidebar-button-padding-x |
calc(0.625rem * var(--st-density)) | Horizontal inset. Load-bearing for rail symmetry. padding-x + icon-size + padding-x equals button-height so the icon centers naturally at rail width without any justify-content override. |
--sidebar-button-radius |
var(--st-radius-sm) | Corner radius |
--sidebar-button-gap |
calc(1rem * var(--st-density)) | Space between icon and label |
--sidebar-button-font-weight |
400 | Label weight |
--sidebar-button-color |
var(--sidebar-color) | Rest text color |
--sidebar-button-hover-bg |
var(--st-accent) | Hover chip fill |
--sidebar-button-hover-color |
var(--st-accent-foreground) | Hover text color |
--sidebar-button-active-bg |
var(--st-highlight) | Active chip fill (aria-current="page" or data-state="active") |
--sidebar-button-active-color |
var(--st-highlight-foreground) | Active text color |
--sidebar-button-icon-size |
1rem | Icon dimensions; load-bearing for rail recipe (see --sidebar-button-padding-x) + chevron |
--sidebar-button-icon-color |
var(--st-muted-foreground) | Icon color at rest + hover. Active rows flip the icon to --sidebar-button-active-color so the row reads unified. |
Item action
| Variable | Default | Use |
|---|---|---|
--sidebar-item-action-size |
2.25rem | Right-edge slot reserved size (matches button height so the slot occupies the full row vertically). A .btn inside still collapses to 1.5rem × 1.5rem via internal override. |
Group
| Variable | Default | Use |
|---|---|---|
--sidebar-group-gap |
calc(1rem * var(--st-density)) | Space between groups in the menu |
--sidebar-group-title-font-size |
0.75rem | Caption text size |
--sidebar-group-title-font-weight |
400 | Caption weight |
--sidebar-group-title-color |
var(--st-muted-foreground) | Caption color (also tints .sidebar__group-action) |
Submenu
| Variable | Default | Use |
|---|---|---|
--sidebar-submenu-border-color |
var(--st-border) | Guide-line color |
--sidebar-submenu-padding-start |
0.5rem | Space between the guide line and submenu rows |
--sidebar-submenu-margin-start |
1.125rem | Inset from the panel edge; aligns the guide line on the parent icon's center axis |
Motion
| Variable | Default | Use |
|---|---|---|
--sidebar-transition |
0.3s ease | Rail-mode soft-hide transition. Zeroed under prefers-reduced-motion. |