A sidebar is the navigation an application keeps on screen: destinations gathered into sections,
each with an icon, a label and optionally a count, in a column that can fold down to its icons
when the content needs the room. What separates it from a menu is that it stays: it is a
landmark with a current destination, not a popover with a selection.

It is the largest of the navigation components because it has a surface of its own: the column
collapses, the labels it loses come back as tooltips, its rows are dressed in two states
separately, and its edge is drawn in four treatments. Each of those is a token in the cascade,
so a product states its navigation once.

```tsx demo="navigation/sidebar-preview"
```

## Usage

```tsx
import { Sidebar } from "@asheeui/web";

<Sidebar
  items={[
    { id: "dashboard", label: "Dashboard", href: "/dashboard", icon: <DashboardIcon /> },
    { id: "campaigns", label: "Campaigns", href: "/campaigns", badge: 12 },
  ]}
  activeKey="dashboard"
  title="Ashee CRM"
/>
```

`items` takes one of two shapes: a flat list of destinations, or a list of sections that each
hold destinations. The first entry decides which, so an array is read as sections when its first
entry has `items` of its own. The test is made once, on that entry, so a mixed array is not read
as anything: state sections with sections and destinations with destinations. A section carries
an optional `label`, shown above its rows while the column is expanded, and the same `roles` an
item does.

An item is an `id`, a `label` and a `href`, plus an `icon` before the label, a `badge` after it,
a `target` and `rel` for a destination that leaves the site, `roles` and `disabled`. Its `id` is
what `activeKey` is compared against and what `onSelect` reports, and `label` is the row's text
and the only other required part.

`onSelect` receives the whole item on every press, including a press on a row that also has an
`href`, so a page can record the choice and let the link navigate. A `disabled` row drops its
`href` and its pointer and is dimmed, so it is drawn without being reachable: it is for a
destination that is not ready rather than for one the reader must be told about.

`userRole` filters both levels: a section or an item with `roles` is shown only to a reader whose
`userRole` matches, and a role-guarded entry is hidden when `userRole` is not given at all. This
is what a reader is shown rather than what a reader is allowed, so the server still refuses what
the filter leaves out.

Every row is a link. `link` states the component that renders one and the props it takes, for
the whole column at once, and `anchorProps` states anchor attributes for every row as well; the
`link` props are merged last and win. Substituting a router's link is
[the same seam every navigation component uses](/concepts/substitution).

The header appears when there is a `title`, an `onBack`, or both: `onBack` puts a back control
before the title, which is where a column below a list of workspaces wants it, and `backIcon`
replaces its glyph. `footer` is a slot at the foot of the column, for a reader chip, an invite
button, a version — or the page's own control that folds the column.

A sidebar is the navigation column of an application shell rather than a page of its own;
`SidebarLayout` places it beside the content. See
[Page compositions](/guides/page-compositions).

## Collapsing

Two options decide whether the column can fold and how it starts. `collapsible` (registered
`true`) is whether folding is possible at all, and `defaultCollapsed` (registered `false`) is
where an uncontrolled column starts. `isCollapsed` with `onCollapseChange` makes the state
controlled; without them the column keeps its own, starting from `defaultCollapsed`, and still
reports each change.

The control that folds the column is the application's, not the framework's: a sidebar that draws
its own button decides the shape, the words and the place of something the page owns. Read the
state the column publishes with `useSidebarCollapse`, which returns `isCollapsed`, `collapsible`,
`setCollapsed` and `toggleCollapse`, and draw whatever button fits the product. A control inside
the column — its `footer` slot, for instance — reads the hook directly:

```tsx
import { Sidebar, useSidebarCollapse } from "@asheeui/web";

function FoldButton() {
  const { isCollapsed, collapsible, toggleCollapse } = useSidebarCollapse();

  return (
    <button
      type="button"
      disabled={!collapsible}
      aria-expanded={!isCollapsed}
      aria-label={isCollapsed ? "Expand sidebar" : "Collapse sidebar"}
      onClick={toggleCollapse}>
      {isCollapsed ? "Expand" : "Collapse"}
    </button>
  );
}

<Sidebar items={items} title="Ashee CRM" footer={<FoldButton />} />;
```

A control that lives outside the column — in a header, beside a workspace switcher — cannot read
the column's context, and does not need to: lift the state and drive the controlled form.

```tsx
const [collapsed, setCollapsed] = useState(false);

<button type="button" aria-expanded={!collapsed} onClick={() => setCollapsed(!collapsed)}>
  Toggle navigation
</button>
<Sidebar items={items} isCollapsed={collapsed} onCollapseChange={setCollapsed} />;
```

Every request is refused when `collapsible` is `false`, and the hook reports the option so a
control can disable itself instead of offering an affordance that would do nothing.

