Skip to main content
ListCell
@coinbase/cds-web@8.21.3
A versatile cell component used for displaying content in a list format, supporting various layouts and interactions.
Import
import { ListCell } from '@coinbase/cds-web/cells/ListCell'
SourceView source codeStorybookView StorybookFigmaView Figma (internal only)
Related components
View as Markdown

Overview

A ListCell row is divided into the following 5 columns:

  • Media
  • Title & description
  • Intermediary
  • End (detail & subdetail or action)
  • Accessory

Basic Usage

Loading...
Live Code
<ListCell
  spacingVariant="condensed"
  title="Basic List Cell"
  description="A simple example of ListCell"
/>
Tip

Prefer spacingVariant="condensed" for the new ListCell design. The compact may be removed in a future major release.

Spacing Variant

Loading...
Live Code
<VStack>
  {/* Preferred (new design) */}
  <ListCell
    accessory="arrow"
    description="New design (condensed)"
    detail="$12,345.00"
    spacingVariant="condensed"
    media={<Avatar src={assets.eth.imageUrl} size="m" />}
    onClick={console.log}
    title="Condensed"
    variant="positive"
  />

  {/* Deprecated options kept for backward compatibility */}
  <ListCell
    accessory="arrow"
    detail="$12,345.00"
    spacingVariant="compact"
    media={<Avatar src={assets.eth.imageUrl} size="m" />}
    onClick={console.log}
    title="Compact (deprecated)"
    variant="positive"
  />
  <ListCell
    accessory="arrow"
    detail="$12,345.00"
    spacingVariant="normal"
    media={<Avatar src={assets.eth.imageUrl} size="m" />}
    onClick={console.log}
    title="Normal"
    variant="positive"
  />
</VStack>

Media

Note

We have deprecated CellMedia; pass media directly as shown below.

Leading Icon

Loading...
Live Code
<ListCell
  spacingVariant="condensed"
  title="List Cell with Icon"
  description="Shows usage with a leading icon"
  media={<Icon active name="info" />}
/>

Leading Avatar

Loading...
Live Code
<ListCell
  spacingVariant="condensed"
  title="List Cell with Icon"
  description="Shows usage with a leading icon"
  media={<Avatar src={assets.btc.imageUrl} size="l" />}
/>

Title & Description

Title Line Limits

  • In condensed spacing (spacingVariant="condensed"), the title shows up to two lines by default, regardless of whether a description is present.
  • In normal and compact spacing, the title shows up to two lines when there is no description; if a description is present, the title is limited to one line.
  • Use disableMultilineTitle to force the title to one line in all cases.
Warning

The title and description props are rendered inside a CDS Text with default fonts and truncation. To render arbitrary React nodes without being wrapped by a <Text>, use titleNode and descriptionNode. When using the Node props, you are responsible for styling, layout, and truncation behavior.

Custom Title/Description via Node Props

Loading...
Live Code
<ListCell
  spacingVariant="condensed"
  media={<Avatar src={assets.eth.imageUrl} size="m" />}
  titleNode={
    <HStack gap={1} alignItems="center">
      <Icon name="checkmark" />
      <span>Verified account</span>
    </HStack>
  }
  descriptionNode={
    <HStack gap={1} alignItems="center">
      <span>Composed description with any React nodes</span>
      <Icon name="info" />
    </HStack>
  }
/>

Multiline Description

Loading...
Live Code
<ListCell
  spacingVariant="condensed"
  title="Multiline Description"
  description="This is a longer description that demonstrates how the text wraps when the multiline prop is enabled. It can span multiple lines without truncating."
  multiline
/>

Intermediary

