---
title: ListView
description: A CSS Grid-based horizontal layout for displaying items with start, intermediary, and end slots.
icon: list
author:
  name: eblairmckee
  url: https://github.com/eblairmckee
mode: wide
---

### Installation

```package-install
npx shadcn@latest add @redpanda/list-view
```

<ComponentPreview name="list-view-demo" />

## Usage

ListView is a presentational three-column grid. Put a native action, such as a Button, Link, Switch, or menu trigger, in `ListViewEnd`. The row itself is not clickable or focusable.

```tsx
import {
  ListView,
  ListViewGroup,
  ListViewStart,
  ListViewIntermediary,
  ListViewEnd,
} from '@/components/redpanda-ui/list-view';
```

## Anatomy

The ListView component is built with a three-column grid system:

```
ListView Structure:
┌─────────────────────────────────────────────────────────────────────────┐
│ <ListViewGroup> (Container for multiple ListView items)                 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ <ListView> (Single row with 3-column grid)                          │ │
│ │ ┌──────────────────┬─────────────────────┬────────────────────────┐ │ │
│ │ │ <ListViewStart>  │ <ListViewIntermed>  │ <ListViewEnd>          │ │ │
│ │ │ • Title          │ • Badges            │ • Single action        │ │ │
│ │ │ • Description    │ • Tags              │ • Switch/Button        │ │ │
│ │ │ • Metadata       │ • Status indicators │ • Right-aligned        │ │ │
│ │ │                  │ • Hidden on mobile  │                        │ │ │
│ │ └──────────────────┴─────────────────────┴────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ <ListView> (Next row - border-top auto-applied in grouped)          │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘

Grid Column Sizes:
• Col 1 (Start): minmax(40%, 2fr) - Primary content, shrinks gracefully
• Col 2 (Intermediary): 2fr - Secondary content, hidden below lg breakpoint
• Col 3 (End): 1fr - Actions, right-aligned, always visible
```

## Slot Guidelines

### ListViewStart (Column 1)

**Purpose:** Primary content - the main information users need to identify the item.

**Best practices:**

- Use `title` prop for the item name (auto-wrapped in a semantic `h3`)
- Use `description` prop for secondary info (styled with `text-body text-subtle`)
- Keep titles concise - they truncate on overflow
- Use custom children for complex layouts (icons + text)

```tsx
// Simple: title and description props
<ListViewStart
  title="Claude 3.5 Sonnet"
  description="Anthropic's most intelligent model"
/>

// Complex: custom children
<ListViewStart>
  <div className="flex items-center gap-2">
    <AnthropicIcon className="size-4" />
    <h4 className="text-heading-sm">Claude 3.5 Sonnet</h4>
  </div>
  <p className="text-body text-subtle">claude-3-5-sonnet-20241022</p>
</ListViewStart>
```

### ListViewIntermediary (Column 2)

**Purpose:** Secondary metadata - badges, tags, status indicators that provide additional context.

**Key behavior:**

- Hidden below the `lg` breakpoint (1024px)
- Left-aligned within its column
- Use for non-critical information that enhances the view

**Best practices:**

- Use BadgeGroup for multiple badges with overflow handling
- Keep content minimal - this column disappears on smaller screens
- Don't put critical actions here

```tsx
<ListViewIntermediary gap="md">
  <Badge tone="success" variant="subtle">
    Active
  </Badge>
  <BadgeGroup
    maxVisible={2}
    items={[
      {
        id: 'React',
        textValue: 'React',
        content: <Badge size="sm">React</Badge>,
      },
      {
        id: 'TypeScript',
        textValue: 'TypeScript',
        content: <Badge size="sm">TypeScript</Badge>,
      },
      {
        id: 'Tailwind',
        textValue: 'Tailwind',
        content: <Badge size="sm">Tailwind</Badge>,
      },
    ]}
  />
</ListViewIntermediary>
```

### ListViewEnd (Column 3)

**Purpose:** Single action - the primary interactive element for the item.

**Common action patterns:**

- **Switch** - Toggle enable/disable state
- **Link** - Navigate to a detail view
- **Button** - Select an item or open a panel
- **Icon button (menu)** - Open dropdown with multiple actions

```tsx
// Toggle action
<ListViewEnd>
  <Switch aria-label="Enable model" checked={enabled} onCheckedChange={setEnabled} />
</ListViewEnd>

// Selection action: the pressed state belongs to the native button
<ListViewEnd>
  <Button
    aria-pressed={selected}
    onClick={() => setSelected((current) => !current)}
    type="button"
    variant="ghost"
  >
    Select model
  </Button>
</ListViewEnd>

// Menu action
<ListViewEnd>
  <DropdownMenu>
    <DropdownMenuTrigger
      render={
        <Button aria-label="Model actions" size="icon-sm" type="button" variant="ghost">
          <MoreHorizontalIcon className="size-4" />
        </Button>
      }
    />
    <DropdownMenuContent align="end">
      <DropdownMenuItem>Edit</DropdownMenuItem>
      <DropdownMenuItem>Duplicate</DropdownMenuItem>
      <DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
    </DropdownMenuContent>
  </DropdownMenu>
</ListViewEnd>
```

## Interaction

ListView displays information; its children own interaction. Clicking a title or description does not activate the End action. Native controls provide their own focus, keyboard, disabled, and navigation behavior without nesting pressable containers.

```tsx
<ListView>
  <ListViewStart title="Model name" description="Provider" />
  <ListViewEnd>
    <Switch aria-label="Enable model" checked={enabled} onCheckedChange={setEnabled} />
  </ListViewEnd>
</ListView>
```

Use an application Link in End for navigation, a Button for an action, or a DropdownMenu for multiple actions. Keep essential actions out of Intermediary because that slot is hidden on smaller screens.

### Breaking change: presentational root

Remove `interactive`, all root event handlers, `role`, `tabIndex`, and `contentEditable`. Move behavior to an explicit native control in End rather than attaching it to the row. Whole-row hover and pointer styles have been removed.

The root accepts `children`, `className`, `id`, `ref`, `style`, `title`, `hidden`, `inert`, `dir`, `lang`, `size`, `variant`, and `testId`. Other DOM props are not forwarded, including legacy props received through a spread. The ref still targets the layout's `HTMLDivElement`; slot and group APIs are unchanged.

## Variants

### Grouped (Default)

Use `ListViewGroup` to group multiple items with shared borders and automatic dividers:

```tsx
<ListViewGroup variant="outlined">
  <ListView>...</ListView>
  <ListView>...</ListView>
  <ListView>...</ListView>
</ListViewGroup>
```

### Standalone

For single items, use the `outlined` or `elevated` variant directly:

```tsx
<ListView variant="outlined">
  <ListViewStart title="Single Item" />
  <ListViewEnd>
    <Button onClick={openDetails} type="button">
      View details
    </Button>
  </ListViewEnd>
</ListView>
```

### Sizes

Control padding and gap with the `size` prop:

```tsx
<ListView size="sm">Compact padding (px-4 py-3)</ListView>
<ListView size="md">Standard padding (px-5 py-4)</ListView>
<ListView size="lg">Generous padding (px-6 py-5)</ListView>
```

## Combining with ListCard

ListView and ListCard share the same data model, making it easy to offer users a choice between list and grid views.

<ComponentPreview name="list-view-toggle-demo" description="Toggle between list and card views for the same data." />

## Examples

### Basic List

<ComponentPreview
  name="list-view-demo"
  description="Grouped rows with independent selection buttons, switches, and a menu."
/>

### List/Card Toggle

<ComponentPreview name="list-view-toggle-demo" description="Toggle between list and card views for the same data." />
