Description List

Key and value rows for detail pages, with copy buttons and inline editing.

@drift-ui/description-listRegistry JSON
ID
usr_8f2a
Owner
Status
Active
Created
Oct 12, 2026, 09:41
On this page

Installation

The CLI also installs the npm packages and registry items it builds on. Register the namespace once first with https://drift-ui.dev/r/{style}/{name}.json. The CLI fills in {style} from your components.json and installs the Base UI or Radix build to match. See setup.

API reference

DescriptionList

A two column <dl> grid for DescriptionItem rows.

Other props go to the <dl>.

DescriptionItem

One label and value row. The value is the children, so editable rows take a string and the parent stores the edits.

Prop
labelRequiredReactNodeThe term. A string label also names the copy and edit buttons.
copyablebooleanAdds a button that copies the value text.
editablebooleanEdits the string children in place with InlineEdit.
onValueChange(value: string) => void | Promise<void>Called with the edited text. A pending promise keeps the field read-only and a rejection keeps it open.
placeholderstringShown by an editable row while its value is empty.

Other props go to the <div>.

Source

components/ui/description-list.tsx
"use client"

import * as React from "react"
import { CheckIcon, CopyIcon } from "lucide-react"

import { useCopyToClipboard } from "@/hooks/use-copy-to-clipboard"
import { cn } from "@/lib/utils"
import { InlineEdit } from "@/components/ui/inline-edit"

function DescriptionList({ className, ...props }: React.ComponentProps<"dl">) {
  return (
    <dl
      data-slot="description-list"
      className={cn(
        "grid grid-cols-[minmax(6rem,auto)_1fr] gap-x-6 text-sm",
        className
      )}
      {...props}
    />
  )
}

function DescriptionItem({
  label,
  copyable = false,
  editable = false,
  onValueChange,
  placeholder,
  children,
  className,
  ...props
}: React.ComponentProps<"div"> & {
  label: React.ReactNode
  copyable?: boolean
  editable?: boolean
  onValueChange?: (value: string) => void | Promise<void>
  placeholder?: string
}) {
  const { copied, copy } = useCopyToClipboard()
  const valueRef = React.useRef<HTMLDivElement>(null)
  const labelText = typeof label === "string" ? label : undefined

  return (
    <div
      data-slot="description-item"
      className={cn(
        "col-span-2 grid min-h-11 grid-cols-subgrid items-center border-b py-1.5 last:border-b-0",
        className
      )}
      {...props}
    >
      <dt className="text-muted-foreground">{label}</dt>
      <dd className="flex min-w-0 items-center gap-1">
        <div ref={valueRef} className="min-w-0 flex-1 break-words">
          {editable ? (
            <InlineEdit
              aria-label={labelText}
              value={typeof children === "string" ? children : ""}
              placeholder={placeholder}
              onSave={onValueChange ?? (() => {})}
            />
          ) : (
            children
          )}
        </div>
        {copyable && (
          <button
            type="button"
            data-slot="description-item-copy"
            aria-label={labelText ? `Copy ${labelText}` : "Copy"}
            onClick={() =>
              copy(
                typeof children === "string"
                  ? children
                  : (valueRef.current?.textContent ?? "")
              )
            }
            className="inline-flex size-7 shrink-0 items-center justify-center rounded-md text-muted-foreground outline-none hover:bg-accent hover:text-accent-foreground focus-visible:ring-[3px] focus-visible:ring-ring/50 [&_svg]:size-3.5"
          >
            {copied ? <CheckIcon /> : <CopyIcon />}
          </button>
        )}
        {copyable && (
          <span role="status" className="sr-only">
            {copied ? "Copied" : ""}
          </span>
        )}
      </dd>
    </div>
  )
}

export { DescriptionItem, DescriptionList }