Sidebar
The primary app-shell navigation rail — workspace switcher, search, record lists, and collapsible sections.
Sidebar is the first entry in crisp's Product layer — components built by composing the primitives (Button, Badge, Avatar, Kbd, NavItem) into real app-shell pieces, rather than being primitives themselves. It's an active work in progress.
Measured from a live left rail: 275px fixed width, workspace switcher, ⌘K search, and collapsible Records / Lists / Chats sections.
<Sidebar /> with no props renders that exact measured reference — pass items/recordsSection/chatsSection/workspace/footer to swap in your own nav and data while keeping the same chrome.
For a nav shape that isn't Records/Lists/Chats — any number of named, collapsible groups, with items that can arrive at runtime — pass groups instead. It replaces those three sections entirely, reusing the same section markup and recipe:
<Sidebar
groups={[
{ label: "Governance", items: [{ label: "Policies", icon: ShieldCheck }] },
{
label: "Identity",
items: [{ label: "Users", href: "/users", active: true, count: 12 }],
},
]}
/>footer defaults to Attio's own onboarding pill + invite row + trial banner. Pass a
{ gettingStartedLabel, inviteLabel, trialText, trialActionLabel } object to override just
that copy (as before), any other node — an account/user menu, say — to replace the slot's
content entirely (it stays pinned to the bottom of the nav), or false to render no footer
at all so you can compose your own element directly below <Sidebar> instead:
<Sidebar
footer={
<button onClick={signOut}>
<Avatar>{user.initial}</Avatar>
{user.name}
</button>
}
/>Pinned header, overlay scrollbar
The workspace row, search and inbox button stay fixed; only the nav list scrolls. Its native scrollbar is hidden and replaced by a 6px overlay thumb, so rows stay 258px wide whether or not the list overflows.
Sections
Sections animate their height (~145ms) when toggled. To remember which are collapsed, control them: a section's key is "records", "lists", "chats", or a groups entry's key ?? label.
<Sidebar
collapsedSections={collapsed}
onCollapsedChange={(next) => {
setCollapsed(next);
localStorage.setItem("collapsed", JSON.stringify(next));
}}
/>Header actions are SidebarSectionActions: 20px hover-only icon buttons whose label is both accessible name and tooltip.
groups={[{
label: "Lists",
actions: <SidebarSectionAction label="Add list" icon={<Plus size={16} />} onClick={create} />,
items,
}]}A groups row can show its icon on a coloured 16px tile (tone, for objects and registers), a muted type label (meta), and a hover-only remove button (onRemove, with removeLabel) for favourites. Give a group onReorder(from, to) and its rows drag-reorder: grab anywhere on a row, a ghost follows the pointer, a 2px line marks the drop. crisp reports the move; you reorder items.
The search pill shows platform-aware chips (Ctrl + K, or ⌘ + K on Apple); override with searchShortcut. Pass toggleShortcut={["Ctrl", "."]} to show the collapse hotkey in its tooltip (crisp shows hotkeys, it does not bind them). Rows with a shortcut show Attio's hotkey tooltip on hover and keyboard focus, and the active row has aria-current="page".
Installation
npm install @crisp-ui-kit/crispUsage
import { Sidebar } from "@crisp-ui-kit/crisp";
export function AppShell({ children }: { children: React.ReactNode }) {
return (
<div style={{ display: "flex" }}>
<Sidebar />
<main>{children}</main>
</div>
);
}Props
Sidebar renders the measured content above by default, so <Sidebar /> with no props is byte-identical to the reference. Every content prop is optional and independently overridable — pass your own items/recordsSection/etc. to drive the same chrome with real app data.
Composition
Sidebar is built entirely from other crisp components, dogfooding the library:
Avatar— the workspace tile (shape="rounded" tone="cyan") and each record type's colored glyph tile.Button— the collapse-panel icon button, the inbox icon button, and the "Getting started 1/6" pill.Badge— the "1" count next to the active Tasks item (variant="solid" size="small").NavItem(Product) — every row in the main nav, record list, and chats section.Kbd— the⌘Ksearch shortcut hint.
Accessibility
The workspace switcher, search field, inbox button, and every nav row are focusable and reachable by keyboard. Collapsible sections (Records, Lists, Chats) are real <button> elements with aria-expanded, so their state is announced to screen readers. Icon-only buttons (collapse panel, inbox) carry aria-label.
| Key | Action |
|---|---|
Tab / Shift+Tab | Moves focus between the switcher, search, and nav rows. |
Enter / Space | Activates the focused control or toggles a section. |
↑ / ↓ | Moves focus to the previous / next nav row. |
Home / End | Moves focus to the first / last nav row. |