Skip to content

List

Displays a continuous group of related items vertically.

  • List View
  • Item List

Overview

The List component displays a continuous, vertical group of related items. It is one of the oldest and most ubiquitous UI patterns — from email inboxes and settings panels to navigation menus and search results — the list is a foundational layout for structured data.

Unlike a Table which organizes data into rigidly defined columns, a list allows each item to have a flexible internal layout: a leading avatar, primary and secondary text lines, trailing metadata, and action controls. This flexibility makes lists ideal for content where items share a type but vary in structure.

When to use a List:

  • To present a scrollable collection of similar items (emails, contacts, files, messages)
  • To display settings or configuration options with descriptions
  • To show navigation links in a sidebar or menu
  • To render search results with heterogeneous content

When NOT to use a List:

  • For tabular data with multiple sortable columns — use a Table
  • For hierarchical nested data — use a Tree View
  • For card-based browsing layouts — use a Card grid
  • For key-value pairs — use a description list (<dl>)

Use the Spacing Tool to establish consistent padding and gap values between list items. Check text contrast with the Contrast Checker to ensure secondary text lines remain readable.

Variants

List Variants

VariantPurposeVisual Treatment
SimpleBasic text-only items.Single line of text per item. Minimal height.
Two-linePrimary text with supporting description.Primary text (bold) + secondary text (muted, smaller).
Three-lineRich content items (e.g., email previews).Primary text, secondary text, and a third metadata line.
InteractiveClickable/selectable items (navigation, selection).Hover background, cursor pointer, focus ring.
GroupedItems organized under section headers.Sticky or static subheaders dividing logical groups.
OrderedSequential items where order matters.Rendered as <ol> with optional visible numbering.

Item Slot Variants

List items are composed of several optional slots:

SlotPositionContentExample
LeadingLeftAvatar, icon, checkbox, thumbnailUser avatar, file type icon
ContentCenterPrimary text, secondary text, metadataName, email, timestamp
TrailingRightAction button, badge, switch, chevronDelete button, unread badge, toggle

Divider Styles

StyleWhen to Use
Full-widthBetween items of equal visual weight
InsetBetween items with leading content (divider aligns with text, not the icon)
NoneTight spacing with clear background alternation or sufficient padding

Use the Spacing Tool to calculate inset divider offsets — typically the leading slot width (40px avatar + 16px gap = 56px inset).

Properties

List Properties

PropertyTypeDefaultDescription
variant'simple' | 'two-line' | 'three-line''simple'Content density per item
divider'full' | 'inset' | 'none''full'Divider style between items
interactivebooleanfalseEnables hover/focus states on items
selectablebooleanfalseEnables single or multi-selection
selectionMode'single' | 'multiple''single'Selection behavior when selectable
densebooleanfalseReduces item padding for compact layouts
disablePaddingbooleanfalseRemoves default list padding
virtualizedbooleanfalseEnables windowed rendering for large datasets
as'ul' | 'ol' | 'nav' | 'div''ul'Root element type

ListItem Properties

PropertyTypeDefaultDescription
leadingReactNode—Left-side content (avatar, icon, checkbox)
trailingReactNode—Right-side content (action, badge, switch)
primaryTextstring—Main text label
secondaryTextstring—Supporting description text
selectedbooleanfalseControlled selection state
disabledbooleanfalsePrevents interaction
hrefstring—Renders item as a link
onClick() => void—Click handler for interactive items

Token Mappings

Design Token Mappings

Lists use a focused set of spacing and surface tokens. See the Design Tokens Guide for full reference.

Token CategoryToken ExampleList Usage
Spacing – Padding--space-3 (12px) / --space-4 (16px)Item vertical/horizontal padding
Spacing – Dense Padding--space-2 (8px)Reduced padding in dense mode
Spacing – Gap--space-3 (12px)Gap between leading slot and content
Spacing – Inset--space-14 (56px)Inset divider left offset (avatar width + gap)
Color – Surface--color-surfaceList background
Color – Hover--color-surface-hoverInteractive item hover state
Color – Selected--color-primary-50Selected item background tint
Color – Primary Text--color-text-primaryItem primary text
Color – Secondary Text--color-text-secondaryItem secondary/description text
Color – Divider--color-border-subtleDivider line color
Border1px solid var(--color-border-subtle)Divider between items
Typography--font-size-sm, --font-size-xsPrimary / secondary text sizes
Transition--duration-fastHover/selection background transitions

Use the Spacing Tool to preview how different padding scales affect list density.

States

List States

StateVisual ChangeNotes
DefaultStandard appearance with dividers and content.—
Hover (interactive)Subtle background tint (surface-hover).Only on interactive or selectable lists.
FocusedFocus ring or background highlight on the focused item.Keyboard navigation focus must be clearly visible (WCAG 2.4.7).
SelectedPrimary-tinted background, optional checkmark in leading slot.Single or multi-select. Communicates selection via aria-selected="true".
DisabledMuted text, no hover/click response.aria-disabled="true" on the item.
EmptyPlaceholder message ("No items to display") or Empty State.Never render an empty <ul> with no feedback.
LoadingSkeleton placeholders mimicking item shape.Show 3–5 skeleton items matching the expected item height.
ReorderableDrag handle visible, item lifts on grab with shadow.Requires ARIA live region announcements for drag position.