Loading...
Live Code
function Intermediary() {
  const dimensions = { width: 62, height: 18 };
  const sparklineData = prices
    .map((price) => parseFloat(price))
    .filter((price, index) => index % 10 === 0);
  const referenceY = sparklineData[Math.floor(sparklineData.length / 3)];

  const CompactChart = memo(
    ({ data, color = 'var(--color-fgPositive)', showArea = false, referenceY }) => (
      <Box style={{ padding: 1 }}>
        <LineChart
          {...dimensions}
          enableScrubbing={false}
          overflow="visible"
          inset={0}
          showArea={showArea}
          series={[
            {
              id: 'series',
              data,
              color,
            },
          ]}
        >
          <ReferenceLine dataY={referenceY} />
        </LineChart>
      </Box>
    ),
  );

  return (
    <ListCell
      media={<Avatar src={assets.btc.imageUrl} />}
      spacingVariant="condensed"
      title="Bitcoin"
      description="BTC"
      intermediary={<CompactChart data={sparklineData} referenceY={referenceY} />}
      detail="$334,239.03"
      subdetail="+4.06%"
      priority="start"
      variant="positive"
    />
  );
}

End

Detail and Subdetail

Loading...
Live Code
<ListCell
  spacingVariant="condensed"
  title="List Cell with Details"
  description="Shows usage with detail and subdetail"
  detail="Primary detail"
  subdetail="Secondary detail"
/>
Warning

Like title and description, detail and subdetail props are also rendered inside a CDS Text with default fonts. To render arbitrary React nodes without being wrapped by a <Text>, use detailNode and subdetailNode.

Custom Detail/Subdetail via Node Props

Loading...
Live Code
<ListCell
  spacingVariant="condensed"
  media={<Avatar src={assets.btc.imageUrl} size="m" />}
  title="Custom end content"
  description="Detail and subdetail rendered with custom nodes"
  detailNode={
    <HStack gap={2} alignItems="center" justifyContent="flex-end">
      <Icon name="info" />
      <Text as="div" font="body" overflow="truncate" textAlign="end">
        $12,345.00
      </Text>
    </HStack>
  }
  subdetailNode={
    <HStack gap={1} alignItems="center" justifyContent="flex-end">
      <Icon name="info" />
      <Text as="div" color="fgPositive" font="label2" overflow="truncate" textAlign="end">
        +5.43%
      </Text>
    </HStack>
  }
/>

End Action

When you pass the end prop, it overrides the detail/subdetail/detailNode/subdetailNode.

Loading...
Live Code
<ListCell
  spacingVariant="condensed"
  title="End action"
  detail="This is overridden and won't show up"
  subdetail="This is overridden and won't show up"
  detailNode="This is overridden and won't show up"
  subdetailNode="This is overridden and won't show up"
  end={
    <Button
      compact
      onClick={() => {
        alert('Action clicked');
      }}
    >
      Action
    </Button>
  }
/>

Accessory

Interactive Cell with Accessory

Loading...
Live Code
<ListCell
  spacingVariant="condensed"
  title="Interactive List Cell"
  description="Click or tap to interact"
  accessory="arrow"
  onClick={() => alert('Cell clicked!')}
/>

Custom Accessory via Node Prop

Loading...
Live Code
<ListCell
  spacingVariant="condensed"
  title="Accessory Node"
  description="Custom accessory with its own onClick"
  media={<Avatar src={assets.eth.imageUrl} size="m" />}
  end={
    <Button
      compact
      onClick={() => {
        alert('Action clicked');
      }}
    >
      Action
    </Button>
  }
  accessoryNode={
    <Tooltip content="question">
      <Icon size="s" name="questionMark" compact variant="secondary" />
    </Tooltip>
  }
/>

Accessibility Label

The accessibility props are only applied when the <ListCell> has a value for the onClick prop. Otherwise, content passed into the <ListCell> must use accessibility props and attributes as needed.

Loading...
Live Code
<VStack gap={1}>
  <ListCell
    accessibilityLabel="Accessibility label. Describes content for entire list cell. Applied when onClick prop has a value"
    intermediary={<Icon name="chartLine" />}
    media={<Avatar src={assets.btc.imageUrl} />}
    onClick={() => window.alert('ListCell clicked!')}
    title="BTC"
    spacingVariant="condensed"
  />

  <ListCell
    intermediary={<Icon accessibilityLabel="Chart icon" name="chartLine" />}
    media={<Avatar accessibilityLabel="Bitcoin" src={assets.btc.imageUrl} />}
    title="BTC"
    spacingVariant="condensed"
  />
