Sidebar
Side panel chrome
Intent
Sidebar is the fixed-width vertical panel that holds primary app navigation -- a brand lockup, the nav itself (sidebar-nav), a user profile or sign-out footer. It owns only the container chrome: width, background, border, scroll, header/footer slots. Contents are consumer-supplied.
Below 64rem viewport width the sidebar hides itself
and a .sidebar-toggle button becomes the navigation
entry point -- consumers wire the toggle to a
drawer holding a copy of the
sidebar's nav. See
app layouts for the
full shell pattern.
Basic usage
.sidebar on an <aside> (or any
block element) wraps a sidebar-nav. Default width is
16rem, background matches
--color-surface, and an inline-end border
separates it from the main content column.
<aside class="sidebar">
<nav class="sidebar-nav" aria-label="Primary">
<a href="#" class="sidebar-link" aria-current="page">
<svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect x="3" y="3" width="7" height="7"></rect>
<rect x="14" y="3" width="7" height="7"></rect>
<rect x="3" y="14" width="7" height="7"></rect>
<rect x="14" y="14" width="7" height="7"></rect>
</svg>
<span class="sidebar-link-text">Dashboard</span>
</a>
<a href="#" class="sidebar-link">
<svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
<span class="sidebar-link-text">Projects</span>
</a>
<a href="#" class="sidebar-link">
<svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>
<span class="sidebar-link-text">Settings</span>
</a>
</nav>
</aside>Header and footer
A direct-child
<header>
or
<footer>
of the sidebar marks the top and bottom regions. Each gets
its own separator border (bottom on the header, top on the
footer) and sits flex-shrink: 0 so the middle
nav scrolls under a fixed header and above a fixed footer.
The common shape: brand lockup in the
<header>,
sidebar-nav
in the middle, user profile or sign-out row in the
<footer>.
<aside class="sidebar">
<header>
<strong>Acme</strong>
</header>
<nav class="sidebar-nav" aria-label="Primary">
<a href="#" class="sidebar-link" aria-current="page">
<svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<rect x="3" y="3" width="7" height="7"></rect>
<rect x="14" y="3" width="7" height="7"></rect>
<rect x="3" y="14" width="7" height="7"></rect>
<rect x="14" y="14" width="7" height="7"></rect>
</svg>
<span class="sidebar-link-text">Dashboard</span>
</a>
<a href="#" class="sidebar-link">
<svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M22 12h-6l-2 3h-4l-2-3H2"></path>
<path d="M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"></path>
</svg>
<span class="sidebar-link-text">Inbox</span>
<span class="sidebar-link-meta badge is-neutral">12</span>
</a>
<a href="#" class="sidebar-link">
<svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="3"></circle>
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
</svg>
<span class="sidebar-link-text">Settings</span>
</a>
</nav>
<footer>
<a href="#" class="sidebar-link">
<svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="8" r="4"></circle>
<path d="M6 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2"></path>
</svg>
<span class="sidebar-link-text">octetic</span>
</a>
</footer>
</aside>Without the inline-end border
.sidebar.no-border drops the inline-end separator.
Use when the sidebar sits against a surface that already
provides visual separation (a card, a different background
tone) and the border would read as redundant or harsh.
<aside class="sidebar no-border">
<nav class="sidebar-nav" aria-label="Secondary">
<a href="#" class="sidebar-link" aria-current="page">
<svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="10"></circle>
</svg>
<span class="sidebar-link-text">Summary</span>
</a>
<a href="#" class="sidebar-link">
<svg class="sidebar-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M3 3h18v18H3z"></path>
</svg>
<span class="sidebar-link-text">Details</span>
</a>
</nav>
</aside>Mobile toggle
Below 64rem viewport width, .sidebar
hides itself via display: none and
.sidebar-toggle (hidden on desktop) reveals as an
inline-flex button. Consumers place the toggle in the mobile
top bar and wire it to a <brio-drawer>
holding a copy of the sidebar-nav for the mobile-only surface.
The toggle uses native
commandfor
invocation -- no bespoke JS.
<button class="sidebar-toggle" type="button" commandfor="mobile-drawer" command="show-modal" aria-label="Open navigation">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
<span>Menu</span>
</button>
The button is hidden in the demo above on wide viewports; narrow
your browser below 64rem to see it revealed (and
the page's own sidebar hidden). For the full shell shape --
sidebar on desktop, top bar + toggle + drawer on mobile --
see app layouts.
Reference
| Name | Role |
|---|---|
.sidebar |
Fixed-width flex column. Sets width, background, inline-end border, vertical scroll. Hidden below 64rem. |
<header> (direct child) |
Top region with a block-end separator border. flex-shrink: 0 so it stays above a scrolling nav. Slot keyed by element, not class. |
<footer> (direct child) |
Bottom region with a block-start separator border. flex-shrink: 0 so it stays below a scrolling nav. Slot keyed by element, not class. |
.sidebar.no-border |
Exception: drops the inline-end separator when the layout already provides visual separation. |
.sidebar-toggle |
Hamburger button for mobile, hidden on desktop. Wire to a <brio-drawer> via commandfor. |
Customization
Default width is 16rem via
--sidebar-width. Full token list and defaults:
src/css/sidebar.css.
Accessibility
-
<aside>is the conventional root. The element provides acomplementarylandmark. Since the sidebar typically holds primary navigation, the<nav>inside (from sidebar-nav) is what screen-reader users rotor-jump to; the outer<aside>is a secondary landmark for the region as a whole. -
One nav source on mobile. When the
desktop sidebar hides and a drawer takes over, the drawer
holds a copy of the same sidebar-nav markup. The hidden
sidebar is
display: noneso it's removed from the accessibility tree -- no duplicate-nav announcement. Wire withcommandforso the toggle button and drawer don't need custom focus management. -
Name the toggle. The hamburger glyph
alone is not announced meaningfully.
aria-label="Open navigation"(or a localized equivalent) gives the button an accessible name. A visible "Menu" text inside the button is also fine -- pick one based on whether the label is visible. -
Keyboard focus returns correctly. The
drawer (via native
<dialog>under the hood) traps focus while open and returns it to the toggle on close -- standard modal behavior, no extra wiring.
CSS Reference: Sidebar
Fixed-width side panel
Source: src/css/sidebar.css
Tokens
| Token | Default | Description |
|---|---|---|
--sidebar-width | 16rem | Width of the sidebar panel |
--sidebar-bg | var(--color-surface) | Background color for the sidebar |
--sidebar-border | var(--border-width-default) solid var(--color-border-subtle) | Inline-end border of the panel |
--sidebar-link-font-size | var(--ui-text-sm) | Font size for sidebar-link rows inside the panel |
--sidebar-header-border | var(--border-width-default) solid var(--color-border-subtle) | Bottom border for the header region |
--sidebar-footer-border | var(--border-width-default) solid var(--color-border-subtle) | Top border for the footer region |
Slots
| Slot | Description |
|---|---|
> header | The header section at the top of a sidebar |
> footer | The footer secton at the bottom of a sidebar |
.sidebar-toggle | Mobile toggle -- hidden on desktop, revealed below 64rem in place of the hidden sidebar. Styled like a minimal button; consumers wire the open action with commandfor targeting a <brio-drawer> by ID. |
Classes
| Class | Description |
|---|---|
.sidebar | component root |
.no-border | Removes the inline-end border |