Sidebar
<aparte-sidebar>
The column beside the chat — conversations, a new-chat button, a search field, an account row — as an element, because it has behaviour: it collapses, it becomes a drawer on a narrow window, and its search field filters the conversation list.
What it does NOT own is its content. Children are yours, in the order you want them:
a .aparte-sidebar__header, a .aparte-sidebar__search, a .aparte-sidebar__body
holding the <aparte-conversation-list>, a .aparte-sidebar__footer. The
stylesheet lays those out and draws nothing you did not put there — the recipe
(.aparte-sidebar, in styles/shell/sidebar.css) is the look, this element is
the three behaviours.
Collapse. collapsed is an attribute, reflected, so the host can set it, read
it and persist it. Any element carrying data-aparte-sidebar-toggle anywhere on
the page toggles the nearest sidebar (or the one whose id the attribute names), so
a hamburger in the header needs no script — and the sidebar keeps aria-expanded
and aria-controls on that control in step, whoever changed the state (it gives
itself an id when the host wrote none). aparte-sidebar-toggle fires on every
change, whoever caused it.
Drawer. Under 48rem of window the sidebar leaves the flow and slides over the
page (data-drawer, set by the element from a media query); open, it draws a
scrim, moves the focus to its first focusable child, closes on Escape from anywhere
on the page or on a click outside, and hands the focus back to the control that
opened it. Nothing here is a portal: the drawer is the same element in the same
place, positioned fixed, which is all a sidebar needs and what a dialog would not
get away with. Collapsed — folded as a column or slid off as a drawer — it carries
inert and aria-hidden, so nothing invisible keeps a tab stop.
Search. An input carrying data-aparte-sidebar-search filters the conversation
list below it by title as the user types — rows that do not match are hidden, and a
date group with nothing left hides with them. Client-side, on the titles the list
already has: an index over message bodies is the storage adapter’s business.
Example
Section titled “Example”<!-- The sidebar alone, as a column: its header, a search field that filters the list, the list, a footer. The shell it sits in — header, chat — is the app-shell guide's. breakpoint="none" keeps it a column at any width; without it, under 48rem of window it becomes a drawer behind a [data-aparte-sidebar-toggle] control. --><aparte-sidebar breakpoint="none" style="height: 22rem"> <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: '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() - 60 * day }, ];</script>Attributes
Section titled “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
Section titled “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
Section titled “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
Section titled “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. |
Theming
Section titled “Theming”Override any of these on :root, on a subtree, or on one instance — custom properties inherit downward. Some are this element’s own; others are site-wide tokens that also style it, and overriding one of those at :root moves everything that reads it. The full set is in the CSS variables reference.
<aparte-sidebar>
Section titled “<aparte-sidebar>”| 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. |
In a framework
Section titled “In a framework”The element is the same object everywhere — the tag does not change. What changes is how an attribute is written and how an event reaches you.
<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.