</VStack>

Helper text

Loading...
Live Code
<VStack gap={3}>
  <ListCell
    spacingVariant="condensed"
    title="List Cell with Helper Text"
    description="Shows usage with helper text below the cell"
    helperText={
      <CellHelperText font="label2" paddingStart={6}>
        This is a default helper message.
      </CellHelperText>
    }
    media={<Avatar src={assets.btc.imageUrl} />}
    end={<Button compact>Action</Button>}
  />
  <ListCell
    spacingVariant="condensed"
    title="List Cell with Warning"
    description="Shows usage with a warning message"
    helperText={
      <CellHelperText font="label2" variant="warning" paddingStart={6}>
        This is a warning message.
      </CellHelperText>
    }
    media={<Avatar src={assets.btc.imageUrl} />}
    end={<Button compact>Action</Button>}
  />
  <ListCell
    spacingVariant="condensed"
    title="List Cell with Error"
    description="Shows usage with an error message"
    helperText={
      <CellHelperText font="label2" variant="error" paddingStart={6}>
        This is an error message.
      </CellHelperText>
    }
    media={<Avatar src={assets.btc.imageUrl} />}
    end={<Button compact>Action</Button>}
  />
</VStack>

Loading States

The ListCellFallback component provides loading state representations of ListCell. It uses placeholder rectangles to indicate where content will appear, creating a smooth loading experience. The web version uses percentage-based widths and custom layouts to match the ListCell's four-column structure.

Loading...
Live Code
<VStack gap={3}>
  {/* Basic loading state */}
  <ListCellFallback title description spacingVariant="condensed" />

  {/* Loading state with media */}
  <ListCellFallback title description media="icon" spacingVariant="condensed" />

  {/* Loading state with details */}
  <ListCellFallback title description detail subdetail spacingVariant="condensed" />

  {/* Full loading state with custom widths */}
  <ListCellFallback
    spacingVariant="condensed"
    title
    description
    detail
    subdetail
    helperText
    media="icon"
    rectWidthVariant={2} // Creates a deterministic variant of the loading state
    disableRandomRectWidth
    styles={{ helperText: { paddingLeft: 48 } }}
  />
</VStack>

Priority

The priority prop controls which parts of the cell are protected from shrinking and truncation when horizontal space is limited. It accepts start, middle, and end as a string or an array of strings.

Loading...
Live Code
function PriorityContent() {
  const dimensions = { width: 62, height: 18 };
  const sparklineData = prices
    .map((price) => parseFloat(price))
    .filter((price, index) => index % 10 === 0);
  const referenceY = sparklineData[Math.floor(sparklineData.length / 3)];

  const CompactChart = memo(
    ({ data, color = 'var(--color-fgPositive)', showArea = false, referenceY }) => (
      <Box style={{ padding: 1 }}>
        <LineChart
          {...dimensions}
          enableScrubbing={false}
          overflow="visible"
          inset={0}
          showArea={showArea}
          series={[
            {
              id: 'series',
              data,
              color,
            },
          ]}
        >
          <ReferenceLine dataY={referenceY} />
        </LineChart>
      </Box>
    ),
  );

  return (
    <VStack gap={3} style={{ width: '100%', maxWidth: 320, overflow: 'hidden' }}>
      <ListCell
        spacingVariant="condensed"
        title="Asset with a really long name"
        description="Some description of the asset"
        intermediary={<CompactChart data={sparklineData} referenceY={referenceY} />}
        detail="$334,239.03"
        subdetail="+4.06%"
        priority="start"
        variant="positive"
      />
      <ListCell
        spacingVariant="condensed"
        title="Asset with a really long name"
        description="Some description of the asset"
        intermediary={<CompactChart data={sparklineData} referenceY={referenceY} />}
        detail="$334,239.03"
        subdetail="+4.06%"
        priority="middle"
        variant="positive"
      />
      <ListCell
        spacingVariant="condensed"
        title="Asset with a really long name"
        description="Some description of the asset"
        intermediary={<CompactChart data={sparklineData} referenceY={referenceY} />}
        detail="$334,239.03"
        subdetail="+4.06%"
        priority="end"
        variant="positive"
      />
      <ListCell
        spacingVariant="condensed"
        title="Asset with a really long name"
        description="Some description of the asset"
        intermediary={<CompactChart data={sparklineData} referenceY={referenceY} />}
        detail="$334,239.03"
        subdetail="+4.06%"
        priority={['start', 'middle', 'end']}
        variant="warning"
      />
    </VStack>
  );
}

