Sidebar
The column beside the chat — conversations, a new-chat button, a search field, an account row — that folds away and becomes a drawer on a narrow window.
-
<aparte-sidebar> -
.aparte-sidebar
On this page
<!-- breakpoint="none": a column at any width. Type in the search field to filter the list. -->
<aparte-sidebar breakpoint="none" style="height: 24rem">
<div class="aparte-sidebar__header">
<span class="aparte-sidebar__brand">aparté</span>
<button class="aparte-btn aparte-btn--icon aparte-btn--sm" type="button" aria-label="New chat">
<aparte-icon name="edit"></aparte-icon>
</button>
</div>
<div class="aparte-sidebar__search aparte-field-group">
<input class="aparte-field aparte-field--sm" type="search" placeholder="Search conversations" data-aparte-sidebar-search>
</div>
<div class="aparte-sidebar__body">
<aparte-conversation-list active-id="c1"></aparte-conversation-list>
</div>
<div class="aparte-sidebar__footer">
<span class="aparte-avatar aparte-avatar--sm">P</span> Paul
</div>
</aparte-sidebar>
<script>
const day = 864e5;
document.querySelector('aparte-conversation-list').conversations = [
{ id: 'c0', title: 'Release checklist', updatedAt: Date.now() - 3 * day, pinnedAt: Date.now() },
{ id: 'c1', title: 'Deploy checklist', updatedAt: Date.now() },
{ id: 'c2', title: 'Rename the segment types', updatedAt: Date.now() - day },
{ id: 'c3', title: 'Tokens, not selectors', updatedAt: Date.now() - 4 * day },
{ id: 'c4', title: 'The first release', updatedAt: Date.now() - 20 * day },
];
</script><aparte-sidebar> is the column, with the three behaviours a sidebar needs. It folds:
collapsed is an attribute you can set, read and save. It becomes a drawer on a narrow window.
And its search field filters the list of conversations below it as the reader types, hiding a
date group once nothing in it matches.
Its content is yours, in the order you want: a .aparte-sidebar__header, a
.aparte-sidebar__search, a .aparte-sidebar__body holding the
conversation list, a .aparte-sidebar__footer. The stylesheet
lays them out and draws nothing you did not put there. Without the element, <aside class="aparte-sidebar"> is the same look with no behaviour.
Variants
Section titled “Variants”As a drawer
Section titled “As a drawer”<!-- Under its breakpoint the sidebar slides over the page. A breakpoint wider than this
frame keeps it a drawer here: press the button, then Escape or the scrim to close it. -->
<div style="height: 24rem">
<button class="aparte-btn aparte-btn--surface" type="button" data-aparte-sidebar-toggle>
<aparte-icon name="menu"></aparte-icon> Conversations
</button>
<aparte-sidebar breakpoint="200rem" collapsed>
<div class="aparte-sidebar__header">
<span class="aparte-sidebar__brand">aparté</span>
<button class="aparte-btn aparte-btn--icon aparte-btn--sm" type="button" aria-label="New chat">
<aparte-icon name="edit"></aparte-icon>
</button>
</div>
<div class="aparte-sidebar__search aparte-field-group">
<input class="aparte-field aparte-field--sm" type="search" placeholder="Search conversations" data-aparte-sidebar-search>
</div>
<div class="aparte-sidebar__body">
<aparte-conversation-list active-id="c1"></aparte-conversation-list>
</div>
<div class="aparte-sidebar__footer">
<span class="aparte-avatar aparte-avatar--sm">P</span> Paul
</div>
</aparte-sidebar>
</div>
<script>
const day = 864e5;
document.querySelector('aparte-conversation-list').conversations = [
{ id: 'c0', title: 'Release checklist', updatedAt: Date.now() - 3 * day, pinnedAt: Date.now() },
{ id: 'c1', title: 'Deploy checklist', updatedAt: Date.now() },
{ id: 'c2', title: 'Rename the segment types', updatedAt: Date.now() - day },
{ id: 'c3', title: 'Tokens, not selectors', updatedAt: Date.now() - 4 * day },
{ id: 'c4', title: 'The first release', updatedAt: Date.now() - 20 * day },
];
</script>Under breakpoint (48rem of window by default, or none for a column that never changes), the
sidebar slides over the page. Open, it dims the page behind a scrim, moves the focus inside,
closes on Escape or a click outside, and gives the focus back to the button that opened it.
<aparte-sidebar collapsed=""></aparte-sidebar>el.addEventListener('aparte-sidebar-toggle', (e) => use(e.detail));<aparte-sidebar collapsed=""></aparte-sidebar>The aparte-* tags are typed JSX intrinsics as soon as you import from @aparte/react. A presence attribute takes '', never true — React stringifies it, and collapsed={false} would render collapsed="false", which hasAttribute reads as on. Events reach you by ref, typed through the DOM.
<template>
<aparte-sidebar collapsed=""
@aparte-sidebar-toggle="(e) => use(e.detail)"
></aparte-sidebar>
</template>Declared through Vue’s GlobalComponents, so vue-tsc checks the tag in any template. A presence attribute takes '' to set and null to remove, never false.
<aparte-sidebar collapsed=""
on:aparte-sidebar-toggle={(e) => use(e.detail)}
></aparte-sidebar>Declared through SvelteHTMLElements, so svelte-check covers the attributes and the on: handlers. A presence attribute takes '', never false.
import { AparteSidebarDirective } from '@aparte/angular';<aparte-sidebar [collapsed]="true"
(sidebarToggle)="use($event)"></aparte-sidebar>A standalone directive whose selector IS the tag, so the real element sits in the template — @if, @for and content projection all reach it — and no CUSTOM_ELEMENTS_SCHEMA is needed.
Installation and the framework-specific traps: React · Vue · Svelte · Angular.
Any element carrying data-aparte-sidebar-toggle toggles the nearest sidebar, or the one whose id
the attribute names, so a button in the header needs no script.
aparte-sidebar-toggle fires on every change, whoever caused it. The search field is any input
with data-aparte-sidebar-search; it filters by title, on the rows the list already has.
Customize
Section titled “Customize”| Variable | What it sets |
|---|---|
--aparte-sidebar-width | The column’s width, and the drawer’s (260px). |
--aparte-sidebar-inset | The inline inset of its four regions. |
--aparte-sidebar-bg | Its background. |
<aparte-sidebar> · class AparteSidebar
Attributes
| Attribute | Description |
|---|---|
collapsed | Hidden (in the flow) or closed (as a drawer). Reflected; set it to start closed. |
breakpoint | The window width under which the sidebar becomes a drawer: a length (default 48rem), or none for a column that never does. |
data-drawer | Reflected BY the element while the window is narrower than breakpoint (48rem unless you set it). Read-only. |
Properties
| Property | Type | Description |
|---|---|---|
collapsed | boolean | Whether the sidebar is collapsed (hidden in the flow, or closed as a drawer). |
drawer (readonly) | boolean | True while the window is under the breakpoint and the sidebar is a drawer. |
Methods
| Method | Description |
|---|---|
open(opener?: HTMLElement): void | Open the sidebar. opener is the control to hand the focus back to when it closes. |
close(): void | |
toggle(opener?: HTMLElement): void | |
filter(query: string): void | Hide the conversation rows whose title does not contain query, and the date groups left empty. Case- and accent-insensitive. An empty query shows everything. |
Events
| Event | Type | Description |
|---|---|---|
aparte-sidebar-toggle | CustomEvent<AparteSidebarToggleDetail> | The sidebar opened or closed, by a toggle, by Escape, by a click on the scrim or by collapsed being set. Bubbles. |
CSS variables
| Variable | Default | Description |
|---|---|---|
--aparte-sidebar-width | 260px | Width of the column, and of the drawer. |
--aparte-sidebar-inset | var(--aparte-space-3) | The inline inset of the four regions (header, search, body, footer): one value, so their content stands on one axis. |
--aparte-sidebar-bg | var(--aparte-surface-2) | Its ground. |
Classes
Section titled “Classes”.aparte-sidebar · .aparte-sidebar__body · .aparte-sidebar__brand · .aparte-sidebar__footer · .aparte-sidebar__header · .aparte-sidebar__scrim · .aparte-sidebar__search
Accessibility
Section titled “Accessibility”- A closed sidebar, folded or slid away, is
inertandaria-hidden: nothing invisible keeps a tab stop. - The toggle’s
aria-expandedandaria-controlsfollow the state; the sidebar gives itself an id when it has none. - The open drawer takes the focus and gives it back on close.
Related
Section titled “Related”- Conversation list — what the body holds.
- App shell — the grid it sits in.
- Header — where its toggle goes.