Description List React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
A React description list for detail pages: key-value grids for clients, invoices and profiles, with copy buttons, skeletons and iOS rows.
Read the details in document order: label above value on phones and in narrow cards, one to three columns once the container is wide enough (it follows its container, not the window). Hover a row with a mouse or Tab into it and a copy button appears next to a copyable value; click it or press Enter and the icon turns into a check while the tooltip says Copied. On a phone the copy buttons are always visible and have a 44px hit area. Tab only stops on real controls (copy buttons, actions, inline edits). While loading, values are skeleton bars that crossfade into the real text. The grouped variant reads like iOS settings: label left, value right, long values move under the label on narrow screens.
Description List is a Pro React component in the React data display components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add description-list, together with Copy Button, Skeleton, Motion presets and UIProvider and the npm package motion.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add description-listDescription List is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import DescriptionList from @/components/ui/description-list.
import { Pencil } from "lucide-react"
import { Button } from "@/components/ui/button"
import { DescriptionList, type DescriptionListItem } from "@/components/ui/description-list"
const CLIENT: DescriptionListItem[] = [
{ label: "Company", value: "Tech Solutions Inc." },
{ label: "Tax ID", value: "12-3456789", copy: true, mono: true },
{ label: "Registration no.", value: "DE-5512345", mono: true },
{ label: "Address", value: "233 S Wacker Dr, Chicago, IL 60606", span: 2 },
{ label: "IBAN", value: "GB29 NWBK 6016 1331 9268 19", copy: "GB29NWBK60161331926819", mono: true },
{ label: "Balance", value: "$12,480.00" },
]
export function ClientDetails({ loading }: { loading: boolean }) {
return (
<DescriptionList
title="Client details"
description="Billing details"
actions={<Button variant="soft" size="sm" leftIcon={<Pencil />}>Edit</Button>}
items={CLIENT}
loading={loading}
/>
)
}Props
itemsDescriptionListItem[]{ id?, label, value, description?, icon?, copy?: boolean | string, span?: 1 | 2 | 3 | "full", mono?, action?, hidden? }. copy: true copies a string or number value, a string copies that string, false opts out of copyable. span clamps to columns; hidden items are skipped.
columnsdefault21 | 2 | 3Columns once the container is at least 32rem (2) or 48rem (3) wide. Always one column below 32rem, so it stacks on phones and in narrow cards.
layoutdefault"auto""auto" | "stacked" | "inline"stacked: label above value. inline: a label column and a value column (from 24rem of container width, so phones stack). auto: inline when there is one column and the container is at least 40rem wide, stacked otherwise.
labelWidthdefault160numberWidth of the label column in px for the inline layout. Never more than 35% of the row.
variantdefault"divided""plain" | "divided" | "surface" | "grouped"plain: no lines. divided: hairlines between rows, per column. surface: the whole list, header included, on a raised panel. grouped: iOS inset rows on a panel, label left and value right, 44px rows (ignores columns and layout).
sizedefault"md""sm" | "md" | "lg"Label 12 / 13 / 14px, value 13 / 14 / 15px, row padding 8 / 12 / 16px.
copyabledefaultfalsebooleanEvery item with a string or number value gets a copy button. Items opt out with copy: false; React element values need copy set to a string.
copyLabeldefault"Copy"stringThe copy button's tooltip and aria-label, followed by the item label: "Copy CUI".
copiedLabeldefault"Copied"stringTooltip and screen reader announcement after a copy.
emptyValuedefault"–"ReactNodeShown, muted, for null, undefined, false and "" values. Empty values are never copyable.
loadingdefaultfalsebooleanValues become skeleton bars (widths vary by row, the same on the server and in the browser) and the <dl> gets aria-busy. With no items, skeletonCount fake rows show. Skeletons crossfade into the values.
skeletonCountdefault6numberRows to fake while loading before any items exist.
titledefault""ReactNodeThe header title. With a title the root becomes a <section> labelled by it.
descriptiondefault""ReactNodeA muted line under the title.
titleAsdefault"h3""h2" | "h3" | "h4"Heading level of the title, to fit the page outline.
actionsReactNodeButtons at the end of the header, like an Edit button.
childrenReactNodeRows built from parts instead of items: DescriptionItem, or DescriptionTerm and DescriptionDetails for full control (see Parts).
classNamesPartial<Record<"header" | "title" | "description" | "actions" | "body" | "grid" | "item" | "term" | "icon" | "details" | "value" | "hint" | "copy" | "action" | "skeleton", string>>Extra classes for the inner parts. grid is the <dl>, item the row <div>, term the <dt>, details the <dd>, copy the copy button.
classNamestringExtra classes for the root, merged last.
Parts
DescriptionItem
componentOne row, for lists written as JSX. Takes the same fields as an item (label, value or children, description, icon, copy, span, mono, action, hidden) and reads size, variant and the copy labels from the list. Without a label its children render as they are, so you can compose the term and details yourself.
labelReactNodeThe term. Leave it out to write DescriptionTerm and DescriptionDetails as children.
valueReactNodeThe details; children work too.
copyboolean | stringA copy button for the value, or for the given string.
span1 | 2 | 3 | "full"Columns the row spans in the grid layout.
monobooleanMonospaced value, for IDs, IBANs and keys.
DescriptionTerm
componentThe <dt> with the label look and an optional icon prop. Use it inside a DescriptionItem without a label.
DescriptionDetails
componentThe <dd> with the value look: muted, end-aligned and tabular in grouped rows, plain text elsewhere.
DescriptionListItem
typeOne entry of items: id, label, value, description, icon, copy, span, mono, action and hidden.
Examples
Invoice summary, grouped
iOS inset rows: label left, value right, a long value moves under its label on a phone.
Inline edit
InlineEdit as the value: click to edit, Enter saves, Escape cancels.
Three columns
An order on a surface panel; three columns from 48rem of container width, the address spans the row.
Loading
Labels stay, values are skeletons that crossfade in after 1.2s. Reload to replay.
Inline profile, small
layout="inline", size="sm", copyable with custom copy labels; stacks below 24rem (phones).
Narrow container
A 280px sidebar card stacks into one column on desktop too; built from DescriptionItem parts.
Dependencies
Files
- components/ui/description-list.tsx
FAQ
Is the React Description List component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install Description List?
Run npx wingo-ui@latest add description-list. It also adds the Wingo UI items it builds on (Copy Button, Skeleton, Motion presets and UIProvider) and the npm package motion. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/description-list.
How does Description List behave on phones?
Read the details in document order: label above value on phones and in narrow cards, one to three columns once the container is wide enough (it follows its container, not the window). On a phone the copy buttons are always visible and have a 44px hit area.
See also
Used in
More React data display components
Articles using this component
- Order History Page Design: List, Detail and Actions
Build it: screens and apps9 min read
- Responsive Table Mobile Layout: Rows to Cards in React
Mobile UI in React11 min read