Skip to the content

ComponentsData

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"

Usage

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

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.

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.

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.

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.

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.

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.

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

Keyboard
KeyBehaviour

API

DescriptionList

Renders a dl and passes it every other prop.

DescriptionList props
PropTypeDefaultDescription
borderedbooleanfalseDraws the list as a table: an edge round it and between the items, the labels on the subtle fill.
columns1 | 2 | 31How many items share a row, from the sm breakpoint (640 px); one per row below it.
orientation"horizontal" | "vertical"verticalvertical 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.

DescriptionItem props
PropTypeDefaultDescription
labelrequiredReactNodeThe label, shown in the muted colour.
actionReactNodeA control at the end of the value, such as a copy button. Name it after the item ("Copy host").
childrenReactNodeThe value.
spannumberHow 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.

The tokens its classes read. Change their values in your theme, and it follows.

Theme tokens
TokenUsed for
--foregroundtext
--muted-foregroundtext
--subtlebackground