# Description list

Shows pairs of labels and values, such as a record's settings, in one to three columns.

- **Import:** `import { DescriptionList, DescriptionItem } from "@booleanpress/ui/description-list"`
- **Page:** <https://ui.booleanpress.com/components/description-list> · @booleanpress/ui 0.2.0

## Usage

Each `DescriptionItem` takes its label in `label` and its value as children.

```tsx
import { DescriptionItem, DescriptionList } from "@booleanpress/ui/description-list"

export function MailerSummary() {
  return (
    <DescriptionList>
      <DescriptionItem label="Provider">Amazon SES</DescriptionItem>
      <DescriptionItem label="From address">no-reply@acme.example</DescriptionItem>
    </DescriptionList>
  )
}
```

`orientation="vertical"` (the default) puts each label above its value; `horizontal` puts it beside, with the labels in a column as wide as the widest one, up to 40% of the list's width (shared between its columns), where a longer label wraps. `columns` lays out one, two or three items per row from the `sm` breakpoint, and one per row below it; `span` on an item stretches a long value across columns, never more than the list has. `bordered` draws the list as a table, the labels on the subtle fill. `size` sets 12, 14 or 16 px text with matching spacing, and defaults to the provider's `controlSize`.

`action` puts a control at the end of a value, such as a copy button; name it after the item. Values are the consumer's: format numbers and dates with `Intl` in the provider's locale.

## Examples

### Basic

A mailer's settings, each label above its value.

```tsx
import { DescriptionItem, DescriptionList } from "@booleanpress/ui/description-list"

export default function DescriptionListBasic() {
  return (
    <DescriptionList className="w-full max-w-sm">
      <DescriptionItem label="Provider">Amazon SES</DescriptionItem>
      <DescriptionItem label="From address">no-reply@acme.example</DescriptionItem>
      <DescriptionItem label="Region">eu-west-1</DescriptionItem>
      <DescriptionItem label="Daily limit">50,000 emails</DescriptionItem>
    </DescriptionList>
  )
}
```

### Horizontal

`orientation="horizontal"` puts each label beside its value, in a column of their own.

```tsx
import { Badge } from "@booleanpress/ui/badge"
import { DescriptionItem, DescriptionList } from "@booleanpress/ui/description-list"

export default function DescriptionListHorizontal() {
  return (
    <DescriptionList orientation="horizontal" className="w-full max-w-md">
      <DescriptionItem label="Provider">Amazon SES</DescriptionItem>
      <DescriptionItem label="From address">no-reply@acme.example</DescriptionItem>
      <DescriptionItem label="Status">
        <Badge variant="success">Active</Badge>
      </DescriptionItem>
      <DescriptionItem label="Last test sent">15 October 2026, 10:30</DescriptionItem>
    </DescriptionList>
  )
}
```

### Columns

`columns={3}` lays out three items per row; `span={3}` gives the subject a whole row.

```tsx
import { DescriptionItem, DescriptionList } from "@booleanpress/ui/description-list"

export default function DescriptionListColumns() {
  return (
    <DescriptionList columns={3} className="w-full">
      <DescriptionItem label="Requester">Ana Lima</DescriptionItem>
      <DescriptionItem label="Assignee">Priya Shah</DescriptionItem>
      <DescriptionItem label="Priority">High</DescriptionItem>
      <DescriptionItem label="Opened">14 October 2026</DescriptionItem>
      <DescriptionItem label="Channel">Email</DescriptionItem>
      <DescriptionItem label="Organisation">Acme Ltd</DescriptionItem>
      <DescriptionItem label="Subject" span={3}>
        SMTP login fails after the password was changed on the provider's side
      </DescriptionItem>
    </DescriptionList>
  )
}
```

### Bordered

`bordered` draws a table, the labels on the subtle fill: beside the values, and above them.

```tsx
import { DescriptionItem, DescriptionList } from "@booleanpress/ui/description-list"

export default function DescriptionListBordered() {
  return (
    <div className="flex w-full flex-col gap-6">
      <DescriptionList bordered orientation="horizontal" columns={2}>
        <DescriptionItem label="Organisation">Acme Ltd</DescriptionItem>
        <DescriptionItem label="Plan">Business</DescriptionItem>
        <DescriptionItem label="Seats">12 of 15</DescriptionItem>
        <DescriptionItem label="Renews">1 January 2027</DescriptionItem>
        <DescriptionItem label="Billing email">billing@acme.example</DescriptionItem>
      </DescriptionList>
      <DescriptionList bordered columns={3}>
        <DescriptionItem label="Organisation">Acme Ltd</DescriptionItem>
        <DescriptionItem label="Plan">Business</DescriptionItem>
        <DescriptionItem label="Seats">12 of 15</DescriptionItem>
        <DescriptionItem label="Renews">1 January 2027</DescriptionItem>
        <DescriptionItem label="Billing email">billing@acme.example</DescriptionItem>
      </DescriptionList>
    </div>
  )
}
```

