Selection & Actions
Row selection, bulk actions, row actions, and clickable rows.
Row Selection
Enable row selection with checkboxes using the enableRowSelection prop.
| Invoice | Status | Type | Customer | Amount | ||
|---|---|---|---|---|---|---|
| INV-0001 | Paid | credit | John Doe | john.doe@example.com | €321.00 | |
| INV-0002 | Pending | debit | Jane Smith | jane.smith@example.com | €106.00 | |
| INV-0003 | Pending | debit | Bob Wilson | bob.wilson@example.com | €348.00 | |
| INV-0004 | Overdue | credit | Alice Brown | alice.brown@example.com | €421.00 | |
| INV-0005 | Overdue | credit | Charlie Davis | charlie.davis@example.com | €399.00 | |
| INV-0006 | Pending | credit | Eva Martinez | eva.martinez@example.com | €529.00 | |
| INV-0007 | Pending | debit | Frank Miller | frank.miller@example.com | €546.00 | |
| INV-0008 | Pending | credit | Grace Lee | grace.lee@example.com | €299.00 | |
| INV-0009 | Paid | credit | Henry Chen | henry.chen@example.com | €387.00 | |
| INV-0010 | Paid | credit | Isabel Garcia | isabel.garcia@example.com | €476.00 |
import { useState } from "react"
import { DataTable, DataTableContent, DataTableFooter } from "@epilot/volt-ui"
function MyTable() {
const [rowSelection, setRowSelection] = useState({})
return (
<DataTable
columns={columns}
data={data}
enableRowSelection
rowSelection={rowSelection}
onRowSelectionChange={setRowSelection}
>
<DataTableContent />
<DataTableFooter>
<span className="text-sm text-gray-light">
{Object.keys(rowSelection).length} of {data.length} row(s) selected
</span>
</DataTableFooter>
</DataTable>
)
}Accessing Selected Row Data
The rowSelection state contains row IDs, not the actual data. To get the selected row data, use the useDataTable hook:
| Invoice | Status | Type | Customer | Amount | ||
|---|---|---|---|---|---|---|
| INV-0001 | Paid | credit | John Doe | john.doe@example.com | €321.00 | |
| INV-0002 | Pending | debit | Jane Smith | jane.smith@example.com | €106.00 | |
| INV-0003 | Pending | debit | Bob Wilson | bob.wilson@example.com | €348.00 | |
| INV-0004 | Overdue | credit | Alice Brown | alice.brown@example.com | €421.00 | |
| INV-0005 | Overdue | credit | Charlie Davis | charlie.davis@example.com | €399.00 | |
| INV-0006 | Pending | credit | Eva Martinez | eva.martinez@example.com | €529.00 | |
| INV-0007 | Pending | debit | Frank Miller | frank.miller@example.com | €546.00 | |
| INV-0008 | Pending | credit | Grace Lee | grace.lee@example.com | €299.00 | |
| INV-0009 | Paid | credit | Henry Chen | henry.chen@example.com | €387.00 | |
| INV-0010 | Paid | credit | Isabel Garcia | isabel.garcia@example.com | €476.00 |
import { useDataTable, Button } from "@epilot/volt-ui"
function BulkActions() {
const { table } = useDataTable()
// Get selected rows as Row<TData>[]
const selectedRows = table.getSelectedRowModel().rows
// Get the actual data from selected rows
const selectedData = selectedRows.map(row => row.original)
const handleBulkDelete = () => {
// selectedData contains the full row objects
console.log('Deleting:', selectedData)
deleteItems(selectedData.map(item => item.id))
}
if (selectedRows.length === 0) return null
return (
<div className="flex items-center gap-2">
<span>{selectedRows.length} selected</span>
<Button variant="secondary" size="sm" onClick={handleBulkDelete} destructive>
Delete selected
</Button>
</div>
)
}
function MyTable() {
const [rowSelection, setRowSelection] = useState({})
return (
<DataTable
columns={columns}
data={data}
enableRowSelection
rowSelection={rowSelection}
onRowSelectionChange={setRowSelection}
>
<DataTableToolbar>
<BulkActions />
</DataTableToolbar>
<DataTableContent />
</DataTable>
)
}Key methods:
table.getSelectedRowModel().rows- Array of selectedRow<TData>objectsrow.original- The original data object for that rowtable.getSelectedRowModel().flatRows- Flattened array (useful with grouped rows)
From Parent Component
Use the getSelectedRowData helper to derive selected data from the controlled rowSelection state. This works with multi-select.
| Invoice | Status | Type | Customer | Amount | ||
|---|---|---|---|---|---|---|
| INV-0001 | Paid | credit | John Doe | john.doe@example.com | €321.00 | |
| INV-0002 | Pending | debit | Jane Smith | jane.smith@example.com | €106.00 | |
| INV-0003 | Pending | debit | Bob Wilson | bob.wilson@example.com | €348.00 | |
| INV-0004 | Overdue | credit | Alice Brown | alice.brown@example.com | €421.00 | |
| INV-0005 | Overdue | credit | Charlie Davis | charlie.davis@example.com | €399.00 | |
| INV-0006 | Pending | credit | Eva Martinez | eva.martinez@example.com | €529.00 | |
| INV-0007 | Pending | debit | Frank Miller | frank.miller@example.com | €546.00 | |
| INV-0008 | Pending | credit | Grace Lee | grace.lee@example.com | €299.00 | |
| INV-0009 | Paid | credit | Henry Chen | henry.chen@example.com | €387.00 | |
| INV-0010 | Paid | credit | Isabel Garcia | isabel.garcia@example.com | €476.00 |
import { useState, useMemo } from "react"
import { DataTable, DataTableContent, Button, getSelectedRowData } from "@epilot/volt-ui"
function MyPage() {
const [rowSelection, setRowSelection] = useState({})
// Use helper to get selected data
const selectedData = useMemo(
() => getSelectedRowData(rowSelection, data),
[rowSelection, data]
)
const handleBulkDelete = () => {
console.log('Deleting:', selectedData)
// Call your API, update state, etc.
}
return (
<div>
{/* Actions outside the table */}
<div className="mb-4 flex items-center gap-2">
<span>{selectedData.length} selected</span>
<Button
variant="secondary"
size="sm"
onClick={handleBulkDelete}
disabled={selectedData.length === 0}
destructive
>
Delete selected
</Button>
</div>
<DataTable
columns={columns}
data={data}
enableRowSelection
rowSelection={rowSelection}
onRowSelectionChange={setRowSelection}
getRowId={(row) => row.id}
>
<DataTableContent />
</DataTable>
</div>
)
}Full Control
If you need more control over the selection logic, you can implement it manually:
const selectedData = useMemo(() => {
const selectedIds = Object.keys(rowSelection).filter(id => rowSelection[id])
return data.filter(item => selectedIds.includes(item.id))
}, [rowSelection, data])Important: When using controlled selection with custom row IDs, ensure getRowId on the DataTable returns the same ID format you use to look up items in your data array.
Row Actions
Add a dropdown menu for row-level actions using an actions column.
| Invoice | Status | Customer | Amount | Actions | |
|---|---|---|---|---|---|
| INV-0001 | Paid | John Doe | john.doe@example.com | €321.00 | |
| INV-0002 | Pending | Jane Smith | jane.smith@example.com | €106.00 | |
| INV-0003 | Pending | Bob Wilson | bob.wilson@example.com | €348.00 | |
| INV-0004 | Overdue | Alice Brown | alice.brown@example.com | €421.00 | |
| INV-0005 | Overdue | Charlie Davis | charlie.davis@example.com | €399.00 | |
| INV-0006 | Pending | Eva Martinez | eva.martinez@example.com | €529.00 | |
| INV-0007 | Pending | Frank Miller | frank.miller@example.com | €546.00 | |
| INV-0008 | Pending | Grace Lee | grace.lee@example.com | €299.00 | |
| INV-0009 | Paid | Henry Chen | henry.chen@example.com | €387.00 | |
| INV-0010 | Paid | Isabel Garcia | isabel.garcia@example.com | €476.00 |
import { DataTable, DataTableContent, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Button } from "@epilot/volt-ui"
import { IconDotsVertical, IconEye, IconPencil, IconCopy, IconTrash } from "@tabler/icons-react"
import type { ColumnDef } from "@tanstack/react-table"
const columnsWithActions: ColumnDef<Invoice>[] = [
{ accessorKey: "id", header: "Invoice" },
{ accessorKey: "status", header: "Status" },
{ accessorKey: "customer", header: "Customer" },
{
id: "actions",
header: () => <span className="sr-only">Actions</span>,
cell: ({ row }) => {
const invoice = row.original
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="tertiary" size="icon-sm">
<IconDotsVertical size={16} />
<span className="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<IconEye size={16} />
View details
</DropdownMenuItem>
<DropdownMenuItem>
<IconPencil size={16} />
Edit
</DropdownMenuItem>
<DropdownMenuItem onClick={() => navigator.clipboard.writeText(invoice.id)}>
<IconCopy size={16} />
Copy ID
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem destructive>
<IconTrash size={16} />
Delete
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
},
size: 48,
},
]
<DataTable columns={columnsWithActions} data={data} enableSorting>
<DataTableContent />
</DataTable>Clickable Rows
Make entire rows clickable using the onRowClick prop. Rows show a blue hover state and trigger an action when clicked. Interactive elements like action menus and checkboxes still work independently.
| Invoice | Status | Type | Customer | Amount | Actions | |
|---|---|---|---|---|---|---|
| INV-0001 | Paid | credit | John Doe | john.doe@example.com | €321.00 | |
| INV-0002 | Pending | debit | Jane Smith | jane.smith@example.com | €106.00 | |
| INV-0003 | Pending | debit | Bob Wilson | bob.wilson@example.com | €348.00 | |
| INV-0004 | Overdue | credit | Alice Brown | alice.brown@example.com | €421.00 | |
| INV-0005 | Overdue | credit | Charlie Davis | charlie.davis@example.com | €399.00 | |
| INV-0006 | Pending | credit | Eva Martinez | eva.martinez@example.com | €529.00 | |
| INV-0007 | Pending | debit | Frank Miller | frank.miller@example.com | €546.00 | |
| INV-0008 | Pending | credit | Grace Lee | grace.lee@example.com | €299.00 | |
| INV-0009 | Paid | credit | Henry Chen | henry.chen@example.com | €387.00 | |
| INV-0010 | Paid | credit | Isabel Garcia | isabel.garcia@example.com | €476.00 |
import { DataTable, DataTableContent } from "@epilot/volt-ui"
function MyTable() {
const handleRowClick = (row) => {
// Navigate to detail page or open modal
router.push(`/invoice/${row.original.id}`)
}
return (
<DataTable
columns={columns}
data={data}
onRowClick={handleRowClick}
>
<DataTableContent />
</DataTable>
)
}When using onRowClick with action menus or checkboxes, clicks on those interactive elements won't trigger the row click handler - they work independently.
getSelectedRowData Helper
Utility function to extract selected row data from selection state.
import { getSelectedRowData } from "@epilot/volt-ui"
const selectedData = useMemo(
() => getSelectedRowData(rowSelection, data),
[rowSelection, data]
)| Parameter | Type | Default | Description |
|---|---|---|---|
rowSelection | RowSelectionState | - | The selection state from DataTable |
data | TData[] | - | Your data array |
getRowId | (row: TData) => string | row.id | Optional ID accessor |
Returns: TData[] - Array of selected data items