List

List and its companions are the building blocks of vertical row layouts — sidebars, navigation panels, settings sheets, and dropdown menus. The five pieces are designed to compose:

  • List — the container. A flex column with p-2; pass className to size it (w-64, w-full, etc.).
  • ListButton — the interactive row primitive. Wraps Button with list-tuned defaults (size="lg", transparent variant, multiline) plus slots for icon, header, footer, and after.
  • ListItem — the static row primitive. Same vertical rhythm as ListButton but renders a plain <div> — for read-only metadata rows.
  • ListSeparator — a hairline that bleeds into the list's padding, for grouping rows.
  • ListTitle — an uppercase eyebrow label for naming a group of rows.

In practice these are most often dropped into a Popover to build option menus, sort pickers, account dropdowns, and command-palette rows — see the popover example below.

Workspace
Storage
Used4.2 / 10 GB
<Surface outline className="w-72 rounded-3xl">
  <List>
    <ListTitle>Workspace</ListTitle>
    <ListButton
      icon={<EnvelopeIcon />}
      selected={selected === 'inbox'}
      onClick={() => setSelected('inbox')}
      after={<Chip color="brand">12</Chip>}
    >
      Inbox
    </ListButton>
    <ListButton
      icon={<NoteIcon />}
      selected={selected === 'drafts'}
      onClick={() => setSelected('drafts')}
    >
      Drafts
    </ListButton>
    <ListButton
      icon={<ArchiveIcon />}
      selected={selected === 'archive'}
      onClick={() => setSelected('archive')}
    >
      Archive
    </ListButton>
    <ListSeparator />
    <ListTitle>Storage</ListTitle>
    <ListItem>
      <span className="text-cladd-fg-soft">Used</span>
      <span className="ml-auto font-mono">4.2 / 10 GB</span>
    </ListItem>
    <ListSeparator />
    <ListButton icon={<PlusIcon />} color="brand">
      New project
    </ListButton>
  </List>
</Surface>

Usage

import {
  List,
  ListButton,
  ListItem,
  ListSeparator,
  ListTitle,
} from '@cladd-ui/react';
 
<List className="w-64">
  <ListTitle>Workspace</ListTitle>
  <ListButton selected>Inbox</ListButton>
  <ListButton>Drafts</ListButton>
  <ListSeparator />
  <ListButton icon={<PlusIcon />}>New project</ListButton>
</List>;

A bare List is a flex column with p-2. Drop in any combination of ListTitle, ListSeparator, ListButton, and ListItem — they all share the list's padding rhythm so spacing stays consistent without per-row tuning.

ListButton inherits the full Button API. Notable defaults that differ from Button: size="lg" (lists usually want more vertical space than a toolbar), variant="transparent", outline={false}, and multiline so long titles wrap instead of truncating.

Examples

With icon

ListButton's icon slot takes a node — typically an SVG — and renders it in a size-matched square ahead of the title. The wrapper applies the size class for you, so the same icon stays in proportion as you change the row's size. Use it for action menus and navigation rows where the leading glyph carries most of the recognition load.

<Surface outline className="w-64 rounded-3xl">
  <List>
    <ListButton icon={<PlusIcon />}>New file</ListButton>
    <ListButton icon={<CopyIcon />}>Duplicate</ListButton>
    <ListButton icon={<CheckIcon />}>Mark as done</ListButton>
  </List>
</Surface>

header and footer are small cladd-xs labels stacked above and below the title — the right slots for an eyebrow date/category and a metadata line. after is a right-aligned end slot for badges, chevrons, Chips, or Shortcuts. Combine them to build the dense, three-line rows you'd see in a task list, an inbox, or an activity feed.

<Surface outline className="w-80 rounded-3xl">
  <List>
    <ListButton
      icon={<CheckIcon />}
      header="Today, 09:14"
      footer="Reviewed by 3 people"
      after={<Chip color="green">Done</Chip>}
    >
      Ship onboarding redesign
    </ListButton>
    <ListButton
      icon={<CopyIcon />}
      header="Yesterday, 17:32"
      footer="Owner: Anna"
      after={<Chip color="yellow">In review</Chip>}
    >
      Migrate billing webhooks
    </ListButton>
    <ListButton
      icon={<PlusIcon />}
      header="2 days ago"
      footer="No assignee yet"
      after={<Chip color="neutral">Backlog</Chip>}
    >
      Draft Q3 retrospective notes
    </ListButton>
  </List>
</Surface>

Selected

selected marks the active row. Internally it forces variant="gradient" and outline={true} regardless of the props you pass, so a selected row always reads as the live one. Pair with color to tint the highlight — neutral by default, "brand" for the typical app accent.

<Surface outline className="w-64 rounded-3xl">
  <List>
    {VIEWS.map((v) => (
      <ListButton
        key={v}
        selected={v === active}
        color="brand"
        onClick={() => setActive(v)}
      >
        {v}
      </ListButton>
    ))}
  </List>
</Surface>

Sizes

size accepts the same 2xs → 2xl scale as Button. The default is lg — one step up from Button's default — because list rows want more vertical air than toolbar buttons. Stay around mdlg for typical menus and sidebars; drop to sm or xs for dense option sheets opened in a Popover.

<Surface outline className="w-64 rounded-3xl">
  <List>
    <ListButton size="lg" icon={<EnvelopeIcon />} selected>
      Inbox
    </ListButton>
    <ListButton size="lg" icon={<NoteIcon />}>
      Drafts
    </ListButton>
    <ListButton size="lg" icon={<ArchiveIcon />}>
      Archive
    </ListButton>
  </List>
