Resource List
Display resources with status indicators, metadata and optional interactive rows.
Installation
pnpm dlx shadcn@4.18.0 add @siteplane/resource-listComplete the one-time setup first. Registry JSON lists source files and dependencies.
For this page’s examples, also install:
pnpm dlx shadcn@4.18.0 add @siteplane/cardUsage
import {
ResourceList,
ResourceListItem,
ResourceListIndicator,
ResourceListContent,
ResourceListTitle,
ResourceListDescription,
ResourceListMeta,
} from "@/siteplane/components/resource-list";
<ResourceList>
<ResourceListItem>
<ResourceListIndicator tone="success" />
<ResourceListContent>
<ResourceListTitle>Website redesign</ResourceListTitle>
<ResourceListDescription>Design team</ResourceListDescription>
</ResourceListContent>
<ResourceListMeta>Active</ResourceListMeta>
</ResourceListItem>
</ResourceList>;Composition
ResourceList
├── ResourceListHeader
└── ResourceListItem
├── ResourceListIndicator
├── ResourceListContent
│ ├── ResourceListTitle
│ └── ResourceListDescription
└── ResourceListMetaThe header, the indicator and the meta are optional, and a row without an indicator or a meta takes no room for them. In a row with a description, the indicator and text in the meta, such as a date, sit on the line of the title. A control in the meta, such as a button or a chevron, stays centered on the row.
A title and a description stay on one line and end with an ellipsis when they are too long, so a dense list keeps its rhythm. Both carry a title attribute with their full text, which the browser shows on hover; pass a title of your own to replace it. Text children give the attribute in the server-rendered HTML. When an element is among the children, such as a quieter suffix, the attribute is set from the rendered text once the list has mounted, so a list rendered by a Server Component hydrates without a mismatch.
Navigation
Set interactive and render an anchor to turn each row into a link. active marks the current page with aria-current="page".
Icon indicators
Give an indicator an icon and a label so the status isn't conveyed by color alone. Without a label, the indicator is hidden from assistive technologies.
Density and surface
Set density="compact" for long lists and variant="soft" to group the rows on a subtle surface. Without density, a list takes the density of its SiteplaneUIProvider. Only the list's own density decides: a compact container around a list, such as a compact Card, leaves it as it is.
Aligning with the content around the list
A row's content sits 12 px inside the list, because the row has its own padding and its hover and selected fills surround the content. Set flush on ResourceList to line the content up with the content around the list instead, such as a Filter Bar or a Card heading. The fills keep their inset look and reach past the edge of the list by the row padding, so put the list in a container with at least that much room, as a Card panel has. On the soft surface the reach is 16 px, because the surface's own padding adds to it. The list is as wide as its container plus that reach, so set a width on a wrapper instead of on the list.
Divided rows
Set variant="divided" to separate the rows with the hairline that cards and separators draw. The line runs across the row content, from where the text starts to where it ends, in the middle of the space between two rows, so a current or selected row's fill stays clear of it. The first row keeps no space above its content and the last row none below it: the list reaches past its top and bottom by the row padding, as flush makes it reach past its sides, so the text lines up with the content around the list. Combine it with flush in a Card panel, as here. A row that is added or removed brings or takes its line along.
Clickable rows and a header
When the rows are clickable (interactive), their hover surface takes the whole row, so the lines run as wide as that surface instead of ending at the text, with flush and without it. Put a ResourceListHeader first in the list to name it. Its text starts where the rows' text does, in the muted color of their metadata, and a second child, such as an action, sits at its end. In a divided list the header gets the same line below it as the rows, and the first row sits as far below that line as every row sits below its own. Without a header, the list keeps the geometry described above. Render the header as a heading with render, such as render={<h3 />}, when it belongs to the page's outline.
Adding and removing rows
Rows that are added, removed or reordered animate on their own: a row expands and fades in, a leaving row fades out and collapses, and the rows around it glide into their new place. Give every row a stable key, such as an id and never the index, and render the rows as direct children of ResourceList. When a row that holds focus leaves, for example because its Remove button was pressed, focus moves to the next row, or to the previous one at the end. The same goes for focus that a popup opened from the row would return to it.
API reference
| Prop | Type | Default | Description |
|---|---|---|---|
Resource | ResourceListDensity: default | compact | provider / default | Row spacing. |
Resource | boolean | false | Aligns the row content with the edges of the list. The rows reach past the edges by their padding, so the hover and selected fills stay inset. |
Resource | ResourceListVariant: default | soft | divided | default | soft groups the rows on a subtle surface; divided draws a hairline between two rows and keeps no space outside the first and the last row's content. With interactive rows the hairline is as wide as their hover surface. |
Resource | boolean | false | Current item: aria-current="page" on a linked row, aria-current="true" on other rows. |
Resource | boolean | unset | Selected appearance. On an interactive button row it also sets aria-pressed. |
Resource | boolean | false | Render the row using a real Button. |
Resource | boolean | false | Unavailable presentation; Button guards activation for interactive rows. |
Resource | bar | diamond | dot | ring | dot | Marker shape. |
Resource | chart-1 … chart-5 | error | info | neutral | success | warning | neutral | Marker color. muted still works as a deprecated alias of neutral. |
Resource | ReactNode | unset | Optional icon content. |
Resource | string | unset | Accessible marker label. |
Resource | boolean | unset | Shows the selection check alongside metadata. The check draws in when the value becomes true and out when it becomes false, and appears instantly under reduced motion. |
ResourceListProps, ResourceListItemProps and ResourceListIndicatorProps export
these contracts, together with density/variant/indicator tone/shape types.
List/Item/Content accept useRender div props; Indicator/Title/Description/Meta
accept useRender span props. All accept render, string className and refs. A
non-interactive row is a div; active/selected do not create listbox selection, a
form value or keyboard navigation. No row data model or loading/empty props are
provided. Rows animate through the motion component, which installs with
Resource List; reduced motion makes every change instant.
ResourceListHeader accepts useRender div props, as the list does, and has no props of its own.
ResourceListIndicatorTone and ResourceListIndicatorShape name the tone and shape unions listed above; ResourceListIndicatorDeprecatedTone holds the earlier muted.
Usage notes
Use ListBox for managed keyboard selection. On an interactive button row, selected sets aria-pressed; pass selected={false} to the other selectable rows so each one reports its state. On links and static rows, and on ResourceListMeta, selected changes only the appearance. For a resource collection, the application owns fetching, ordering, empty/error messages and selected-record state. Avoid nested buttons inside an interactive row: put an action button in a static row, as in the Remove example. A row inside a wrapper component, and anything else the list contains, appears and leaves at once.