KeyValue
Every example on this page is live, drawn by the library's Storybook, where you can also change its props and see the result.
Displays a list of key-value pairs in vertical (definition list) or horizontal (side-by-side) layout.
Import
import { KeyValue } from "~lib/data-display/key-value";
Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
data | KeyValueRow[] | KeyValueSection[] | Yes | N/A | The rows, or sections of rows each under a heading |
orientation | "vertical" | "horizontal" | No | "vertical" | Layout direction |
divider | boolean | No | true | Show dividers between rows, and between columns with columns |
size | "xs" | "sm" | No | "sm" | Text size and row spacing |
truncate | boolean | No | false | Keep every key and value to one line ending in "…", the key giving way first |
columns | number | No | One column | Split the rows across up to this many columns, down the first before the next |
minColumnWidth | number | No | 160 | Narrowest a column gets, in pixels, before the list drops to fewer columns |
A KeyValueRow is one fact:
| Name | Type | Required | Description |
|---|---|---|---|
key | string | Yes | The fact's name |
value | ReactNode | No | The fact's value. Left out or empty, the key has the row to itself |
description | string | No | A second line under the key at the xs size, past any leading node |
leading | ReactNode | No | Sits before the key, centered on its line |
trailing | ReactNode | No | Sits beside the value |
done | boolean | No | Strikes the key, the description and the value's text through, for a finished task |
A KeyValueSection is { heading: string; rows: KeyValueRow[] }: the heading is set as a bold
xs line above its rows.
Examples
Vertical
Horizontal
No Divider
Compact
Use size="xs" in narrow columns: smaller text and tighter rows fit more facts in the same
height.
With Trailing Content
Give a row trailing content, such as a Badge or a Sparkline, to show it beside the value
instead of writing it into the value's text.
<KeyValue
orientation="horizontal"
data={[{ key: "AAPL", value: "$517.53", trailing: <Badge>▲ 0.92%</Badge> }]}
/>
With Leading Content
Give a row leading content, such as a Badge for a train's line, to show it before the key,
centered on the key's line, the way a departure board reads.
<KeyValue
orientation="horizontal"
data={[{ key: "Howard", value: "7:53 pm", leading: <Badge>Red</Badge> }]}
/>
Vertical Rows With Leading and Trailing Content
A vertical row draws leading before the key, centered on its line, in a column of its own, so
the value lines up under the key's text; trailing sits beside the value. A to-do list down a
slim column keeps the same row shape as one across a band: the tick box before the task, the
overdue badge beside its date.
<KeyValue
size="xs"
truncate
data={tasks.map((task) => ({
key: task.name,
value: task.due,
leading: <Icon name={task.done ? "square-check" : "square"} size={18} />,
trailing: task.overdue ? <Badge size="xs">Overdue</Badge> : undefined,
done: task.done,
}))}
/>
With a Description
Give a row a description to set a smaller second line under its key, at the xs size whatever
the list's size, such as the stop a departure leaves from. It starts past any leading node,
so it lines up with the key's text, and the value stays on the key's line. With truncate it is
kept to one line ending in "…" like the key. The description is part of its row, so every row
still keeps whole in columns and the columns balance to one height; a done row strikes its
description through with the rest of its text.
<KeyValue
orientation="horizontal"
size="xs"
truncate
data={departures.map((departure) => ({
key: departure.destination,
description: `from ${departure.stop}`,
value: departure.due,
leading: <Badge size="xs">{departure.line}</Badge>,
}))}
/>
Done
Set done on a row to strike its key, its description and its value's text through, in full ink, the way a
finished task or a bought item is crossed off. Its leading and trailing content, and a value
that is a node of its own such as a Badge, are left as they are.
<KeyValue
orientation="horizontal"
data={[
{ key: "Oat milk", value: "2 L", done: true },
{ key: "Eggs", value: "12" },
]}
/>
No Value
Leave a row's value out, or empty, for a fact that is a name alone, such as an ingredient with
no amount: the key has the whole row to itself rather than stopping short of a blank column.
<KeyValue
orientation="horizontal"
data={[
{ key: "Plain flour", value: "200 g" },
{ key: "A pinch of sea salt, or to taste" },
]}
/>
Truncated
Keys and values wrap by default, so a long key such as an ingredient reads in full. Set
truncate to keep every key and value to one line ending in "…" instead, so each row keeps one
height and a box that counts its rows never meets a row that grew. The key gives way first: in a
horizontal row the value is cut short only once the key has no room left, and leading and
trailing content is never cut.
<KeyValue
orientation="horizontal"
truncate
data={[
{
key: "Harlem/Lake via the Loop",
value: "8:04 pm",
leading: <Badge>Green</Badge>,
},
]}
/>
Columns
Set columns to split a long list into columns side by side, such as a line-status board in a
wide, short box. The rows run down the first column and then the next, every column is the same
width, and a rule sits between them (divider={false} leaves only a gap). Use it with truncate,
and give every row the same kind of content, such as a badge in each, so the rows are one height
and line up across the columns.
<KeyValue
orientation="horizontal"
size="xs"
truncate
columns={3}
data={lines.map((line) => ({
key: line.name,
value: <Badge variant="outline">{line.status}</Badge>,
}))}
/>
A box too narrow for every column at minColumnWidth gets fewer columns. The browser works this
out from the box, so a saved preview shown in a narrower box drops a column the same way.
The columns are balanced to one height, so in a Brim a list taller than its box is cut across every column at the same line, under the fade.
<Brim>
<KeyValue
orientation="horizontal"
size="xs"
truncate
columns={3}
data={rows}
/>
</Brim>
Sections
Pass data as sections, each a heading over its rows, to group a list the way it is read,
such as a statement's payments under the day they were made. Each heading is a bold xs line
across the list with a rule above it and none between it and its own rows, so the rules keep one
rhythm from the first row to the last.
<KeyValue
orientation="horizontal"
size="xs"
truncate
data={[
{
heading: "Today",
rows: [
{ key: "Tesco Express", value: "−£12.40" },
{ key: "Pret A Manger", value: "−£5.10" },
],
},
{ heading: "Yesterday", rows: [{ key: "Spotify", value: "−£11.99" }] },
]}
/>
With columns the sections flow down one column and into the next the way rows do, balanced to
one height, and a heading never stays at the foot of a column without its first row. In a
Brim the list is cut across every column at the same
line.