Components

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".

Available since@crisp-ui-kit/crisp@0.11.0

Installation

npm install @crisp-ui-kit/crisp

Usage

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

Prop

Type

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 ⌘K search 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.

KeyAction
Tab / Shift+TabMoves focus between the switcher, search, and nav rows.
Enter / SpaceActivates the focused control or toggles a section.
↑ / ↓Moves focus to the previous / next nav row.
Home / EndMoves focus to the first / last nav row.

On this page