ListView
A CSS Grid-based horizontal layout for displaying items with start, intermediary, and end slots.
Installation
npx shadcn@latest add @redpanda/list-viewpnpm dlx shadcn@latest add @redpanda/list-viewyarn dlx shadcn@latest add @redpanda/list-viewbunx shadcn@latest add @redpanda/list-viewnubx shadcn@latest add @redpanda/list-viewaube dlx shadcn@latest add @redpanda/list-viewUsage
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.
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 visibleSlot Guidelines
ListViewStart (Column 1)
Purpose: Primary content - the main information users need to identify the item.
Best practices:
- Use
titleprop for the item name (auto-wrapped in a semantich3) - Use
descriptionprop for secondary info (styled withtext-body text-subtle) - 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" /> <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
lgbreakpoint (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 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
// 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.
<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:
<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 onClick={openDetails} type="button"> View details </Button> </ListViewEnd></ListView>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.