List
Settings and records as rows with a label, a description and a control on the right.
import { List, Switch } from "@openperf/deft";
import { Preview } from "../../src/Preview";
export default function ListBasic() {
return (
<Preview block={true}>
<List.Root>
{Array.from({ length: 2 }, (_, index) => (
<List.Field description="Description" key={index} label={`Label ${index + 1}`}>
<Switch.Root aria-label={`Label ${index + 1}`} />
</List.Field>
))}
</List.Root>
</Preview>
);
}
- Use a list for settings and for short collections people manage one by one: members, tokens, webhooks. Use Table when people scan and compare many rows.
List.Fieldpairs one control with its label and description. PasshtmlForwith the control’sidso the label is alabel.- The label names the setting; the description says what it changes, in one sentence.
- The list sits on the inset background, like a panel, and rows are split by hairline dividers.
- Give inputs, textareas, selects and buttons inside a list
variant="field", so they stand out from the inset background. layout="inline", the default, keeps the control on the label’s line at any width, in a fixed column on the right, or the right half of the row in a narrow list. Use it for switches, buttons, short text and fields without a description.layout="wrap"keeps the control on the right in wide lists and moves it under the label at full width in narrow ones. Use it for an input or select next to a description, or for a group of controls.layout="stacked"always puts the control under the label, for wide controls such as a textarea.List.Emptyholds the empty state inside the list. Fill it like an Empty.
Records
import { Button, List } from "@openperf/deft";
import { IconDots, IconPlus } from "@tabler/icons-react";
import { Preview } from "../../src/Preview";
export default function ListRecords() {
return (
<Preview block={true}>
<List.Root>
{Array.from({ length: 2 }, (_, index) => (
<List.Item key={index}>
<List.Icon variant="tile">
<IconPlus />
</List.Icon>
<List.Header>
<List.Label>Item {index + 1}</List.Label>
<List.Description>Description</List.Description>
</List.Header>
<List.Actions>
<Button.Root aria-label="Options" shape="square" size="sm" variant="ghost">
<IconDots />
</Button.Root>
</List.Actions>
</List.Item>
))}
</List.Root>
</Preview>
);
}
Compose List.Item from Icon, Header with Label and Description, and Actions. Give the leading icon variant="tile" so every list uses the same tile; avatars go in a tile with shape="circle", which sizes the avatar to fill it. Put a row’s secondary actions in List.Actions, behind a ghost icon button at the end.
Links
import { List } from "@openperf/deft";
import { Preview } from "../../src/Preview";
export default function ListLinks() {
return (
<Preview block={true}>
<List.Root>
{Array.from({ length: 2 }, (_, index) => (
<List.Item key={index} render={<a href={`#item-${index + 1}`} />}>
<List.Header>
<List.Label>Item {index + 1}</List.Label>
</List.Header>
<List.Chevron />
</List.Item>
))}
</List.Root>
</Preview>
);
}
A row that opens a detail page renders as a link through render and ends with List.Chevron. The chevron is decorative (aria-hidden) and nudges right on row hover. A status such as a Dot goes between the header and the chevron. Rows without a page keep their action in List.Actions and have no chevron.
Buttons
import { Badge, List } from "@openperf/deft";
import { Preview } from "../../src/Preview";
export default function ListButtons() {
return (
<Preview block={true}>
<List.Root>
<List.Item render={<button type="button" />}>
<List.Header>
<List.Label>Option 1</List.Label>
</List.Header>
<List.Chevron />
</List.Item>
<List.Item render={<button disabled={true} type="button" />}>
<List.Header>
<List.Label>Option 2</List.Label>
<List.Description>Description</List.Description>
</List.Header>
<Badge.Root>Disabled</Badge.Root>
</List.Item>
</List.Root>
</Preview>
);
}
A row that picks an option without opening a page renders as a button through render. It takes the full width and gets the same hover and focus ring as a link. A disabled button keeps its look but loses the hover, so explain why in List.Description and show the state with a Badge in place of the chevron.
API
layout?"inline" | "wrap" | "stacked"
On Item and Field.
"inline" | "wrap" | "stacked""inline"render?ReactElement | function
On Item and Label. Renders a link or a button in place of the `div`.
ReactElement | functionlabelReactNode
On Field.
ReactNodedescription?ReactNode
On Field.
ReactNodehtmlFor?string
On Field and Label. Renders the label as a `label`.
stringvariant?"plain" | "tile"
On Icon.
"plain" | "tile""plain"shape?"square" | "circle"
On Icon, for tiles.
"square" | "circle""square"Parts: Root, Actions, Chevron, Control, Description, Empty, Field, Header, Icon, Item, Label. Field takes the Item props and renders Header, Label, Description and Control itself. Other props go to the native element.