Accessibility

Accessibility

Lists are semantically rich and must convey structure, interactivity, and state to assistive technologies.

WCAG 1.3.1 — Info and Relationships: Use proper list semantics. A collection of related items should be a <ul> or <ol>, each item a <li>. Navigation lists should be wrapped in a <nav> landmark. Grouped lists should use headings or aria-labelledby to associate section headers with their groups. Screen readers announce "list, 12 items" — this structural information helps users understand the page layout.

WCAG 4.1.2 — Name, Role, Value: For selectable lists, use role="listbox" on the container and role="option" on each item with aria-selected. For navigation lists, each item should contain an <a> or use role="link". The current page in a navigation list should have aria-current="page".

WCAG 2.1.1 — Keyboard: Interactive lists must be fully keyboard navigable. Two patterns exist:

  • Roving tabindex: One item in the list is tabindex="0", the rest are tabindex="-1". Arrow keys move focus between items. This keeps the list as a single tab stop.
  • Tab per item: Each item is individually tabbable. Simpler but creates many tab stops for long lists.

For selectable lists, Space selects/deselects the focused item. For navigation lists, Enter activates the link.

WCAG 2.4.7 — Focus Visible: Focused list items must display a clearly visible focus indicator. Ensure the focus ring has at least 3:1 contrast against adjacent colors (WCAG 1.4.11). Check with the Contrast Checker.

WCAG 1.4.3 — Contrast: Primary text must achieve 4.5:1 against the list background. Secondary text (often lighter/smaller) must also meet this threshold — a common failure point. Verify secondary text color with the Contrast Checker.

Virtualized lists: When using windowed/virtualized rendering (react-window, tanstack-virtual), ensure items outside the viewport are still announced correctly. Set aria-setsize and aria-posinset on each visible item so screen readers know the total list size and the current item's position.

Reorderable lists: Drag-and-drop reordering must have a keyboard alternative. Typically: select an item, press Space to "grab" it, use Arrow keys to move it, press Space to "drop." Announce position changes via an ARIA live region: "Item 3, moved to position 5 of 12."

Usage Guidelines

Usage Guidelines

Do:

  • Use semantic list elements (<ul>, <ol>, <li>) as the foundation
  • Keep list items consistent — every item should have the same slot layout within a single list
  • Use inset dividers when items have leading content (icons, avatars) to create visual alignment
  • Implement virtualization for lists exceeding ~100 items to maintain scroll performance
  • Provide an empty state message when the list has no items

Don't:

  • Don't mix wildly different item layouts within the same list — this confuses users and screen readers
  • Don't use lists for tabular data that needs column headers and sorting — use a Table
  • Don't remove all visual separators between items unless spacing alone creates clear boundaries
  • Don't auto-focus the first list item on page load unless the list is the primary interaction target
  • Don't use <div> soup where semantic <ul>/<li> would work

Spacing guidelines (use the Spacing Tool):

  • Standard item padding: 12–16px vertical, 16px horizontal
  • Dense item padding: 8px vertical, 16px horizontal
  • Gap between leading slot and content: 12–16px
  • Gap between content and trailing slot: 8–12px
  • Section header padding: 8px vertical, 16px horizontal

Code Snippets

html
<!-- Basic unordered list -->
<ul class="list" role="list">
  <li class="list__item">
    <div class="list__leading">
      <img src="avatar.jpg" alt="" class="avatar avatar--sm" />
    </div>
    <div class="list__content">
      <span class="list__primary">Jane Cooper</span>
      <span class="list__secondary">jane@example.com</span>
    </div>
    <div class="list__trailing">
      <span class="badge badge--subtle badge--green">Active</span>
    </div>
  </li>
  <li class="list__divider list__divider--inset" role="separator"></li>
  <li class="list__item">
    <div class="list__leading">
      <img src="avatar2.jpg" alt="" class="avatar avatar--sm" />
    </div>
    <div class="list__content">
      <span class="list__primary">Alex Johnson</span>
      <span class="list__secondary">alex@example.com</span>
    </div>
    <div class="list__trailing">
      <span class="badge badge--subtle badge--gray">Inactive</span>
    </div>
  </li>
</ul>

<!-- Navigation list -->
<nav aria-label="Settings">
  <ul class="list list--interactive" role="list">
    <li class="list__item" tabindex="0">
      <div class="list__leading">
        <svg aria-hidden="true"><!-- icon --></svg>
      </div>
      <div class="list__content">
        <span class="list__primary">Account</span>
        <span class="list__secondary">Manage your profile and preferences</span>
      </div>
      <div class="list__trailing">
        <svg aria-hidden="true"><!-- chevron --></svg>
      </div>
    </li>
  </ul>
