Pho Design System

Sidebar

The app navigation rail — Pho’s first section: a composed assembly built from the micro components, not another primitive. It encodes the rail used across Photon surfaces (this docs site, the dashboard): a sticky full-height column with a brand header, a scrollable stack of titled nav groups, and a pinned footer that usually carries the account row.

Sections stay presentational. No router, no data fetching, no theme plumbing — links render through the host’s LinkProvider (the same contract ButtonLink uses), the current page arrives as an active prop, and interactive parts compose with other Pho components (the account row slots into a Menu.Trigger).

Import

import { Sidebar } from "@photon-ai/pho-ui/sections/sidebar";

Anatomy

<Sidebar.Root>
  <Sidebar.Header>
    <PhotonMark className="text-pho-brand size-6" />
    <span className="font-medium tracking-tight">Photon</span>
  </Sidebar.Header>

  <Sidebar.Nav>
    <Sidebar.Group>
      <Sidebar.GroupLabel>Pages</Sidebar.GroupLabel>
      <Sidebar.List>
        <Sidebar.Item href="/" icon={IconLayoutDashboard} active>
          Overview
        </Sidebar.Item>
        <Sidebar.Item href="/members" icon={IconUsers}>
          Members
        </Sidebar.Item>
      </Sidebar.List>
    </Sidebar.Group>
  </Sidebar.Nav>

  <Sidebar.Footer>
    <Sidebar.List>
      <Sidebar.Item icon={IconBook} href="https://photon.codes/docs/" external>
        Docs
      </Sidebar.Item>
    </Sidebar.List>

    <Menu.Root>
      <Menu.Trigger render={<Sidebar.AccountRow name="Ada Lovelace" />} />
      <Menu.Portal>{/* account menu */}</Menu.Portal>
    </Menu.Root>
  </Sidebar.Footer>
</Sidebar.Root>

Items

Sidebar.Item renders a link through the host LinkProvider when href is given, and a plain button otherwise. active lifts the row onto the primary surface with the site hairline and sets aria-current="page"; external opens a new tab and appends a ↗ cue (with a screen-reader note).

Columns

For a long, flat registry — like this site’s component list — List lays rows out in a two-column grid with columns.

<Sidebar.List columns>
  <Sidebar.Item href="/components/button">Button</Sidebar.Item>
  <Sidebar.Item href="/components/input">Input</Sidebar.Item>
  {/* … */}
</Sidebar.List>

Account row

Sidebar.AccountRow is ONE button — the ⋯ circle on the right is decorative (a span wearing the stock secondary-circle button classes). The row is the control, the circle is the indicator: hovering or pressing anywhere on the row lifts and squeezes the ⋯ circle (driven by the row’s group state) while the row itself stays quiet, so the whole chip reads as a single control without nesting buttons. It forwards every prop, so it slots straight into Base UI’s render prop:

<Menu.Root>
  <Menu.Trigger
    render={<Sidebar.AccountRow name="Ada Lovelace" avatarSrc={pictureUrl} />}
  />
  <Menu.Portal>
    <Menu.Positioner side="top" align="end">
      <Menu.Popup>{/* settings / theme / log out */}</Menu.Popup>
    </Menu.Positioner>
  </Menu.Portal>
</Menu.Root>

data-popup-open and data-pressed from the trigger drive the ⋯ circle: it stays lifted while the menu is open and scales down slightly on press — the row keeps only its keyboard focus ring; hover, press, and menu-open feedback all land on the circle.

Props

Sidebar.Item

Sidebar.List

Sidebar.AccountRow

Every part merges className onto its root and forwards native attributes.

Accessibility

Best practices