Cards
Get a cards view without writing a card, then take over rendering with custom cards or drop to a fully bare card.
Cards without writing a card
A table config renders a cards view too, built from the same columns — stacked label/value pairs that honor the user's column choices and each column's render. That is what the view toggle switches to, and what a viewport under 1024px shows automatically.
defineListConfig<Order>({
id: 'orders',
table: { columns },
// card: undefined → generated from `columns` (the default)
// card: item => … → your own renderer, below
// card: false → table only, no toggle, no cards on mobile
})The auto card is a starting point, not a ceiling: pass a card renderer as soon as a list deserves a designed one.
Custom cards with actions and theme
The card renderer receives the row item plus a ctx object with actions, the active color theme, and
the row's index — pass that index along to reuse the very same RowAction[] the table rows use,
instead of writing the actions a second time for cards:
defineListConfig<Product>({
/* … */
actions: {
onEdit: item => openEditModal(item),
onDelete: item => confirmDelete(item),
},
card: (item, ctx) => (
<div className='p-4'>
<h3 className='font-semibold'>{item.name}</h3>
<div className='mt-3 flex gap-2'>
<button
onClick={() => ctx.actions.onEdit?.(item)}
className={cn(
'rounded-md px-3 py-1 text-sm',
ctx.colorTheme.primaryBg,
ctx.colorTheme.primaryText
)}
>
Edit
</button>
</div>
</div>
),
})Fully custom cards (bareCard)
By default each card is wrapped in listkit's <Card> (border, padding, shadow). Set bareCard: true to render your card output directly — drop in your own card component without double chrome:
defineListConfig<Post>({
bareCard: true,
gridCols: 'md:grid-cols-2 lg:grid-cols-3',
card: post => <MyPostCard {...post} />,
})When both a card and a table are configured, listkit shows a view toggle and defaults to table on desktop, cards on narrow screens. Set defaultView: 'cards' to open in cards on desktop too (the table stays available via the toggle, and a manual switch still wins):
defineListConfig<Post>({
defaultView: 'cards',
card: post => <MyPostCard {...post} />,
table: { columns },
})