# Sidebar

> The desktop column next to the rail that lists what the selected destination contains, with rows that mark where you are, what is unread and what mentions you.

Source: https://raft-design.botiverse.build/components/sidebar. Generated from the current release; where it differs from what you remember, this page is right.

The sidebar is the desktop column between the rail and the main area. It lists what the destination selected on the [AppRail](https://raft-design.botiverse.build/components/app-rail.md) contains: channels, agents and people, for example. Its header names the destination, and its rows show where you are, what is unread and what mentions you.

```tsx
import { AtSign, BellOff, Bookmark, Hash, Pencil } from "lucide-react";
import { useCallback, useState } from "react";
import type { ReactNode } from "react";
import {
  Avatar,
  AvatarFallback,
  AvatarGroup,
  Status,
  SidebarLiveActivity,
  SidebarContent,
  SidebarItemAside,
  SidebarItemCount,
  SidebarBody,
  SidebarHeader,
  SidebarHeaderTitle,
  SidebarItem,
  SidebarItemIcon,
  SidebarItemChannelIcon,
  SidebarItemIndicator,
  SidebarItemMetaIcon,
  SidebarItemContent,
  SidebarItemStatus,
  SidebarItemSubtitle,
  SidebarItemTitle,
  SidebarList,
  SidebarMenu,
  SidebarMenuAside,
  SidebarMenuCount,
  SidebarMenuIcon,
  SidebarMenuTitle,
  SidebarRoot,
  SidebarSection,
  SidebarSectionEmpty,
  SidebarSectionControls,
  SidebarSectionAttention,
  SidebarSectionDisclosure,
  SidebarSectionChevron,
  SidebarSectionHeader,
  SidebarSectionTitle,
  SidebarSectionTrailing,
  Text,
} from "@botiverse/raft-ui";

import {
  SidebarSectionAddButton,
  SidebarSectionChannelsMenu,
  SidebarSectionHeaderMenu,
  SidebarSectionSortMenu,
} from "#/modules/dropdown-menu/compositions/sidebar-section-header-menu.composition.tsx";
import { LiveAgentActivityBarComposition } from "#/modules/live-agent-activity-bar/live-agent-activity-bar.composition.tsx";
import type { LiveAgentActivityBarItem } from "#/modules/live-agent-activity-bar/live-agent-activity-bar.composition.tsx";

export type SidebarContentState = "populated" | "empty";
export type SidebarLiveActivityState = "none" | "active";

export type SidebarCompositionProps = {
  contentState?: SidebarContentState;
  liveActivityState?: SidebarLiveActivityState;
};

const LIVE_ACTIVITY: Record<SidebarLiveActivityState, LiveAgentActivityBarItem | null> = {
  none: null,
  active: { text: "Preparing the next release candidate" },
};

function SidebarDestinationItem({
  children,
  destination,
  selected,
  onOpen,
}: {
  children: ReactNode;
  destination: string;
  selected: boolean;
  onOpen: (destination: string) => void;
}) {
  const handleClick = useCallback(() => onOpen(destination), [destination, onOpen]);

  return (
    <SidebarItem type="button" active={selected} onClick={handleClick}>
      {children}
    </SidebarItem>
  );
}

export function SidebarComposition({
  contentState = "populated",
  liveActivityState = "none",
}: SidebarCompositionProps) {
  const [selectedItem, setSelectedItem] = useState("launch-week");
  const [announcement, setAnnouncement] = useState({ message: "Opened launch-week", sequence: 0 });
  const isEmpty = contentState === "empty";

  const announce = useCallback((message: string) => {
    setAnnouncement((current) => ({ message, sequence: current.sequence + 1 }));
  }, []);

  const openDestination = useCallback(
    (destination: string) => {
      setSelectedItem(destination);
      announce(`Opened ${destination}`);
    },
    [announce],
  );

  const handleOpenSaved = useCallback(() => openDestination("Saved"), [openDestination]);

  return (
    <SidebarRoot className="overflow-x-clip">
      <SidebarHeader>
        <div className="min-w-0 flex-1">
          <SidebarHeaderTitle>Chat</SidebarHeaderTitle>
        </div>
        <span className="sr-only" aria-live="polite">
          <span key={announcement.sequence}>{announcement.message}</span>
        </span>
        <span className="flex items-center gap-1.5" role="img" aria-label="2 agents working">
          <AvatarGroup aria-hidden>
            <Avatar size="xs" type="agent" />
            <Avatar size="xs" type="agent" />
          </AvatarGroup>
          <Text.Mono size="meta">2</Text.Mono>
        </span>
      </SidebarHeader>
      <SidebarBody>
        <SidebarContent className="min-h-0">
          <div className="min-h-full">
            <SidebarSection>
              <SidebarSectionHeader className="mt-0">
                <SidebarMenu
                  aria-current={selectedItem === "Saved" ? "page" : undefined}
                  onClick={handleOpenSaved}
                >
                  <SidebarMenuIcon>
                    <Bookmark />
                  </SidebarMenuIcon>
                  <SidebarMenuTitle>Saved</SidebarMenuTitle>
                  <SidebarMenuAside>
                    <SidebarMenuCount>2</SidebarMenuCount>
                  </SidebarMenuAside>
                </SidebarMenu>
              </SidebarSectionHeader>
            </SidebarSection>

            <SidebarSection>
              <SidebarSectionHeaderMenu menu="sort">
                <SidebarSectionDisclosure type="button">
                  <SidebarSectionTitle>Pinned</SidebarSectionTitle>
                  <SidebarSectionChevron />
                </SidebarSectionDisclosure>
                <SidebarSectionTrailing>
                  <SidebarSectionControls>
                    <SidebarSectionSortMenu aria-label="Sort pinned" />
                  </SidebarSectionControls>
                </SidebarSectionTrailing>
              </SidebarSectionHeaderMenu>
              {isEmpty ? (
                <SidebarSectionEmpty>No pinned conversations yet</SidebarSectionEmpty>
              ) : (
                <SidebarList>
                  <SidebarDestinationItem
                    destination="launch-week"
                    selected={selectedItem === "launch-week"}
                    onOpen={openDestination}
                  >
                    <SidebarItemChannelIcon aria-hidden>
                      <Hash />
                    </SidebarItemChannelIcon>
                    <SidebarItemContent>
                      <SidebarItemTitle className="shrink-0">launch-week</SidebarItemTitle>
                      <SidebarItemSubtitle>Martin replied</SidebarItemSubtitle>
                    </SidebarItemContent>
                  </SidebarDestinationItem>
                  <SidebarDestinationItem
                    destination="Bugen planning"
                    selected={selectedItem === "Bugen planning"}
                    onOpen={openDestination}
                  >
                    <SidebarItemIcon>
                      <Avatar size="2xs" type="agent">
                        <AvatarFallback className="text-[9px] font-bold">B</AvatarFallback>
                      </Avatar>
                    </SidebarItemIcon>
                    <SidebarItemContent>
                      <SidebarItemTitle>Bugen</SidebarItemTitle>
                      <SidebarItemSubtitle>planning</SidebarItemSubtitle>
                    </SidebarItemContent>
                    <SidebarItemAside>
                      <SidebarItemStatus>
                        <Status aria-hidden variant="success" />
                      </SidebarItemStatus>
                    </SidebarItemAside>
                  </SidebarDestinationItem>
                  <SidebarDestinationItem
                    destination="release-notes"
                    selected={selectedItem === "release-notes"}
                    onOpen={openDestination}
                  >
                    <SidebarItemChannelIcon aria-hidden>
                      <Hash />
                    </SidebarItemChannelIcon>
                    <SidebarItemContent>
                      <SidebarItemTitle>release-notes</SidebarItemTitle>
                    </SidebarItemContent>
                    <SidebarItemAside>
                      <SidebarItemMetaIcon role="img" aria-label="Draft">
                        <Pencil aria-hidden />
                      </SidebarItemMetaIcon>
                    </SidebarItemAside>
                  </SidebarDestinationItem>
                </SidebarList>
              )}
            </SidebarSection>

            <SidebarSection>
              <SidebarSectionHeaderMenu
                menu="channels"
                count={0}
                countLabel="channels"
                createLabel="Create Joint Channel"
                createIcon="joint"
                createDisabled
              >
                <SidebarSectionDisclosure type="button">
                  <SidebarSectionTitle>Joint Channels</SidebarSectionTitle>
                  <SidebarSectionChevron />
                </SidebarSectionDisclosure>
                <SidebarSectionTrailing>
                  <SidebarSectionAttention />
                  <SidebarSectionControls>
                    <SidebarSectionAddButton aria-label="Create joint channel" disabled />
                    <SidebarSectionChannelsMenu
                      aria-label="Joint channels menu"
                      count={1}
                      countLabel="channels"
                      createLabel="Create Joint Channel"
                      createIcon="joint"
                      createDisabled
                    />
                  </SidebarSectionControls>
                </SidebarSectionTrailing>
              </SidebarSectionHeaderMenu>
              <SidebarSectionEmpty>No joint channels yet</SidebarSectionEmpty>
            </SidebarSection>

            <SidebarSection>
              <SidebarSectionHeaderMenu
                menu="channels"
                count={isEmpty ? 0 : 2}
                countLabel="channels"
                createLabel="Create Channel"
                createIcon="channel"
                createDisabled
              >
                <SidebarSectionDisclosure type="button">
                  <SidebarSectionTitle>Channels</SidebarSectionTitle>
                  <SidebarSectionChevron />
                </SidebarSectionDisclosure>
                <SidebarSectionTrailing>
                  <SidebarSectionControls>
                    <SidebarSectionAddButton aria-label="Create channel" disabled />
                    <SidebarSectionChannelsMenu
                      aria-label="Channels menu"
                      count={isEmpty ? 0 : 2}
                      countLabel="channels"
                      createLabel="Create Channel"
                      createIcon="channel"
                      createDisabled
                    />
                  </SidebarSectionControls>
                </SidebarSectionTrailing>
              </SidebarSectionHeaderMenu>
              {isEmpty ? (
                <SidebarSectionEmpty>No channels yet</SidebarSectionEmpty>
              ) : (
                <SidebarList>
                  <SidebarDestinationItem
                    destination="general"
                    selected={selectedItem === "general"}
                    onOpen={openDestination}
                  >
                    <SidebarItemChannelIcon aria-hidden>
                      <Hash />
                    </SidebarItemChannelIcon>
                    <SidebarItemContent>
                      <SidebarItemTitle>general</SidebarItemTitle>
                    </SidebarItemContent>
                    <SidebarItemAside>
                      <SidebarItemIndicator role="img" aria-label="Mentioned you">
                        <AtSign aria-hidden />
                      </SidebarItemIndicator>
                      <SidebarItemCount>12</SidebarItemCount>
                    </SidebarItemAside>
                  </SidebarDestinationItem>
                  <SidebarDestinationItem
                    destination="release"
                    selected={selectedItem === "release"}
                    onOpen={openDestination}
                  >
                    <SidebarItemChannelIcon aria-hidden>
                      <Hash />
                    </SidebarItemChannelIcon>
                    <SidebarItemContent>
                      <SidebarItemTitle>release</SidebarItemTitle>
                      <SidebarItemMetaIcon
                        role="img"
                        aria-label="Activity muted"
                        title="Activity muted. Direct mentions can still notify you."
                      >
                        <BellOff aria-hidden />
                      </SidebarItemMetaIcon>
                    </SidebarItemContent>
                    <SidebarItemAside>
                      <SidebarItemMetaIcon role="img" aria-label="Draft">
                        <Pencil aria-hidden />
                      </SidebarItemMetaIcon>
                    </SidebarItemAside>
                  </SidebarDestinationItem>
                </SidebarList>
              )}
            </SidebarSection>

            <SidebarSection>
              <SidebarSectionHeaderMenu menu="sort">
                <SidebarSectionDisclosure type="button">
                  <SidebarSectionTitle>Direct Messages</SidebarSectionTitle>
                  <SidebarSectionChevron />
                </SidebarSectionDisclosure>
                <SidebarSectionTrailing>
                  <SidebarSectionControls>
                    <SidebarSectionSortMenu aria-label="Sort direct messages" />
                  </SidebarSectionControls>
                </SidebarSectionTrailing>
              </SidebarSectionHeaderMenu>
              {isEmpty ? (
                <SidebarSectionEmpty>No direct messages yet</SidebarSectionEmpty>
              ) : (
                <SidebarList>
                  <SidebarDestinationItem
                    destination="Martin"
                    selected={selectedItem === "Martin"}
                    onOpen={openDestination}
                  >
                    <SidebarItemIcon>
                      <Avatar size="2xs" type="human">
                        <AvatarFallback className="text-[9px] font-bold">M</AvatarFallback>
                      </Avatar>
                    </SidebarItemIcon>
                    <SidebarItemContent>
                      <SidebarItemTitle>Martin</SidebarItemTitle>
                      <SidebarItemSubtitle>design</SidebarItemSubtitle>
                    </SidebarItemContent>
                    <SidebarItemAside>
                      <SidebarItemStatus>
                        <Status aria-hidden variant="success" />
                      </SidebarItemStatus>
                      <SidebarItemCount>4</SidebarItemCount>
                    </SidebarItemAside>
                  </SidebarDestinationItem>
                  <SidebarDestinationItem
                    destination="Wug idle"
                    selected={selectedItem === "Wug idle"}
                    onOpen={openDestination}
                  >
                    <SidebarItemIcon>
                      <Avatar size="2xs" type="agent" />
                    </SidebarItemIcon>
                    <SidebarItemContent>
                      <SidebarItemTitle>Wug</SidebarItemTitle>
                      <SidebarItemSubtitle>idle</SidebarItemSubtitle>
                    </SidebarItemContent>
                    <SidebarItemAside>
                      <SidebarItemMetaIcon role="img" aria-label="Draft">
                        <Pencil aria-hidden />
                      </SidebarItemMetaIcon>
                    </SidebarItemAside>
                  </SidebarDestinationItem>
                </SidebarList>
              )}
            </SidebarSection>
          </div>
        </SidebarContent>
      </SidebarBody>
      <SidebarLiveActivity>
        <LiveAgentActivityBarComposition latest={LIVE_ACTIVITY[liveActivityState]} />
      </SidebarLiveActivity>
    </SidebarRoot>
  );
}
```

## When to use

| Situation                             | Use                                                        |
| ------------------------------------- | ---------------------------------------------------------- |
| The contents of a desktop destination | Sidebar                                                    |
| The same list on a phone              | Sidebar rows, on the page itself; below `md` they are 44px |
| The destinations themselves           | [AppRail](https://raft-design.botiverse.build/components/app-rail.md)                         |
| Activity and notifications            | [InboxItem](https://raft-design.botiverse.build/components/inbox-item.md)                   |

Which destinations have a sidebar, and what each one lists, is the product's choice ([Raft Chat](https://raft-design.botiverse.build/products/raft-chat.md)). A destination without a sidebar puts its content next to the rail ([Page structure](https://raft-design.botiverse.build/patterns/page-structure.md#sidebar-and-side-panel)).

## Anatomy

- `SidebarRoot`: the column, on `layer-sidebar`, with a `line-seam` along its right edge.
- `SidebarHeader`: the 56px header, with a `SidebarHeaderTitle` and, on its right, the working summary when someone is working.
- `SidebarBody` and `SidebarContent`: the scrolling list.
- `SidebarSection`: a group of rows.
  - `SidebarSectionHeader`: holds a `SidebarSectionDisclosure` (the button that collapses the group, with `SidebarSectionChevron`, `SidebarSectionTitle` and `SidebarSectionCount`) and, in `SidebarSectionTrailing` or `SidebarSectionControls`, `SidebarSectionControl` buttons such as New. `SidebarSectionAttention` is the group's pink pixel.
  - `SidebarList`: the rows. `SidebarSectionEmpty` replaces them when the group is empty.
- `SidebarMenu`, with `SidebarMenuIcon`, `SidebarMenuTitle`, `SidebarMenuAside` and `SidebarMenuCount`: a single destination in a section header, such as Saved.
- `SidebarGroupLabel`: a plain group heading that doesn't collapse.
- `SidebarItem`: one row.
  - First, a `SidebarItemChannelIcon`, a `SidebarItemIcon` or a 19px [Avatar](https://raft-design.botiverse.build/components/avatar.md).
  - `SidebarItemContent`: the `SidebarItemTitle` (the name) and an optional `SidebarItemSubtitle` (a description).
  - `SidebarItemAside`: what follows: `SidebarItemMeta`, `SidebarItemMetaIcon`, `SidebarItemStatus`, the unread `SidebarItemCount` (with `SidebarItemThreadCount` and `SidebarItemCountSeparator`), or the mention marker `SidebarItemIndicator`.

```tsx
import { AtSign, BellOff, Hash, Pencil } from "lucide-react";
import {
  SidebarItem,
  SidebarItemAside,
  SidebarItemChannelIcon,
  SidebarItemContent,
  SidebarItemCount,
  SidebarItemIcon,
  SidebarItemIndicator,
  SidebarItemMetaIcon,
  SidebarItemTitle,
} from "@botiverse/raft-ui";

export function SidebarItemChannelComposition() {
  return (
    <div>
      <SidebarItem active>
        <SidebarItemIcon aria-hidden>
          <SidebarItemChannelIcon>
            <Hash />
          </SidebarItemChannelIcon>
        </SidebarItemIcon>
        <SidebarItemContent>
          <SidebarItemTitle>general</SidebarItemTitle>
        </SidebarItemContent>
      </SidebarItem>

      <SidebarItem>
        <SidebarItemIcon aria-hidden>
          <SidebarItemChannelIcon>
            <Hash />
          </SidebarItemChannelIcon>
        </SidebarItemIcon>
        <SidebarItemContent>
          <SidebarItemTitle>design</SidebarItemTitle>
          <SidebarItemMetaIcon role="img" aria-label="Activity muted">
            <BellOff aria-hidden />
          </SidebarItemMetaIcon>
        </SidebarItemContent>
        <SidebarItemAside>
          <SidebarItemIndicator role="img" aria-label="Mentioned you">
            <AtSign aria-hidden />
          </SidebarItemIndicator>
          <SidebarItemCount>12</SidebarItemCount>
          <SidebarItemMetaIcon role="img" aria-label="Draft">
            <Pencil aria-hidden />
          </SidebarItemMetaIcon>
        </SidebarItemAside>
      </SidebarItem>
    </div>
  );
}
```

## States

| State                 | Look                                                                                                                                                                                                                                                                             |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `active`              | A yellow sticker: you are here. It doesn't change on hover; a press lays `sticker-medium`                                                                                                                                                                                        |
| `tone="unread"`       | A bold name and a pink `SidebarItemCount`. When someone mentions the person, a yellow `SidebarItemIndicator` replaces the count                                                                                                                                                  |
| `tone="muted"`        | A `bell-off` `SidebarItemMetaIcon`, the name in `foreground-muted`, and the unread number as quiet text in `SidebarItemMeta`                                                                                                                                                     |
| `tone="away"`         | Offline, or not joined: the name in `foreground-hint`                                                                                                                                                                                                                            |
| Menu open             | `data-popup-open` (a context menu, or press and hold on a phone): `fill-subtle`, the same subtle tier as the hover, so the row the menu acts on stays marked; an `active` row stays the sticker ([Hover and press](https://raft-design.botiverse.build/foundations/states.md#hover-and-press)) |
| `interactive={false}` | A display-only row, such as this computer, whose action is a button inside it: a `div` that doesn't react to hover or press                                                                                                                                                      |

```tsx
import { BellOff, Hash, Monitor } from "lucide-react";
import {
  Button,
  SidebarItem,
  SidebarItemAside,
  SidebarItemChannelIcon,
  SidebarItemContent,
  SidebarItemCount,
  SidebarItemIcon,
  SidebarItemMeta,
  SidebarItemMetaIcon,
  SidebarItemSubtitle,
  SidebarItemTitle,
} from "@botiverse/raft-ui";

export function SidebarItemTonesComposition() {
  return (
    <div>
      <SidebarItem tone="unread" render={<a href="#composition-tones" />}>
        <SidebarItemChannelIcon>
          <Hash />
        </SidebarItemChannelIcon>
        <SidebarItemContent>
          <SidebarItemTitle>release-review</SidebarItemTitle>
        </SidebarItemContent>
        <SidebarItemAside>
          <SidebarItemCount>2</SidebarItemCount>
        </SidebarItemAside>
      </SidebarItem>
      <SidebarItem tone="muted" render={<a href="#composition-tones" />}>
        <SidebarItemChannelIcon>
          <Hash />
        </SidebarItemChannelIcon>
        <SidebarItemContent>
          <SidebarItemTitle>random</SidebarItemTitle>
        </SidebarItemContent>
        <SidebarItemAside>
          <SidebarItemMetaIcon role="img" aria-label="Muted">
            <BellOff aria-hidden />
          </SidebarItemMetaIcon>
          <SidebarItemMeta>12</SidebarItemMeta>
        </SidebarItemAside>
      </SidebarItem>
      <SidebarItem tone="away" render={<a href="#composition-tones" />}>
        <SidebarItemChannelIcon>
          <Hash />
        </SidebarItemChannelIcon>
        <SidebarItemContent>
          <SidebarItemTitle>design-crit</SidebarItemTitle>
        </SidebarItemContent>
      </SidebarItem>
      <SidebarItem interactive={false} wrap>
        <SidebarItemIcon>
          <Monitor />
        </SidebarItemIcon>
        <SidebarItemContent stacked>
          <SidebarItemTitle>This Mac</SidebarItemTitle>
          <SidebarItemSubtitle>Not running agents yet</SidebarItemSubtitle>
        </SidebarItemContent>
        <Button variant="outline" size="md" render={<a href="#composition-tones" />}>
          Enable
        </Button>
      </SidebarItem>
    </div>
  );
}
```

## Behavior

- The sidebar is 240px wide and can be dragged between 200px and 400px; it keeps one width on every page that has it ([Spacing](https://raft-design.botiverse.build/foundations/spacing.md#layout-widths)).
- A name stays on one line and ends with an ellipsis. A description after it gives up its room first; the name truncates only once the description is gone.
- A row whose content is 2 lines (a computer's name and whether it runs agents) uses `wrap` with `SidebarItemContent stacked`: it grows from 36px, its text stays 8px from the row's edges, and its icon lines up with the first line. The name still stays on one line.
- A collapsed group shows how many rows it holds in `SidebarSectionCount`.
- While an agent works, what it is doing replaces its runtime ([Working agents](https://raft-design.botiverse.build/people-and-agents/working-agents.md#where-working-shows)), in `text-footnote` and `foreground-muted`, and takes at most 58% of the row.

## Content

- Title the header with the destination's name.
- Mark a channel with a bare `#` icon, a private channel with `lock`, and agents and people with their 19px avatar.
- Write an agent's runtime in Space Mono after its name.

## Accessibility

- `SidebarRoot` renders a `div`. Render it as an `aside` or a `nav` and name it after the destination: `aria-label="Chat"`.
- `SidebarItem` renders the Base UI Button. For a row that navigates, render a link (`render={<a href="…" />}`) and set `aria-current="page"` on the `active` row: `active` only changes how it looks.
- Give the unread count a name that says what it counts ("2 unread"), and give an icon-only `SidebarItemMetaIcon` a name ("Muted").
- `SidebarSectionDisclosure` is a Base UI Collapsible trigger, which sets `aria-expanded`.
- On a display-only row, focus lands only on the controls inside it.

## Rules

- Draw a row the same way in every group: original, pinned or custom. A group never changes a row (no wrapping, no hidden description in one group), because the same conversation would look like two different things.
- Don't use `wrap` to let a name run onto more lines, including in a pinned group. Use it only for a row whose content is 2 lines.
- Make every row 36px high, in `text-subhead`, on a desktop. Below `md` the row grows on its own to at least 44px in `text-body` at 600: there it is a phone list of places (Home, Members, the Settings list), which a finger has to hit. Don't add phone sizes at the callsite. The Settings list sat at 36px on phones because the size lived in one product class that only Home's rows were given.
- Don't put the server switcher in the sidebar header. It is in the web top bar, or at the top of the rail when there is no top bar.
- Don't use `SidebarLiveActivity` for a live bar at the bottom of the sidebar. Working agents appear in the header's working summary and in their own rows instead ([Working agents](https://raft-design.botiverse.build/people-and-agents/working-agents.md#where-working-shows)).

## API

### SidebarBody

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarLiveActivity

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarGroupLabel

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarHeader

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarHeaderTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItemAside

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItemCount

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItemCountSeparator

The dot between the channel's own unread count and its thread count.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItemChannelIcon

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItem

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `active` | `boolean` |  |  |
| `interactive` | `boolean` | `true` | `false` renders a display-only `div`: no button, no type, nothing to press. Its own controls (an action button) stay the targets. |
| `tone` | `RowTone` | `"default"` | Something waits for you (a 700 name), the place is muted (an ink-2 name), or it is away: offline or not joined (an ink-3 name). |
| `wrap` | `boolean` | `false` | For a row whose content is two lines (a name and a second line): the row grows from its fixed height and its content aligns to the first line. The name itself stays on one line and truncates, in every group (Sidebar's rules); never use this to let a long name wrap. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItemIcon

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItemIndicator

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItemContent

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `stacked` | `boolean` | `false` |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItemMeta

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItemMetaIcon

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItemStatus

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItemSubtitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItemThreadCount

Thread segment of an unread count: a thread icon followed by the number of threads with new replies. Works inside SidebarItemCount or as quiet text.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | `"threads with new replies"` | Visually hidden words read after the number, so screen readers hear what the count is (the icon is decorative). Pass localized copy. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarItemTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarList

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `hiddenUntilFound` | `boolean` | `false` | Allows the browser's built-in page search to find and expand the panel contents. |
| `keepMounted` | `boolean` | `false` | Whether to keep the element in the DOM while the panel is hidden. This prop is ignored when `hiddenUntilFound` is used. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarMenu

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarMenuAside

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarMenuCount

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarMenuIcon

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarMenuTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarRoot

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarControl

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `loading` | `boolean` |  |  |
| `loadingLabel` | `string` |  |  |
| `size` | `"md" \| "lg" \| "xl" \| "icon-md" \| "icon-lg" \| DeprecatedButtonSize` |  | `md` (32px) and `icon-md` for rows, cards, banners, toolbars and panel headers; `lg` and `icon-lg` (the control height) for dialog footers, forms and beside a field; `xl` for a full-width call to action. The size follows the place: see Button › Size by place. |
| `variant` | `"primary" \| "accent" \| "warning" \| "danger" \| "default" \| "outline" \| "information" \| "muted" \| "success" \| "danger-secondary" \| "danger-outline" \| "ghost" \| "on-frame"` |  |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarSection

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `defaultOpen` | `boolean` | `true` | Whether the collapsible panel is initially open. |
| `disabled` | `boolean` | `false` | Whether the component should ignore user interaction. |
| `onOpenChange` | `(open: boolean, eventDetails: CollapsibleRootChangeEventDetails) => void` |  | Event handler called when the panel is opened or closed. |
| `open` | `boolean` |  | Whether the collapsible panel is currently open. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarSectionEmpty

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `hiddenUntilFound` | `boolean` | `false` | Allows the browser's built-in page search to find and expand the panel contents. |
| `keepMounted` | `boolean` | `false` | Whether to keep the element in the DOM while the panel is hidden. This prop is ignored when `hiddenUntilFound` is used. |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarSectionControl

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `loading` | `boolean` | `false` |  |
| `loadingLabel` | `string` | `"Loading"` |  |
| `size` | `"md" \| "lg" \| "xl" \| "icon-md" \| "icon-lg" \| DeprecatedButtonSize` | `"icon-md"` | `md` (32px) and `icon-md` for rows, cards, banners, toolbars and panel headers; `lg` and `icon-lg` (the control height) for dialog footers, forms and beside a field; `xl` for a full-width call to action. The size follows the place: see Button › Size by place. |
| `variant` | `"primary" \| "accent" \| "warning" \| "danger" \| "default" \| "outline" \| "information" \| "muted" \| "success" \| "danger-secondary" \| "danger-outline" \| "ghost" \| "on-frame"` | `"ghost"` |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarSectionControls

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarSectionDisclosure

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarSectionChevron

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarSectionCount

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarSectionHeader

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarSectionAttention

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `presence` | `"online" \| "busy" \| "error" \| "offline" \| "external"` |  | A status light (online, busy, error, offline, external): a fixed meaning that needs its word nearby, or an aria-label. It takes the place of `variant`'s colour. |
| `size` | `"xs" \| "sm" \| "md" \| "lg"` | `"md"` |  |
| `variant` | `"primary" \| "accent" \| "warning" \| "danger" \| "default" \| "information" \| "muted" \| "success"` |  |  |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarSectionTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

### SidebarSectionTrailing

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `render` | `ReactElement \| ((props, state) => ReactElement)` |  | Allows you to replace the component's HTML element with a different tag, or compose it with another component. |

Also takes the attributes of its element.

## Changes

- 0.8.1 (Fixed): Below `md`, a `SidebarItem` row is at least 44px high in the 16px title type, so a phone sidebar row can be tapped without help from the app.
- 0.6.1 (Added): `SidebarItem` takes `tone` (`RowTone`, shared with ListRow) and `interactive={false}` for a display-only row whose own buttons are the targets.
- 0.6.0 (Breaking Changes): **Props that no longer change anything are removed:** `Badge` `uppercase`; `Checkbox` `size`, `variant`, `color`; `Status` `pulse`, `attention` (status never animates); `Switch` / `SwitchThumb` `size`; `SidebarItem` `variant`; `SidebarItemCount` / `SidebarItemIndicator` `variant` and `appearance` (the count is always the pink sticker, the indicator the yellow @); `NotificationCenterActionButton` `variant`; `DrawerContent` `width` (one-go forms use the wide `Dialog`); `AppRailItemIndicator` `attention`; `AppRailItemAttention` `children`; `Steps` `size`, `track`, `renderIndicator`, `renderTraveler`; `FilePreviewBadge` `type`, `color`. Remove them at call sites.
- 0.5.64 (Changed): `SidebarItemThreadCount` draws the package's `ThreadIcon` (was lucide `MessageSquare`), at 12px with a 1.75 stroke; badge height is unchanged.
- 0.5.60 (Added): `SidebarItemThreadCount` (thread icon + number of followed threads with new replies, with a visually hidden, localizable `label` for screen readers) and `SidebarItemCountSeparator` (the dot between counts), so a channel row's `SidebarItemCount` can read `5 · [thread] 1`. Both also work as quiet text inside `SidebarItemMeta` for muted channels. `SidebarItemCount` stays as tight as a plain number when it holds the thread icon.
- 0.5.35 (Changed): `SidebarSection`'s attention badge no longer hides on its own while a section menu is open. The section's own open state and header hover still hide it; to also hide it while a menu is open, the header must mirror the menu's open state onto itself as `data-popup-open` (the docs composition shows the wiring).
- 0.5.0 (Breaking Changes): **Brutal `SidebarItem` active styling is neutral by default.** Active items now use a white surface instead of pink. Pass `variant="accent"` to preserve the previous pink treatment, or `variant="primary"` for yellow.
