Redpanda UI
RC
Redpanda UI

ListView

A CSS Grid-based horizontal layout for displaying items with start, intermediary, and end slots.

Made by eblairmckee
Loading component...

Installation

Usage

The ListView component provides a consistent horizontal layout for displaying items in a list. It uses CSS Grid to create three distinct slots with different behavior:

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 Heading h3)
  • Use description prop for secondary info (auto-wrapped in muted Text)
  • Keep titles concise - they truncate on overflow
  • Use custom children for complex layouts (icons + text)
// 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" />
    <Heading level={4}>Claude 3.5 Sonnet</Heading>
  </div>
  <Text variant="muted">claude-3-5-sonnet-20241022</Text>
</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
<ListViewIntermediary gap="md">
  <Badge variant="success-inverted">Active</Badge>
  <BadgeGroup maxVisible={2}>
    <Badge size="sm">React</Badge>
    <Badge size="sm">TypeScript</Badge>
    <Badge size="sm">Tailwind</Badge>
  </BadgeGroup>
</ListViewIntermediary>

ListViewEnd (Column 3)

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

Common action patterns:

  • Switch - Toggle enable/disable state
  • Icon button (chevron) - Navigate to detail view
  • Icon button (menu) - Open dropdown with multiple actions
// Toggle action
<ListViewEnd>
  <Switch checked={enabled} onCheckedChange={setEnabled} />
</ListViewEnd>

// Navigate action
<ListViewEnd>
  <Button size="icon-sm" variant="ghost">
    <ChevronRightIcon className="size-4" />
  </Button>
</ListViewEnd>

// Menu action
<ListViewEnd>
  <DropdownMenu>
    <DropdownMenuTrigger asChild>
      <Button size="icon-sm" variant="ghost">
        <MoreHorizontalIcon className="size-4" />
      </Button>
    </DropdownMenuTrigger>
    <DropdownMenuContent align="end">
      <DropdownMenuItem>Edit</DropdownMenuItem>
      <DropdownMenuItem>Duplicate</DropdownMenuItem>
      <DropdownMenuItem className="text-destructive">Delete</DropdownMenuItem>
    </DropdownMenuContent>
  </DropdownMenu>
</ListViewEnd>

Interaction Patterns

ListView supports two mutually exclusive interaction patterns. Never nest pressable elements.

Pattern 1: Action in End Slot

The row displays information, with a single interactive element in the End slot.

<ListView>
  <ListViewStart title="Model Name" description="Provider" />
  <ListViewEnd>
    <Switch checked={enabled} onCheckedChange={setEnabled} />
  </ListViewEnd>
</ListView>

Use when:

  • Toggling a setting (Switch)
  • Opening a menu with multiple actions (DropdownMenu)
  • A specific action that doesn't navigate away

Pattern 2: Entire Row Clickable

The entire row is pressable and navigates somewhere. No interactive elements inside.

<ListView onClick={() => navigate(`/models/${id}`)}>
  <ListViewStart title="Model Name" description="Click to view details" />
  <ListViewEnd>
    <ChevronRightIcon className="size-4 text-muted-foreground" />
  </ListViewEnd>
</ListView>

Use when:

  • Navigating to a detail view
  • Selecting an item from a list
  • Opening a modal or panel

Key differences:

  • Hover styles and cursor are automatic when onClick is provided
  • End slot contains a visual indicator (icon), not a button
  • The chevron is not wrapped in a Button - it's just an icon

Anti-pattern: Nested Pressables

Never do this:

// ❌ BAD: Button inside a clickable row
<ListView interactive onClick={handleRowClick}>
  <ListViewStart title="Model" />
  <ListViewEnd>
    <Button onClick={handleEdit}>Edit</Button>  {/* Nested pressable! */}
  </ListViewEnd>
</ListView>

This creates confusion about what happens when clicking different parts of the row.

Variants

Grouped (Default)

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

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

Standalone

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

<ListView variant="outlined">
  <ListViewStart title="Single Item" />
  <ListViewEnd>
    <Button>Action</Button>
  </ListViewEnd>
</ListView>

Interactive

When onClick is provided, the row automatically gets hover styles and a pointer cursor:

<ListView onClick={() => navigate('/details')}>
  <ListViewStart title="Clickable Item" />
</ListView>

You can also explicitly set interactive={true} to add hover styles without an onClick handler, or interactive={false} to disable hover styles even with an onClick.

Sizes

Control padding and gap with the size prop:

<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.

Loading component...

Examples

Basic List

Loading component...

List/Card Toggle

Loading component...

Built by malinskibeniamin. The source code is available on GitHub.

On this page