### With actions

`action` puts a copy button at the end of each connection setting.

```tsx
import { useState } from "react"
import { CheckIcon, CopyIcon } from "lucide-react"
import { Button } from "@booleanpress/ui/button"
import { DescriptionItem, DescriptionList } from "@booleanpress/ui/description-list"

const SETTINGS = [
  { label: "SMTP host", value: "smtp.acme.example" },
  { label: "Port", value: "587" },
  { label: "Username", value: "mailer@acme.example" },
  { label: "API key", value: "bp_live_4f9a…c21e" },
]

export default function DescriptionListWithActions() {
  const [copied, setCopied] = useState<string | null>(null)

  const copy = (label: string, value: string) => {
    void navigator.clipboard?.writeText(value)
    setCopied(label)
  }

  return (
    <DescriptionList bordered orientation="horizontal" className="w-full max-w-md">
      {SETTINGS.map(({ label, value }) => (
        <DescriptionItem
          key={label}
          label={label}
          action={
            <Button variant="ghost" size="icon-sm" aria-label={`Copy ${label}`} onClick={() => copy(label, value)}>
              {copied === label ? <CheckIcon /> : <CopyIcon />}
            </Button>
          }
        >
          <code className="font-mono text-[0.8125rem]">{value}</code>
        </DescriptionItem>
      ))}
    </DescriptionList>
  )
}
```

### Sizes

`sm`, `default` and `lg` set 12, 14 and 16 px text with matching padding.

```tsx
import { DescriptionItem, DescriptionList } from "@booleanpress/ui/description-list"

const SIZES = ["sm", "default", "lg"] as const

export default function DescriptionListSizes() {
  return (
    <div className="flex w-full flex-col gap-6">
      {SIZES.map((size) => (
        <DescriptionList key={size} size={size} bordered orientation="horizontal" columns={2}>
          <DescriptionItem label="Provider">Postmark</DescriptionItem>
          <DescriptionItem label="Region">us-east-1</DescriptionItem>
          <DescriptionItem label="From address">receipts@acme.example</DescriptionItem>
          <DescriptionItem label="Daily limit">10,000 emails</DescriptionItem>
        </DescriptionList>
      ))}
    </div>
  )
}
```

## Accessibility

**Semantics.** A description list: `dl`, with each item a `div` holding a `dt` (the label) and a `dd` (the value), so a screen reader pairs every value with its label.

**Labels.** A `dl` takes no name of its own: put a heading above it. Name every action after its item ("Copy SMTP host"), as the same button repeats on every row.

**Focus.** The list takes no focus. Actions are tab stops in reading order.

**Known limits.**

- The columns are visual only: a screen reader reads the items in source order, row by row.
- Long values wrap. Very long words, such as keys or addresses, break at any character rather than overflow.

### Keyboard

| Key | Behaviour |
| --- | --- |

## API

### DescriptionList

Renders a `dl` and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `bordered` | `boolean` | `false` | Draws the list as a table: an edge round it and between the items, the labels on the subtle fill. |
| `columns` | `1 \| 2 \| 3` | `1` | How many items share a row, from the `sm` breakpoint (640 px); one per row below it. |
| `orientation` | `"horizontal" \| "vertical"` | `vertical` | `vertical` puts each label above its value; `horizontal` puts it beside, the labels in a column of their own. |
| `size` | `"default" \| "sm" \| "lg"` |  | The text size, 12, 14 or 16 px, and the spacing with it. Defaults to the provider's `controlSize`. |

### DescriptionItem

Renders a `div` and passes it every other prop.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label` (required) | `ReactNode` |  | The label, shown in the muted colour. |
| `action` | `ReactNode` |  | A control at the end of the value, such as a copy button. Name it after the item ("Copy host"). |
| `children` | `ReactNode` |  | The value. |
| `span` | `number` |  | How many columns the item spans, from the `sm` breakpoint, for a long value such as an address; at most `columns`. |

Every part takes `className`, merged with its defaults by `cn()`, and `ref`, which reaches the element it renders.

**Data attributes:** `data-slot="description-list"` (DescriptionList), `data-slot="description-item"` (DescriptionItem), and `data-orientation`, `data-size`, `data-bordered`.

## Theming

Labels are `--muted-foreground`, values `--foreground`. Bordered lists draw 1 px `--border` lines with a 6 px radius, and the labels sit on `--subtle`.

| Token | Used for |
| --- | --- |
| `--foreground` | text |
| `--muted-foreground` | text |
| `--subtle` | background |