</Surface>

Color

color tints the row's text and accent treatment (the highlight under hover, the gradient under selected). Use it in isolation to mark a destructive row — a red "Delete" row at the end of a menu — without changing the surrounding rhythm, or pair it with selected for a colored active-row highlight.

<Surface outline className="w-64 rounded-3xl">
  <List>
    <ListButton icon={<CopyIcon />} color="brand">
      Duplicate
    </ListButton>
    <ListButton icon={<PlusIcon />} color="brand" selected>
      Add to favorites
    </ListButton>
    <ListSeparator />
    <ListButton color="red">Delete project</ListButton>
  </List>
</Surface>

Static info rows

ListItem is the non-interactive sibling of ListButton — same vertical rhythm, but it renders a plain <div> with no button behavior, no hover overlay, and smaller cladd-xs text. Use it for read-only metadata rows in a settings panel, account header rows, or anywhere a row needs to line up with surrounding ListButtons but stay static. It composes with Chip for inline status, and with ListSeparator and ListTitle for sectioning.

Account
Plan
Pro
Seats8 / 10
RenewsMay 24, 2026
<Surface outline className="w-72 rounded-3xl">
  <List>
    <ListTitle>Account</ListTitle>
    <ListItem>
      <span className="text-cladd-fg-soft">Plan</span>
      <span className="ml-auto">
        <Chip color="brand">Pro</Chip>
      </span>
    </ListItem>
    <ListItem>
      <span className="text-cladd-fg-soft">Seats</span>
      <span className="ml-auto font-mono">8 / 10</span>
    </ListItem>
    <ListItem>
      <span className="text-cladd-fg-soft">Renews</span>
      <span className="ml-auto">May 24, 2026</span>
    </ListItem>
    <ListSeparator />
    <ListButton icon={<CheckIcon />}>Manage billing</ListButton>
  </List>
</Surface>

In a popover

The List family was designed to drop straight into a Popover. The popover supplies the floating surface, the anchoring against the trigger, and the open/close transitions; the list supplies the rows. Wire selected to your selection state and you have a sort picker, an account menu, or a properties dropdown in a few lines.

When using ListButton inside a popover, drop the size to md (or sm for very dense menus) — popovers are typically narrower than sidebars and the default lg rows can feel oversized. Override the popover's default width via className (w-56, w-64) to match the longest row.

<PopoverRoot defaultOpen>
  <PopoverTrigger>
    <Button>Sort by</Button>
  </PopoverTrigger>
  <Popover className="w-56" offset={8}>
    <List>
      <ListTitle>Sort by</ListTitle>
      {OPTIONS.map((o) => (
        <ListButton
          key={o.id}
          size="md"
          selected={sort === o.id}
          onClick={() => setSort(o.id)}
          after={sort === o.id ? <CheckIcon /> : undefined}
        >
          {o.label}
        </ListButton>
      ))}
      <ListSeparator />
      <ListButton size="md" icon={<PlusIcon />}>
        Add custom sort
      </ListButton>
    </List>
  </Popover>
</PopoverRoot>

API Reference

List

children: ReactNodeList rows (typically ListButton, ListItem, ListTitle, or ListSeparator).
className: stringExtra classes for the list root.

ListButton

C extends ElementType = 'button'/Inherits from ButtonProps
after: ReactNodeSlot rendered right-aligned at the end of the row (e.g. badge, chevron).
as: ElementType'button'Polymorphic root element. Defaults to 'button'. Pass 'a' for navigation list rows.
children: ReactNodeTitle content of the row (the main text/element on the row).
className: stringExtra classes for the row's surface root.
color: ColorAccent color token. Forwarded to Button.color.
contentClassName: stringExtra classes for the row's inner content area.
disabled: booleanVisually dim the row and disable pointer events.
footer: ReactNodeSmall text rendered below the title. Typically secondary metadata.
footerClassName: stringExtra classes for the footer element.
header: ReactNodeSmall text rendered above the title. Typically a category, date, or eyebrow label.
headerClassName: stringExtra classes for the header element.
icon: ReactNodeIcon node rendered on the left, before the inner content column.
iconClassName: stringExtra classes for the icon wrapper.
innerContentClassName: stringExtra classes for the inner column (the wrapper around header/title/footer).
outline: booleanfalseOutline ring on the row. Default false. Effective value is outline || selected, so a selected row always shows the ring.
readOnly: booleanBlock clicks while keeping the row visually enabled.
rounded: booleantrueDefault true. Forwarded to the underlying Button.
selected: booleanMarks this list row as selected. When true, forces variant to 'gradient' and outline to true regardless of the props passed - used for the active row in a list.
size: ButtonSize'lg'Default 'lg' (vs Button's 'md') - list rows usually want more vertical space.
titleClassName: stringExtra classes for the title row (the wrapper around children).
variant: SurfaceVariant'transparent'Surface variant. Default 'transparent' so rows blend into the list surface.

Overridden to 'gradient' when selected is true.

ListItem

children: ReactNodeRow content. Non-interactive list row (use ListButton for clickable rows).
className: stringExtra classes for the row root.

ListSeparator

children: ReactNodeOptional content rendered on the divider (rarely used).
className: stringExtra classes for the separator root.

ListTitle

children: ReactNodeTitle content (typically a short uppercase label).
className: stringExtra classes for the title root.