</nav>

<style>
.list {
  list-style: none;
  margin: 0;
  padding: var(--space-2) 0;
}
.list__item {
  display: flex;
  align-items: center;
  padding: var(--space-3) var(--space-4);
  gap: var(--space-3);
}
.list--interactive .list__item {
  cursor: pointer;
  transition: background var(--duration-fast);
}
.list--interactive .list__item:hover {
  background: var(--color-surface-hover);
}
.list__content {
  flex: 1;
  min-width: 0;
}
.list__primary {
  display: block;
  font-size: var(--font-size-sm);
  color: var(--color-text-primary);
}
.list__secondary {
  display: block;
  font-size: var(--font-size-xs);
  color: var(--color-text-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.list__divider--inset {
  border-bottom: 1px solid var(--color-border-subtle);
  margin-left: 56px; /* avatar (32px) + gap (12px) + padding (12px) */
}
</style>
tsx
interface ListItemData {
  id: string;
  primaryText: string;
  secondaryText?: string;
  leading?: React.ReactNode;
  trailing?: React.ReactNode;
  disabled?: boolean;
  href?: string;
}

interface ListProps {
  items: ListItemData[];
  variant?: 'simple' | 'two-line' | 'three-line';
  divider?: 'full' | 'inset' | 'none';
  interactive?: boolean;
  dense?: boolean;
  onItemClick?: (id: string) => void;
  as?: 'ul' | 'ol' | 'nav';
}

function List({
  items,
  variant = 'simple',
  divider = 'full',
  interactive = false,
  dense = false,
  onItemClick,
  as: Component = 'ul',
}: ListProps) {
  const listClass = [
    'list',
    interactive && 'list--interactive',
    dense && 'list--dense',
  ].filter(Boolean).join(' ');

  const content = items.map((item, index) => (
    <React.Fragment key={item.id}>
      <li
        className={`list__item${item.disabled ? ' list__item--disabled' : ''}`}
        onClick={interactive && !item.disabled ? () => onItemClick?.(item.id) : undefined}
        tabIndex={interactive && !item.disabled ? 0 : undefined}
        aria-disabled={item.disabled || undefined}
        role={interactive ? 'button' : undefined}
        onKeyDown={interactive ? (e) => {
          if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault();
            onItemClick?.(item.id);
          }
        } : undefined}
      >
        {item.leading && <div className="list__leading">{item.leading}</div>}
        <div className="list__content">
          <span className="list__primary">{item.primaryText}</span>
          {item.secondaryText && (
            <span className="list__secondary">{item.secondaryText}</span>
          )}
        </div>
        {item.trailing && <div className="list__trailing">{item.trailing}</div>}
      </li>
      {divider !== 'none' && index < items.length - 1 && (
        <li
          className={`list__divider list__divider--${divider}`}
          role="separator"
        />
      )}
    </React.Fragment>
  ));

  if (Component === 'nav') {
    return (
      <nav aria-label="List navigation">
        <ul className={listClass} role="list">{content}</ul>
      </nav>
    );
  }

  return <Component className={listClass} role="list">{content}</Component>;
}

Design Systems

Design System Implementations

Material Design 3 provides List, ListItem, ListItemButton, ListItemText, ListItemAvatar, ListItemIcon, ListItemSecondaryAction, ListSubheader, and Divider. MUI's composition model is rich — ListItemButton handles hover/focus/ripple for interactive items, while ListItemText accepts primary and secondary props for two-line rendering. The inset prop on Divider and ListItemText aligns content when some items lack icons. Material's list items use a 48px minimum height (56px with avatars) and follow the 8px spacing grid. Nested lists are achieved by placing a List inside a collapsible Collapse component within a ListItem.

Ant Design provides List with dataSource, renderItem, grid (for responsive grid layout), pagination, and loading (shows skeleton or spinner). Ant's List.Item supports actions (trailing action buttons) and List.Item.Meta for avatar + title + description layout. Ant's approach is data-driven — pass an array and a render function — making it ideal for API-fetched content. It includes built-in pagination and loading states.

Chakra UI does not provide a dedicated List component beyond HTML <List>, <ListItem>, <ListIcon>, and <OrderedList> / <UnorderedList>. These are thin wrappers around semantic HTML with Chakra's style props. For interactive/selectable lists, developers compose with Box, Flex, and Stack.

Radix UI does not provide a List primitive. Lists are considered an application-level composition of semantic HTML. For selectable lists, Radix's Select or Listbox pattern is the recommended approach.

Headless UI provides Listbox for selectable list behavior (keyboard navigation, ARIA roles) without any visual styling — the ideal starting point for custom selectable lists.

shadcn/ui does not ship a List component, but its documentation demonstrates list patterns using Tailwind utilities over semantic HTML. For command-palette style lists, shadcn's Command component (built on cmdk) is the go-to.

For consistent list spacing that aligns to your design system grid, use the Spacing Tool.

Data DisplayList ViewItem List