Better Tables

Selection & Actions

Row selection with checkboxes, and bulk actions that operate on the selected set.

Row selection adds a checkbox column (with select-all in the header) and tracks selected row ids. Enable it explicitly, or implicitly by passing actions — the checkbox column appears whenever actions is non-empty or features.rowSelection is true.

<BetterTable
  id="tickets"
  columns={columns}
  data={data}
  features={{ rowSelection: true }}
  initialSelectedRows={new Set(['42'])}
  onSelectionChange={(selected) => console.log([...selected])}
/>

Row identity comes from rowConfig.getId(row) when provided, falling back to the row's id field — set getId whenever your rows have a different key.

Plain row selection needs no module — it is part of core.

Bulk actions

The actions toolbar is an opt-in module. Add it once:

bunx @better-tables/cli add actions

Then wire it into the table's actionsToolbar slot. actions is a TableAction[]; the toolbar renders above the table and invokes handlers with the current selection:

import { Archive, Trash2 } from 'lucide-react';
import { ActionsToolbar } from '@/components/better-tables-ui/table/actions-toolbar';

<BetterTable
  id="tickets"
  columns={columns}
  data={data}
  slots={{ actionsToolbar: ActionsToolbar }}
  actions={[
    {
      id: 'archive',
      label: 'Archive',
      icon: Archive,
      handler: async (selectedIds, selectedData) => {
        await archiveTickets(selectedIds);
      },
    },
    {
      id: 'delete',
      label: 'Delete',
      icon: Trash2,
      variant: 'destructive',
      confirmationDialog: {
        title: 'Delete tickets?',
        description: 'This cannot be undone.',
        confirmLabel: 'Delete',
      },
      handler: async (selectedIds) => deleteTickets(selectedIds),
    },
  ]}
/>

TableAction (complete)

FieldPurpose
idUnique action identifier
labelButton label
iconIcon component (optional)
variantButton visual variant: 'default' | 'destructive' | 'secondary'
handler(selectedIds, selectedData?)The work — sync or async
confirmationDialog{ title, description, confirmLabel?, cancelLabel?, destructive? } — confirm before running
isVisible(selectedIds, selectedData?)Hide the action for some selections
isEnabled(selectedIds, selectedData?)Disable (but show) the action
metaArbitrary metadata passthrough

After a handler resolves, refetch your data — the table doesn't mutate rows itself. Selection state can also be seeded with initialSelectedRows and observed via onSelectionChange (see the props reference).

To customize the toolbar (for example, the selected-count label), edit the copied actions-toolbar.tsx directly; add app-specific toast handling there as needed.

On this page