Lists
Lists are continuous, vertical indexes of text or images. Expressive Lists feature shape morphing animations, multi-action support, and built-in drag-and-drop sorting.
Lists are used to group related content or actions into a single column. The MD3 Expressive List features dynamic corner rounding (shape morphing) based on interaction states and native drag-and-drop capabilities.
Introduction
Expressive Lists are designed to feel tactile and fluid. In the expressive variant, items transition their corner shapes from standard roundness (CornerExtraSmall - 4px) to dynamic bubbles (CornerMedium/CornerLarge - 12px/16px) upon hover, focus, and drag interaction.
Anatomy
- Container: The list wrapper (either standard or segmented with gap).
- Leading Slot: Supports icons, avatars, images, video previews, or selection indicators (checkboxes/radio buttons).
- Content Area: Contains an optional overline, main headline, and supporting text (1 or 2 lines).
- Trailing Slot: Contains action elements like text, switches, buttons, or a drag handle.
- Divider: A separating line that can be full-width or inset starting after leading elements.
Variants
Basic Lists
Baseline and Expressive variants showing standard or segmented styles.
Selection Modes
Single-select (radio buttons) or multi-select (checkboxes) lists. Clicking anywhere on the item or the indicator toggles selection state.
Drag & Drop Reordering
Integrated sorting powered by @dnd-kit. Activating reorderable enables list items to be dragged and reordered with fluid spring animations.
Rich Media & Multi-Action
List items can hold complex elements like video previews (top-aligned) and support Multi-Action mode where the container and the trailing action have separate focus areas.
Features
Expressive Motion
Expressive Lists utilize physics-based spring animations for fluid state transitions. When a user hovers, focuses, or drags a list item, its corners dynamically expand from 4px (extra small shape) to 12px or 16px (medium/large shape). This shape morphing is governed by:
- Spatial Spring: Manages the corner radius shape morphing and size changes (
FAST_SPATIAL_SPRINGtier) for instant tactile feedback. - Effects Spring: Controls color background transitions and state overlay opacities (
DEFAULT_EFFECTS_SPRINGtier).
Responsive Scaling & Adaptation
List layouts adapt dynamically across screen size classes to preserve comfortable reading line lengths:
- Compact Window Sizes (under 600dp): List containers extend edge-to-edge. Selecting an item navigates to a full-screen detailed view.
- Medium & Expanded Window Sizes (600dp - 1200dp+): Lists can adapt margins, scale down padding, or transition into a side-by-side list-detail view or a multi-column grid.
- Line Length: Primary and supporting text is optimized for quick scanning, keeping the line length between 40 to 60 characters for maximum readability.
Usage
Basic Usage
import { List, ListItem, ListDivider } from "@bug-on/m3-expressive";
<List variant="expressive">
<ListItem
value="item1"
headline="Inbox"
supportingText="Check your emails"
leadingType="icon"
leadingContent={<span className="material-symbols-rounded">inbox</span>}
interactive
/>
<ListDivider inset insetType="icon" />
<ListItem
value="item2"
headline="Trash"
supportingText="Deleted items"
leadingType="icon"
leadingContent={<span className="material-symbols-rounded">delete</span>}
interactive
/>
</List>
Reorderable List
import { List, ListItem } from "@bug-on/m3-expressive";
import { useState } from "react";
const [items, setItems] = useState([
{ id: "1", title: "Item 1" },
{ id: "2", title: "Item 2" },
]);
<List
variant="expressive"
listStyle="segmented"
reorderable
items={items}
onReorder={setItems}
>
{items.map((item) => (
<ListItem
key={item.id}
value={item.id}
headline={item.title}
dragHandle
/>
))}
</List>
Best Practices
Do
- Align supporting visuals (such as icons, avatars, or images) consistently at the leading edge to maintain scannability.
- Use segmented gaps (
listStyle="segmented") and filled container items to clearly define contained lists. - Keep label text brief and limit supporting description text to 1 to 3 lines, allowing it to truncate based on screen size.
- Provide descriptive
aria-labels for interactive elements like custom trailing actions or drag handles. - Adjust container margins on large screens (e.g. tablet or desktop) to prevent overly long line lengths and maintain comfortable reading.
Don't
- Don't vary the placement of visual elements (e.g. shifting icons from leading to middle) within the same list.
- Don't use a high-emphasis design for repetitive secondary actions in multi-action lists.
- Don't pair checkboxes with single-select lists, or radio buttons with multi-select lists.
- Don't rely solely on background color changes to indicate selection states. Always pair color shifts with supporting visual cues (e.g. checkboxes, radio buttons, or selection checkmarks).
Design Tokens
Corner Radius
The Expressive List component implements MD3 Expressive shape morphing across state layers.
| State | CSS Token | Value | Corner Level |
|---|---|---|---|
| Resting | --md-sys-shape-corner-extra-small | 4px | Extra Small |
| Hovered | --md-sys-shape-corner-medium | 12px | Medium |
| Focused / Pressed | --md-sys-shape-corner-large | 16px | Large |
| Selected / Dragged | --md-sys-shape-corner-large | 16px | Large |
| Disabled | --md-sys-shape-corner-extra-small | 4px | Extra Small |
Container Heights
Heights align strictly with the MD3 vertical spacing specs:
| Complexity | Layout / Configuration | Target Height |
|---|---|---|
| One-line | Label text only, with/without leading icons | 56px |
| Two-line | Label + 1 line of supporting text | 72px |
| Three-line | Label + 2 lines of supporting text / overline | 88px |
Motion & Springs
Transitions use organic spring physics to communicate state shifts.
| Transition | Spring Token | Tier |
|---|---|---|
| Shape/Size (Morphing) | FAST_SPATIAL_SPRING | Fast |
| Color/Opacity (State overlays) | DEFAULT_EFFECTS_SPRING | Default |
Accessibility
- Keyboard Navigation (Vertical): Press
ArrowUp/ArrowDownto cycle focus through interactive items. Focus automatically wraps around. - Keyboard Navigation (Horizontal): In
multi-actionmode, pressArrowLeft/ArrowRightto transition focus between the primary item body and secondary trailing actions. Focus automatically skips disabled controls. - Initial Focus: When tabbed, focus lands on the first item. If the list has an active selected item, focus lands on the selected item first.
- ARIA Roles: Automatically assigns
role="listbox"andaria-selectedfor selection lists, androle="list"for actions. - Visual Cues: Selection is indicated by both a color background change and a checkbox/radio control to support users with low vision (WCAG 2.1 compliance).
- Disabled State: Correctly assigns
aria-disabled="true"and removes focus viatabIndex={-1}.
API Reference
List
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "baseline" | "expressive" | "baseline" | Design scheme variant. |
listStyle | "standard" | "segmented" | "standard" | Segmented list has 2px gap. |
selectionMode | "none" | "single-action" | "multi-action" | "single-select" | "multi-select" | "none" | Changes interaction roles and keyboard routing. |
value | string | string[] | — | Controlled selection value. |
defaultValue | string | string[] | — | Uncontrolled selection value initial state. |
onChange | (value: string | string[]) => void | — | Fired when selection state changes. |
reorderable | boolean | false | Enables drag-and-drop. |
items | unknown[] | — | Array of items data to reorder. |
onReorder | (newItems: unknown[]) => void | — | Callback returned sorted array. |
outerRadius | number | 16 | Custom outer corner radius (px) for leading/trailing list items in expressive variant. |
innerRadius | number | 4 | Custom inner corner radius (px) for contiguous list items in expressive variant. |
ListItem
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | Required | Unique identifier for selection and sorting. |
headline | ReactNode | Required | Principal list item text. |
supportingText | ReactNode | — | Supplementary description. |
supportingTextLines | 1 | 2 | 1 | Max lines for supporting description. |
overline | ReactNode | — | Small text appearing above headline. |
leadingType | "none" | "icon" | "avatar" | "image" | "video" | "checkbox" | "radio" | "custom" | "none" | Leading layout configuration. |
leadingSrc | string | — | Image or video source link. |
leadingAlt | string | — | Alternative label for leading image. |
leadingContent | ReactNode | — | Content used for icon/avatar or custom. |
trailingType | "none" | "icon" | "icon-button" | "text" | "checkbox" | "radio" | "switch" | "custom" | "none" | Trailing layout configuration. |
trailingText | string | — | Trailing status text. |
trailingContent | ReactNode | — | Custom element or switch overrides. |
disabled | boolean | false | Disables interaction and reduces opacity. |
selected | boolean | — | Manual selected state override. |
href | string | — | Renders item as a link <a>. |
onClick | (event: React.MouseEvent<HTMLElement>) => void | — | Event callback. |
interactive | boolean | false | Forces interactive state (ripples & hover overlays) when no event or link is provided. |
dragHandle | boolean | false | Restricts drag activation to handle button instead of full container. |
position | "solo" | "leading" | "middle" | "trailing" | — | Overrides the list item position for corner radius calculations. |
ListDivider
| Prop | Type | Default | Description |
|---|---|---|---|
inset | boolean | false | Indents divider line after leading slot elements. |
insetType | "icon" | "avatar" | "custom" | "avatar" | Sets indentation amount (56px for icon, 72px for avatar). |