Skip to main content

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 install @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-expanded and aria-controls onto 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 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 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​

PropertyMarkupTypeDefaultDescription
variantvariantRCListVariant'standard'Appearance hint.
selectionselectionRCListSelection'none'Native selection coordination mode.
orderedorderedbooleanfalseWhether item order is meaningful; positions are exposed without rendered counters.
reorderablereorderablebooleanfalseWhether people can reorder items with a handle. Implies `ordered`.
reorderMessagesJS property onlyRCListReorderMessages | undefinedNot specifiedOverrides for the reorder announcements and the handle hint.
roleJS property onlystring'list'No description provided.

Events​

EventDetail typeDescription
rc-list-reorderNo detail type documentedFires, 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​

NameDescription
(default)Direct `rc-list-item` children.

CSS Custom Properties​

PropertyDefaultDescription
--rc-list-padding-inline0Shared inline content padding.
--rc-list-padding-block0List block padding.
--rc-list-leading-sizeminmax(0, max-content)Shared leading column size when any visible item has leading content.
--rc-list-leading-gap1remGap after the leading column.
--rc-list-trailing-sizeminmax(0, max-content)Shared trailing column size when any visible item has trailing content.
--rc-list-trailing-gap1remGap before the trailing column.
--rc-list-row-gap0Gap 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​

PartDescription
listShared list grid.

Properties​

PropertyMarkupTypeDefaultDescription
selectedselectedbooleanfalsePresentational selected state. Host writes are silent.
disableddisabledbooleanfalsePresentational disabled state. Host writes are silent.
interactiveinteractivebooleanfalseEnables row interaction styling.
actionTargetaction-targetstring''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).
defaultExpandeddefault-expandedbooleanfalseInitial expanded state of the `details` region for uncontrolled usage.
expandedexpandedbooleanNot specifiedCurrent expanded state of the `details` region. Host writes are silent and take control; assign `undefined` to release control.
roleJS property onlystring'listitem'No description provided.

Events​

EventDetail typeDescription
rc-list-item-toggleNo detail type documentedFires when the toggle button requests a new expanded state.

Slots​

NameDescription
leadingOptional 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.
trailingOptional trailing metadata or action.
detailsOptional 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​

PropertyDefaultDescription
--rc-list-item-min-block-size3remMinimum rendered row height.
--rc-list-item-padding-block0.5remRow block padding.
--rc-list-item-colorCanvasTextRow foreground color.
--rc-list-item-backgroundtransparentRow surface color.
--rc-list-item-selected-colorNot specifiedSelected foreground color.
--rc-list-item-selected-backgroundtransparentSelected surface color.
--rc-list-item-border0Row border shorthand.
--rc-list-item-border-radius0Row corner radius.
--rc-list-item-box-shadownoneRow shadow.
--rc-list-item-divider0Divider border shorthand.
--rc-list-item-divider-inset-inline0Divider inline inset.
--rc-list-item-grid-template-rowsautoInternal row track definition.
--rc-list-item-row-gap0Gap between internal row tracks.
--rc-list-item-leading-grid-columnleading-start / leading-endLeading region column placement.
--rc-list-item-leading-grid-rowautoLeading region row placement.
--rc-list-item-content-grid-columncontent-start / content-endContent region column placement.
--rc-list-item-content-grid-rowautoContent region row placement.
--rc-list-item-content-white-spacenowrapContent wrapping behavior.
--rc-list-item-content-text-overflowellipsisContent overflow marker.
--rc-list-item-trailing-grid-columntrailing-start / trailing-endTrailing region column placement.
--rc-list-item-trailing-grid-rowautoTrailing region row placement.
--rc-list-item-trailing-justify-selfendTrailing region alignment within its grid area.
--rc-list-item-state-layer-colorcurrentColorInteraction state layer color.
--rc-list-item-state-layer-opacity0Resting interaction state layer opacity.
--rc-list-item-hover-state-layer-opacity0.08Hover state opacity.
--rc-list-item-focus-state-layer-opacity0.1Focus state opacity.
--rc-list-item-pressed-state-layer-opacity0.1Pressed state opacity.
--rc-list-item-disabled-opacity0.38Disabled row opacity.
--rc-list-item-dragged-backgroundCanvasSurface 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-shadownoneShadow of a lifted item. Falls back to `--rc-list-item-box-shadow`.
--rc-list-item-dragged-state-layer-opacity0.16Lifted item state opacity.
--rc-list-item-reorder-duration0msDuration of the shift that makes room for a lifted item. Read from the item, applied from light-DOM base styles.
--rc-list-item-reorder-easingeaseEasing of that shift.
--rc-list-item-transition-duration0msRow state transition duration.
--rc-list-item-transition-easingeaseRow state transition easing.
--rc-list-item-details-grid-columncontent-start / trailing-endDetails region column placement.
--rc-list-item-details-padding-block-end0Space below the details region.
--rc-list-item-details-colorinheritDetails region foreground.
--rc-list-item-details-duration0msDetails reveal duration.

CSS Parts​

PartDescription
rowRow surface and subgrid.
state-layerHover, focus, and pressed state layer.
leadingLeading slot wrapper.
contentDefault content slot wrapper.
trailingTrailing slot wrapper.
dividerRow divider.
detailsExpanded supporting region wrapper.