Sidebar
A composable, themeable application sidebar with collapsible state, mobile sheet overlay, collapsible groups, submenus, badges, and keyboard shortcut (Cmd+B).
Default
Full sidebar with collapsible groups, submenus, badges, and toggle button. Press ⌘B to toggle.
<div class="sidebar-layout">
<aside class="app-sidebar" id="my-sidebar" data-state="expanded">
<div class="sidebar-header">
<a class="sidebar-logo" href="/">MyApp</a>
</div>
<div class="sidebar-content">
<details class="sidebar-group" open>
<summary><span>Platform</span> <i data-lucide="chevron-right"></i></summary>
<nav class="sidebar-nav">
<a class="sidebar-link" href="#" aria-current="page">
<i data-lucide="home"></i> <span>Dashboard</span>
</a>
<a class="sidebar-link" href="#">
<i data-lucide="inbox"></i> <span>Inbox</span>
<span class="sidebar-badge">12</span>
</a>
</nav>
</details>
</div>
<div class="sidebar-footer">user@example.com</div>
</aside>
<main style="flex:1">
<button class="sidebar-trigger" data-sidebar-trigger="my-sidebar">
<i data-lucide="panel-left"></i>
</button>
</main>
</div>
CSS view file
Full sidebar layout with collapsible state, mobile dialog overlay, collapsible groups, submenus, badges, and responsive behavior.
/* -- Sidebar component ------------------------------------------ */
/* Application sidebar with collapsible state, mobile sheet overlay, */
/* collapsible groups, submenus, and keyboard shortcut (Cmd+B). */
@layer components {
/* ── Shell layout ─────────────────────────────────────────── */
.sidebar-layout {
display: flex;
min-height: 100vh;
min-height: 100dvh;
}
/* ── Sidebar panel ────────────────────────────────────────── */
.app-sidebar {
display: flex;
flex-direction: column;
width: var(--sidebar-width, 16rem);
height: 100vh;
height: 100dvh;
position: sticky;
top: 0;
background-color: var(--sidebar);
border-right: 1px solid var(--sidebar-border);
flex-shrink: 0;
transition: width 200ms ease;
overflow: hidden;
z-index: 30;
/* Right side */
&[data-side="right"] {
border-right: none;
border-left: 1px solid var(--sidebar-border);
order: 1;
}
/* Collapsed — icon-only rail */
&[data-state="collapsed"] {
width: var(--sidebar-width-collapsed, 3.5rem);
& .sidebar-link span,
& .sidebar-section-title,
& .sidebar-group-action,
& .sidebar-badge,
& .sidebar-footer,
& .sidebar-logo-text { display: none; }
& .sidebar-link { justify-content: center; padding: 0.5rem; }
& .sidebar-header { justify-content: center; padding: 0.75rem 0.5rem; }
& .sidebar-group > summary { justify-content: center; }
& .sidebar-group > summary span { display: none; }
& .sidebar-group > summary svg:last-child { display: none; }
}
/* Hidden on mobile by default — shown via dialog */
@media (max-width: 767px) {
display: none;
}
}
/* ── Sidebar header ───────────────────────────────────────── */
.sidebar-header {
display: flex;
align-items: center;
padding: 1rem;
gap: 0.5rem;
border-bottom: 1px solid var(--sidebar-border);
flex-shrink: 0;
}
.sidebar-logo {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
font-weight: 600;
color: var(--sidebar-foreground);
text-decoration: none;
}
/* ── Sidebar content (scrollable area) ────────────────────── */
.sidebar-content {
display: flex;
flex-direction: column;
flex: 1;
overflow-y: auto;
overscroll-behavior: contain;
padding: 0.5rem;
gap: 0.5rem;
scrollbar-width: thin;
scrollbar-color: var(--sidebar-border) transparent;
}
/* ── Sidebar nav ──────────────────────────────────────────── */
.sidebar-nav {
display: flex;
flex-direction: column;
gap: 0.125rem;
}
/* ── Nav links ────────────────────────────────────────────── */
.sidebar-link {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
border-radius: var(--radius-md);
font-size: 0.875rem;
color: var(--sidebar-foreground);
text-decoration: none;
transition: background-color 150ms ease;
position: relative;
&:hover {
background-color: var(--sidebar-accent);
color: var(--sidebar-accent-foreground);
}
&[data-active="true"],
&[aria-current="page"] {
background-color: var(--sidebar-accent);
color: var(--sidebar-accent-foreground);
font-weight: 500;
}
&:focus-visible {
outline: 2px solid var(--sidebar-ring);
outline-offset: -2px;
}
& svg {
width: 1rem;
height: 1rem;
flex-shrink: 0;
}
}
/* ── Menu badge ───────────────────────────────────────────── */
.sidebar-badge {
margin-left: auto;
font-size: 0.6875rem;
font-weight: 500;
padding: 0.125rem 0.375rem;
border-radius: var(--radius-sm);
background-color: var(--sidebar-primary);
color: var(--sidebar-primary-foreground);
line-height: 1;
}
/* ── Section title ────────────────────────────────────────── */
.sidebar-section-title {
padding: 0.75rem 0.75rem 0.375rem;
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted-foreground);
}
/* ── Collapsible group (uses <details>) ────────────────────── */
.sidebar-group {
& > summary {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted-foreground);
cursor: pointer;
list-style: none;
border-radius: var(--radius-md);
transition: background-color 150ms ease;
&:hover { background-color: var(--sidebar-accent); }
&::-webkit-details-marker { display: none; }
& svg:last-child {
width: 0.875rem;
height: 0.875rem;
margin-left: auto;
transition: rotate 200ms ease;
}
}
&[open] > summary svg:last-child { rotate: 90deg; }
& > .sidebar-nav { padding: 0.25rem 0 0.25rem 0.75rem; }
}
/* ── Submenu (nested <details>) ────────────────────────────── */
.sidebar-submenu {
& > summary {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
border-radius: var(--radius-md);
font-size: 0.875rem;
color: var(--sidebar-foreground);
cursor: pointer;
list-style: none;
transition: background-color 150ms ease;
&:hover {
background-color: var(--sidebar-accent);
color: var(--sidebar-accent-foreground);
}
&::-webkit-details-marker { display: none; }
& svg:last-child {
width: 0.875rem;
height: 0.875rem;
margin-left: auto;
transition: rotate 200ms ease;
}
}
&[open] > summary svg:last-child { rotate: 90deg; }
& > .sidebar-nav {
padding: 0.25rem 0 0.25rem 1.5rem;
border-left: 1px solid var(--sidebar-border);
margin-left: 0.75rem;
}
}
/* ── Group action button ──────────────────────────────────── */
.sidebar-group-action {
margin-left: auto;
padding: 0.125rem;
border: none;
background: transparent;
color: var(--muted-foreground);
cursor: pointer;
border-radius: var(--radius-sm);
&:hover { color: var(--sidebar-foreground); background-color: var(--sidebar-accent); }
& svg { width: 0.875rem; height: 0.875rem; }
}
/* ── Sidebar footer ───────────────────────────────────────── */
.sidebar-footer {
padding: 0.75rem 1rem;
border-top: 1px solid var(--sidebar-border);
font-size: 0.8125rem;
color: var(--sidebar-foreground);
flex-shrink: 0;
& p { margin: 0; }
}
/* ── Sidebar trigger ──────────────────────────────────────── */
.sidebar-trigger {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border: none;
background: transparent;
color: var(--foreground);
cursor: pointer;
border-radius: var(--radius-md);
&:hover { background-color: var(--accent); }
&:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
& svg { width: 1rem; height: 1rem; }
}
/* ── Mobile sidebar (<dialog> sheet overlay) ──────────────── */
.sidebar-mobile {
border: none;
padding: 0;
margin: 0;
position: fixed;
inset: 0;
width: var(--sidebar-width, 16rem);
max-width: 80vw;
height: 100%;
background-color: var(--sidebar);
color: var(--sidebar-foreground);
z-index: 50;
overflow: hidden;
display: flex;
flex-direction: column;
opacity: 0;
translate: -100% 0;
transition: opacity 200ms ease, translate 200ms ease,
display 200ms allow-discrete;
&[open] {
opacity: 1;
translate: 0 0;
}
&[data-side="right"] {
right: 0;
left: auto;
translate: 100% 0;
&[open] { translate: 0 0; }
}
&::backdrop {
background: oklch(0 0 0 / 0.5);
opacity: 0;
transition: opacity 200ms ease, display 200ms allow-discrete;
}
&[open]::backdrop { opacity: 1; }
& .sidebar-mobile-close {
position: absolute;
top: 0.75rem;
right: 0.75rem;
width: 1.5rem;
height: 1.5rem;
border: none;
background: transparent;
color: var(--sidebar-foreground);
cursor: pointer;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
&:hover { background-color: var(--sidebar-accent); }
& svg { width: 0.875rem; height: 0.875rem; }
}
@media (min-width: 768px) {
display: none;
}
}
@starting-style {
.sidebar-mobile[open] { opacity: 0; translate: -100% 0; }
.sidebar-mobile[open]::backdrop { opacity: 0; }
.sidebar-mobile[data-side="right"][open] { opacity: 0; translate: 100% 0; }
}
/* ── Reduced motion ───────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
.app-sidebar { transition: none; }
.sidebar-link { transition: none; }
.sidebar-group > summary svg:last-child { transition: none; }
.sidebar-submenu > summary svg:last-child { transition: none; }
.sidebar-mobile { transition: none; }
.sidebar-mobile::backdrop { transition: none; }
}
}
JavaScript view file
Toggle collapse, keyboard shortcut (Cmd+B / Ctrl+B), and mobile dialog management.
// -- Sidebar --------------------------------------------------
// Toggle collapse, keyboard shortcut (Cmd+B), and mobile dialog.
function init() {
document.querySelectorAll('.app-sidebar:not([data-init])').forEach((sidebar) => {
sidebar.dataset.init = '';
// -- Toggle button → collapse/expand -----------------------
const triggerId = sidebar.id ? `[data-sidebar-trigger="${sidebar.id}"]` : '.sidebar-trigger';
document.querySelectorAll(triggerId).forEach((trigger) => {
trigger.addEventListener('click', () => {
const state = sidebar.dataset.state === 'collapsed' ? 'expanded' : 'collapsed';
sidebar.dataset.state = state;
});
});
});
// -- Mobile dialog triggers ----------------------------------
document.querySelectorAll('[data-sidebar-mobile]:not([data-init])').forEach((trigger) => {
trigger.dataset.init = '';
const dialog = document.getElementById(trigger.dataset.sidebarMobile);
if (!dialog) return;
trigger.addEventListener('click', () => {
dialog.showModal();
});
// Close button inside the dialog
dialog.querySelectorAll('.sidebar-mobile-close').forEach((btn) => {
btn.addEventListener('click', () => { dialog.close(); });
});
});
}
init();
new MutationObserver(init).observe(document, { childList: true, subtree: true });
// -- Keyboard shortcut: Cmd+B / Ctrl+B ----------------------
if (!document.__sidebarKbInit) {
document.__sidebarKbInit = true;
document.addEventListener('keydown', (e) => {
if ((e.metaKey || e.ctrlKey) && e.key === 'b') {
e.preventDefault();
// Toggle the first sidebar found on the page
const sidebar = document.querySelector('.app-sidebar');
if (sidebar) {
sidebar.dataset.state = sidebar.dataset.state === 'collapsed' ? 'expanded' : 'collapsed';
}
}
});
}