Anatomy

Without helper text (top-only layout):

┌───────────────────────────────────────────────────────────────────────────┐
│ root (Box) │
│┌─────────────────────────────────────────────────────────────────────────┐│
││ pressable ││
││┌───────────────────────────────────────────────────────────────────────┐││
│││ contentContainer & mainContent (HStack) │││
│││ ┌─────┐ ┌────────────────┐ ┌────────────┐ ┌────────────┐ ┌─────────┐ │││
│││ │media│ │ VStack │ │intermediary│ │ end │ │accessory│ │││
│││ │ │ │ ┌──────────┐ │ │ │ │ (detail │ │ │ │││
│││ │ │ │ │ title │ │ │ │ │ or │ │ │ │││
│││ │ │ │ └──────────┘ │ │ │ │ action) │ │ │ │││
│││ │ │ │ ┌────────────┐ │ │ │ │ │ │ │ │││
│││ │ │ │ │ description│ │ │ │ │ │ │ │ │││
│││ │ │ │ └────────────┘ │ │ │ │ │ │ │ │││
│││ └─────┘ └────────────────┘ └────────────┘ └────────────┘ └─────────┘ │││
││└───────────────────────────────────────────────────────────────────────┘││
│└─────────────────────────────────────────────────────────────────────────┘│
└───────────────────────────────────────────────────────────────────────────┘

With helper text (top + bottom layout):

┌─────────────────────────────────────────────────────────────────────────────┐
│ root (Box) │
│┌───────────────────────────────────────────────────────────────────────────┐│
││ pressable ││
││┌─────────────────────────────────────────────────────────────────────────┐││
│││ contentContainer (VStack) │││
│││┌───────────────────────────────────────────────────────────────────────┐│││
││││ mainContent (HStack) ││││
││││ ┌─────┐ ┌────────────────┐ ┌────────────┐ ┌────────────┐ ┌─────────┐ ││││
││││ │media│ │ VStack │ │intermediary│ │ end │ │accessory│ ││││
││││ │ │ │ ┌──────────┐ │ │ │ │ (detail │ │ │ ││││
││││ │ │ │ │ title │ │ │ │ │ or │ │ │ ││││
││││ │ │ │ └──────────┘ │ │ │ │ action) │ │ │ ││││
││││ │ │ │ ┌────────────┐ │ │ │ │ │ │ │ ││││
││││ │ │ │ │ description│ │ │ │ │ │ │ │ ││││
││││ │ │ │ └────────────┘ │ │ │ │ │ │ │ ││││
││││ └─────┘ └────────────────┘ └────────────┘ └────────────┘ └─────────┘ ││││
│││└───────────────────────────────────────────────────────────────────────┘│││
│││┌───────────────────────────────────────────────────────────────────────┐│││
││││ helperText ││││
│││└───────────────────────────────────────────────────────────────────────┘│││
││└─────────────────────────────────────────────────────────────────────────┘││
│└───────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘

Mapping to styles / classNames keys:

  • root: outer Box wrapping the entire cell
  • pressable: interactive overlay when href / onClick keyboard handlers are present
  • contentContainer: container around top and optional bottom content
  • mainContent: inner horizontal layout that holds the main pieces
  • title/description: stacked text column within topContent
  • media: leading media container
  • intermediary: middle container between main and end
  • end: container for detail / subdetail or end prop you pass in
  • accessory: trailing accessory container
  • helperText: container below main content to display helper text

Is this page useful?

Coinbase Design is an open-source, adaptable system of guidelines, components, and tools that aid the best practices of user interface design for crypto products.