A collapsed column is the icon column: 4rem at `sm`, 4.5rem at `md` and 5rem at `lg`, against
14, 16 and 18rem when it is expanded. The rows lose their visible labels and badges, the section
labels disappear, the header keeps its back control but loses its title — a header left with
nothing to show is not drawn at all — and the footer slot stays
where it is, so the control that unfolded the column is still there to fold it back. The icons
stay and the current row is still marked. A row's label is not lost to assistive technology when
it is folded away: it is rendered visually hidden, so the row keeps its name even though it shows
only a glyph.

The width transition is a cascade value too: `animated` (registered `true`) is what makes folding
animate rather than jump, and `components.sidebar.animated` turns it off for every column.

`footer` is drawn whether or not the column can fold and whether or not it is folded: the slot
belongs to the page, and the page's control is usually in it. Its wrapper clips rather than
widening the rail, so a slot written for the expanded column cannot push the column out of its
shell.

A row with no room for its label explains itself instead: `tooltip.show` (registered `true`)
puts the label back on hover and on keyboard focus, at `tooltip.placement`, registered as
`right`, which is the side a collapsed column has room on. `variant` and `color` are not
registered, so the hint falls back to a solid secondary treatment unless the application states
otherwise.

```tsx demo="navigation/sidebar-collapsed"
```

## Item and section states

The two states of a row are option groups of their own. `options.inactive` dresses every row
that is not the current one and `options.active` dresses the one that is. The registered pair is
a ghost row with no colour of its own, so an idle row takes a quiet wash on hover, and a solid
primary row for the destination the reader is on.

```tsx demo="navigation/sidebar-options"
```

`options.radius` rounds the rows and resolves from the application's `defaultRadius`, falling
back to `md`, so a product that rounds its controls rounds its navigation with them. A row
treatment of `underlined` resolves that radius to `none`, which is the same rule the button
follows. `options.itemClassName` is the escape hatch for anything the tokens do not state.

A section carries `disabled` as well as a row: marking one draws all of its rows as unavailable,
no destination, `aria-disabled`, the press refused, and dims the section's label with them, which
is what an entitlement the reader does not have looks like.

The column's own `radius` rounds its surface, and `full` is read as `xl` there: a column cannot
be a circle in a shell that lays it out as a rectangle. The surface itself is `variant`:
`default` draws a border on the edge the column sits against, `bordered` a heavier one,
`floating` insets it as a rounded panel with a shadow, and `ghost` keeps the background and
drops the border.

## Props

```props Sidebar
```

The registered defaults cover the behaviour rather than the dressing: `size` `md`,
`collapsible` `true`, `defaultCollapsed` `false`, `animated` `true`,
`tooltip.show` `true` with `placement` `right`, and the two row states as a ghost inactive row and
a solid primary active one.

`variant`, `radius`, `options.radius`, `options.inactive.color`, `tooltip.variant` and
`tooltip.color` are not registered, so each falls through to the next tier that states it:
`radius` and `options.radius` resolve from the application's `defaultRadius` and round the column
and its rows together, the surface falls back to `default`, an idle row to no colour of its own,
and the tooltip to a solid secondary treatment.

`items`, `activeKey`, `onSelect`, `isCollapsed`, `onCollapseChange`, `userRole`, `link`,
`anchorProps`, `footer`, `navLabel` and the four class-name overrides are instance-only, because
the destinations, the current one, and who is reading them are statements about one screen rather
than about the design. `isCollapsed` and `onCollapseChange` are how a control outside the column
folds it, and `useSidebarCollapse` is how one inside it does.

## Accessibility

- On its own the column is an `aside` with a `nav` inside it, so it is both a complementary landmark
  and a navigation landmark: `aria-label` on the component names the complementary region and
  `navLabel` (default `Sidebar navigation`) names the navigation landmark, so two columns on one
  page, a docs column beside an on-page one, can be told apart in a landmark list. Inside a
  [`SidebarLayout`](/components/layouts/sidebar-layout) column it renders a `div` instead, because
  the column already is the complementary landmark, and contributes only the navigation one.
- The current row carries `aria-current="page"`, so the reader's destination is announced rather
  than only filled. A `disabled` row is drawn without an `href` and with `aria-disabled`, so the
  press is refused while the row stays readable; a whole section marked `disabled` renders its
  rows the same way.
- The control the page draws should name what it will do — `Collapse sidebar` or `Expand sidebar`
  — and carry `aria-expanded`, so its state is announced rather than inferred from the direction of
  a chevron. The header's back control is named `Go back`.
- A collapsed row keeps its name: the label is rendered visually hidden instead of removed, so the
  icon is not the only thing naming the destination. The tooltip stays a description, it explains
  the row rather than being what announces it.

## Related

```related pagination, tabs, dropmenu
```
