rc-list
Shared-column list layout with standard and segmented appearances, optional leading and trailing content, and native-backed selection.
- Package
@rcarls/rc-list- Element
<rc-list, rc-list-item>- Native dependency
- Author-owned radio or checkbox controls in selecting lists
- State model
- Native checked/disabled state mirrored to list items
- Main events
- None
Installation
- npm
- Yarn
- pnpm
npm install @rcarls/rc-webcomponents@next
yarn add @rcarls/rc-webcomponents@next
pnpm add @rcarls/rc-webcomponents@next
import '@rcarls/rc-webcomponents/rc-list/define';
Usage
Place rc-list-item elements as direct children, and name the list with aria-label or
aria-labelledby.
<rc-list aria-label="Recent recipes">
<rc-list-item>Apple pie</rc-list-item>
<rc-list-item>Tomato soup</rc-list-item>
</rc-list>
Live demo
Accessibility
The list and its items expose the list and listitem roles through ElementInternals, so no
role attributes appear in the markup. Rows add no keyboard behavior of their own; each feature
keeps native controls in charge:
- Reordering uses native handle buttons, with Space or Enter to pick up and drop, arrow keys, Home, and End to move, and announcements in a polite live region.
- Expandable details wires
aria-expandedandaria-controlsonto the author's toggle button. - Native selection leaves the radio or checkbox as the source of checked, disabled, and accessibility state.
- Action targets extend pointer reach only; the target stays its own tab stop.
Shared columns
rc-list owns leading, content, and trailing grid columns. Each direct
rc-list-item participates in them through CSS subgrid, so content stays
aligned even when leading visuals or trailing actions have different sizes.
If no visible row uses an optional slot, the corresponding column and gap
collapse across the whole list.
Mark an icon in the leading slot with data-rc-list-leading when the active
theme should apply its standard icon size and alignment. Other leading media
such as images, avatars, and selection controls retain their authored geometry.
<rc-list aria-label="Recent recipes">
<rc-list-item>
<span slot="leading" data-rc-list-leading aria-hidden="true">★</span>
Apple pie
<span slot="trailing">42 min</span>
</rc-list-item>
</rc-list>
Primary content truncates to one line with an ellipsis by default. Set
--rc-list-item-content-white-space: normal on a list or item for deliberate
multi-line rows.
Adapt region placement
The shared column sizes and each item's leading, content, and trailing placements are exposed as a coordinated CSS custom-property contract. Use those tokens when a container query should rearrange the same regions without changing source order, focus behavior, or semantics:
.list-region {
container-type: inline-size;
}
@container (max-width: 24rem) {
.list-region rc-list {
--rc-list-trailing-size: 0;
--rc-list-trailing-gap: 0;
--rc-list-item-grid-template-rows: auto auto;
--rc-list-item-row-gap: 0.25rem;
--rc-list-item-leading-grid-row: 1 / -1;
--rc-list-item-content-grid-row: 1;
--rc-list-item-trailing-grid-column: content-start / content-end;
--rc-list-item-trailing-grid-row: 2;
--rc-list-item-trailing-justify-self: start;
}
}
Keep the named leading-*, content-*, and trailing-* column lines in
placement values. They are the stable coordination points supplied by
rc-list's shared subgrid.
Variants
variant="standard" is the default continuous list. variant="segmented"
creates a grouped set of discrete rows; themes use the item position assigned
by the list to shape first, middle, last, and only rows.
Ordered lists
Set ordered when item order carries meaning, such as recipe steps. Each visible item receives
aria-posinset and aria-setsize, so assistive technology reports its position. The list
renders no counters: numbering the content shows, if any, stays in the item text. Items that
already carry authored positions, as in a virtualized slice, keep them.
<rc-list ordered aria-label="Directions">
<rc-list-item>1. Heat the oven.</rc-list-item>
<rc-list-item>2. Mix the batter.</rc-list-item>
</rc-list>
Reordering
Set reorderable to let people change the order. It implies ordered. Give each item a native
<button data-rc-reorder-handle>, usually in the trailing slot, and label it yourself. Handles
without an authored aria-description get a short keyboard hint, which reorderMessages can
replace along with every announcement.
- Pointer: press a handle and drag. The lifted item follows the pointer, the items it passes shift out of its way, and the nearest scroll container scrolls near its edges. Escape cancels. A press without movement stays a plain button press.
- Touch and pen: as on Android, a long press anywhere on an item also picks it up, then
drag. Moving before the press completes scrolls the list instead, and lifting the finger
after the drag does not also activate a control in the item. A mouse drags only by the
handle. Item text cannot be selected in a
reorderablelist. - Keyboard: Space or Enter picks the item up. The block-axis arrow keys, Home, and End move it, Space or Enter drops it, and Escape returns it to where it started. Moving focus away drops it. Each step is announced through a polite live region in the nearest open dialog or popover, else the document body.
Each committed move fires a cancelable rc-list-reorder event with { item, from, to }. Left
alone, the list moves the item's node itself, so plain HTML needs no script. A framework that
renders the items cancels the event and reorders its own data; focus returns to the moved
item's handle even when the framework replaces the item's nodes. A pointer drag fires once on
drop. The keyboard fires on every step, and once more when a reorder is canceled.
<rc-list reorderable aria-label="Photo order">
<rc-list-item>
Cover photo
<button slot="trailing" type="button" data-rc-reorder-handle aria-label="Reorder cover photo">
⠿
</button>
</rc-list-item>
<rc-list-item>
Plated
<button slot="trailing" type="button" data-rc-reorder-handle aria-label="Reorder plated">
⠿
</button>
</rc-list-item>
</rc-list>
list.addEventListener('rc-list-reorder', (event) => {
event.preventDefault();
photos = move(photos, event.detail.from, event.detail.to);
});
While an item is lifted it carries data-dragging and the list carries data-reordering. Themes
style the lifted item with the --rc-list-item-dragged-* properties and animate the shifting
items with --rc-list-item-reorder-duration. Reduced motion turns that shift off.
Expandable details
An item can carry a supporting region below its row in the details slot. Mark a button inside
the item, usually in the trailing slot, with data-rc-list-item-toggle. The item wires the
button's aria-expanded and aria-controls, keeps the region hidden while collapsed, and
fires rc-list-item-toggle with the requested state. Use default-expanded for an initial
uncontrolled state, or set expanded to control it; assign undefined to release control.
<rc-list aria-label="Ingredients">
<rc-list-item>
3 garlic cloves, chopped
<button slot="trailing" type="button" data-rc-list-item-toggle aria-label="Garlic details">
i
</button>
<div slot="details">"garlic" is not a recognized unit of measure.</div>
</rc-list-item>
</rc-list>
The region spans the text and trailing columns, so its content wraps freely even while the row text truncates.
Native selection
Use selection="single" with one direct radio per item, or
selection="multiple" with one direct checkbox per item. The native input is
the source of truth for checked, disabled, form, reset, and accessibility
behavior. The component mirrors native state to the row and makes the otherwise
non-interactive row surface activate its input. Independent links, buttons,
inputs, selects, and textareas keep their own behavior. Radios in a
single-selection list should share a name, exactly as they would without the
custom element enhancement.
<rc-list variant="segmented" selection="single" aria-label="Sort recipes">
<rc-list-item>
<input slot="leading" type="radio" name="sort" value="recent" checked />
Recently updated
</rc-list-item>
<rc-list-item>
<input slot="leading" type="radio" name="sort" value="title" />
Recipe title
</rc-list-item>
</rc-list>
Programmatic writes to selected, disabled, and interactive are silent.
In a selecting list, the next native change synchronizes those presentation
states from the input again.
Action targets
For a row whose primary action is an anchor or button rather than its selection
input, set interactive and point action-target at that same-root native
control. A plain primary click on otherwise non-interactive row content invokes
the target. Nested links, buttons, form controls, editable content, and
modifier-qualified or non-primary clicks retain their own behavior.
<rc-list aria-label="Theme settings">
<rc-list-item interactive action-target="open-theme">
<span slot="leading" aria-hidden="true">◐</span>
Theme
<button id="open-theme" type="button" slot="trailing">Choose</button>
</rc-list-item>
</rc-list>
The target remains its own keyboard tab stop. Delegation extends pointer reach without creating a second interactive role on the row.
Scope
The package intentionally does not implement drag-and-drop between lists, swipe-to-reveal actions, virtualization, infinite loading, or pull-to-refresh. Those behaviors have distinct data, focus, scrolling, and platform contracts and can compose with list rows when their APIs are defined.
To render only part of a long list, wrap it in rc-virtual-scroller and give
each rendered row aria-posinset and aria-setsize for its place in the whole set. The list
then gives first- and last-row styling only to the set's real first and last
rows, not to the ends of the rendered slice.
Localization
The reorder handle hint and announcements follow the list's own lang, else <html lang>.
Override them with reorderMessages, or register them for every list under reorder:
instructions, grabbed, moved, dropped, canceled. See
Localization.
API
Properties
| Property | Markup | Type | Default | Description |
|---|---|---|---|---|
variant | variant | RCListVariant | 'standard' | Appearance hint. |
selection | selection | RCListSelection | 'none' | Native selection coordination mode. |
ordered | ordered | boolean | false | Whether item order is meaningful; positions are exposed without rendered counters. |
reorderable | reorderable | boolean | false | Whether people can reorder items with a handle. Implies `ordered`. |
reorderMessages | JS property only | RCListReorderMessages | undefined | Not specified | Overrides for the reorder announcements and the handle hint. |
role | JS property only | string | 'list' | No description provided. |
Events
| Event | Detail type | Description |
|---|---|---|
rc-list-reorder | No detail type documented | Fires, cancelable, when a reorder moves an item; `detail: { item, from, to }`. Unless the event is canceled the list moves the item's node itself. A framework that renders the items cancels it and reorders its own data. A pointer drag fires once on drop; the keyboard fires on each step, and once more to move the item back when the reorder is canceled. |
Slots
| Name | Description |
|---|---|
(default) | Direct `rc-list-item` children. |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--rc-list-padding-inline | 0 | Shared inline content padding. |
--rc-list-padding-block | 0 | List block padding. |
--rc-list-leading-size | minmax(0, max-content) | Shared leading column size when any visible item has leading content. |
--rc-list-leading-gap | 1rem | Gap after the leading column. |
--rc-list-trailing-size | minmax(0, max-content) | Shared trailing column size when any visible item has trailing content. |
--rc-list-trailing-gap | 1rem | Gap before the trailing column. |
--rc-list-row-gap | 0 | Gap between rows. ## Reordering With `reorderable`, a native `<button data-rc-reorder-handle>` inside an item starts a reorder. Label the button yourself, for example "Reorder Photo 2". Handles without an authored `aria-description` get a short keyboard hint. - Pointer: press the handle and drag. The lifted item follows the pointer, the items it passes shift out of its way, and the nearest scroll container scrolls near its edges. Escape cancels. - Touch and pen: as on Android, a long press anywhere on an item also picks it up, then drag. Moving before the press completes scrolls the list instead. A mouse drags only by the handle. Item text cannot be selected in a reorderable list. - Keyboard: Space or Enter picks the item up, the block-axis arrow keys, Home, and End move it, Space or Enter drops it, and Escape returns it. Leaving the handle drops it. Each step is announced through a polite live region mounted in the nearest open `dialog` or popover, else the document body; override the text with `reorderMessages`. Focus stays on the moved item's handle, and is restored to the item at the new position when a framework replaces the item's nodes. The lifted item carries `data-dragging`, and the list carries `data-reordering`. The list writes inline `translate` offsets to items while a pointer drag previews a move. |
CSS Parts
| Part | Description |
|---|---|
list | Shared list grid. |
Properties
| Property | Markup | Type | Default | Description |
|---|---|---|---|---|
selected | selected | boolean | false | Presentational selected state. Host writes are silent. |
disabled | disabled | boolean | false | Presentational disabled state. Host writes are silent. |
interactive | interactive | boolean | false | Enables row interaction styling. |
actionTarget | action-target | string | '' | ID of a same-root anchor or button that receives forwarded surface clicks — the same contract as `rc-card`'s `action-target`. Checked before the native radio/checkbox fallback, so a row can combine a selection input with its own `action-target` if it ever needs to (uncommon, but not fought against). |
defaultExpanded | default-expanded | boolean | false | Initial expanded state of the `details` region for uncontrolled usage. |
expanded | expanded | boolean | Not specified | Current expanded state of the `details` region. Host writes are silent and take control; assign `undefined` to release control. |
role | JS property only | string | 'listitem' | No description provided. |
Events
| Event | Detail type | Description |
|---|---|---|
rc-list-item-toggle | No detail type documented | Fires when the toggle button requests a new expanded state. |
Slots
| Name | Description |
|---|---|
leading | Optional leading visual or native selection control. Add `data-rc-list-leading` when a theme should apply its standard leading-icon geometry. |
(default) | Primary row content. It truncates to one line by default. |
trailing | Optional trailing metadata or action. |
details | Optional supporting region below the row, shown while expanded. A descendant `button[data-rc-list-item-toggle]` (usually in `trailing`) toggles it; the item wires the button's `aria-expanded` and `aria-controls` and the region's `hidden` state. |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--rc-list-item-min-block-size | 3rem | Minimum rendered row height. |
--rc-list-item-padding-block | 0.5rem | Row block padding. |
--rc-list-item-color | CanvasText | Row foreground color. |
--rc-list-item-background | transparent | Row surface color. |
--rc-list-item-selected-color | Not specified | Selected foreground color. |
--rc-list-item-selected-background | transparent | Selected surface color. |
--rc-list-item-border | 0 | Row border shorthand. |
--rc-list-item-border-radius | 0 | Row corner radius. |
--rc-list-item-box-shadow | none | Row shadow. |
--rc-list-item-divider | 0 | Divider border shorthand. |
--rc-list-item-divider-inset-inline | 0 | Divider inline inset. |
--rc-list-item-grid-template-rows | auto | Internal row track definition. |
--rc-list-item-row-gap | 0 | Gap between internal row tracks. |
--rc-list-item-leading-grid-column | leading-start / leading-end | Leading region column placement. |
--rc-list-item-leading-grid-row | auto | Leading region row placement. |
--rc-list-item-content-grid-column | content-start / content-end | Content region column placement. |
--rc-list-item-content-grid-row | auto | Content region row placement. |
--rc-list-item-content-white-space | nowrap | Content wrapping behavior. |
--rc-list-item-content-text-overflow | ellipsis | Content overflow marker. |
--rc-list-item-trailing-grid-column | trailing-start / trailing-end | Trailing region column placement. |
--rc-list-item-trailing-grid-row | auto | Trailing region row placement. |
--rc-list-item-trailing-justify-self | end | Trailing region alignment within its grid area. |
--rc-list-item-state-layer-color | currentColor | Interaction state layer color. |
--rc-list-item-state-layer-opacity | 0 | Resting interaction state layer opacity. |
--rc-list-item-hover-state-layer-opacity | 0.08 | Hover state opacity. |
--rc-list-item-focus-state-layer-opacity | 0.1 | Focus state opacity. |
--rc-list-item-pressed-state-layer-opacity | 0.1 | Pressed state opacity. |
--rc-list-item-disabled-opacity | 0.38 | Disabled row opacity. |
--rc-list-item-dragged-background | Canvas | Surface of an item lifted for reordering. Falls back to `--rc-list-item-background`, then `Canvas`, so the item stays opaque. |
--rc-list-item-dragged-box-shadow | none | Shadow of a lifted item. Falls back to `--rc-list-item-box-shadow`. |
--rc-list-item-dragged-state-layer-opacity | 0.16 | Lifted item state opacity. |
--rc-list-item-reorder-duration | 0ms | Duration of the shift that makes room for a lifted item. Read from the item, applied from light-DOM base styles. |
--rc-list-item-reorder-easing | ease | Easing of that shift. |
--rc-list-item-transition-duration | 0ms | Row state transition duration. |
--rc-list-item-transition-easing | ease | Row state transition easing. |
--rc-list-item-details-grid-column | content-start / trailing-end | Details region column placement. |
--rc-list-item-details-padding-block-end | 0 | Space below the details region. |
--rc-list-item-details-color | inherit | Details region foreground. |
--rc-list-item-details-duration | 0ms | Details reveal duration. |
CSS Parts
| Part | Description |
|---|---|
row | Row surface and subgrid. |
state-layer | Hover, focus, and pressed state layer. |
leading | Leading slot wrapper. |
content | Default content slot wrapper. |
trailing | Trailing slot wrapper. |
divider | Row divider. |
details | Expanded supporting region wrapper. |