Row selection
Add selectableRows to enable checkbox-based multi-select. Shift-click extends the selection across a contiguous range. Pass selectedRows to drive selection from outside, or use the imperative ref API to clear it.
⚠ keyField is required for reliable selection
DataTable uses keyField to uniquely identify each row. Tracking checked state across sorts, page changes, and re-renders. It defaults to "id". If your rows use a different unique field, set it explicitly:
<DataTable selectableRows keyField="deviceId" columns={columns} data={data} />If keyField resolves to undefined on any row, selection state will be unreliable and the table will log a console warning. TypeScript also catches explicit mismatches at compile time since keyField is typed as keyof T.
Multi-select with imperative clear
Select rows, then clear with the button. Toggle single-select mode or disable 'On Leave' rows with the checkboxes.
import { useState, useRef } from 'react';
import DataTable, { type TableColumn, type DataTableHandle } from 'react-data-table-component';
interface Employee {
id: number;
name: string;
role: string;
department: string;
status: 'Active' | 'On Leave';
}
const data: Employee[] = [
{ id: 1, name: 'Aria Chen', role: 'Engineering Lead', department: 'Engineering', status: 'Active' },
{ id: 2, name: 'Marcus Webb', role: 'Product Manager', department: 'Product', status: 'Active' },
{ id: 3, name: 'Priya Kapoor', role: 'Senior Designer', department: 'Design', status: 'On Leave' },
{ id: 4, name: 'Jordan Ellis', role: 'Data Scientist', department: 'Analytics', status: 'Active' },
{ id: 5, name: 'Sam Rivera', role: 'DevOps Engineer', department: 'Engineering', status: 'On Leave' },
{ id: 6, name: 'Taylor Brooks', role: 'Account Manager', department: 'Sales', status: 'Active' },
];
const columns: TableColumn<Employee>[] = [
{ name: 'Name', selector: r => r.name, sortable: true },
{ name: 'Role', selector: r => r.role },
{ name: 'Department', selector: r => r.department, sortable: true },
{ name: 'Status', selector: r => r.status },
];
export default function App() {
const [selectedRows, setSelectedRows] = useState<Employee[]>([]);
const [single, setSingle] = useState(false);
const [disableOnLeave, setDisableOnLeave] = useState(false);
const ref = useRef<DataTableHandle>(null);
return (
<div>
<label>
<input type="checkbox" checked={single} onChange={e => setSingle(e.target.checked)} /> Single select
</label>
<label>
<input type="checkbox" checked={disableOnLeave} onChange={e => setDisableOnLeave(e.target.checked)} /> Disable
"On Leave" rows
</label>
<button onClick={() => ref.current?.clearSelectedRows()}>Clear selection</button>
{selectedRows.length > 0 && (
<span>
{selectedRows.length} selected: {selectedRows.map(r => r.name).join(', ')}
</span>
)}
<DataTable
ref={ref}
columns={columns}
data={data}
keyField="id"
selectableRows
selectableRowsSingle={single}
selectableRowDisabled={disableOnLeave ? r => r.status === 'On Leave' : undefined}
onSelectedRowsChange={({ selectedRows }) => setSelectedRows(selectedRows)}
highlightOnHover
/>
</div>
);
}Range selection (Shift-click)
Click one row's checkbox, then Shift-click another to toggle every row in between to match the anchor's intended state. The anchor row is the most recent single toggle. Range selection is enabled by default; opt out with selectableRowsRange={false}.
The behaviour respects selectableRowDisabled — disabled rows in the range are skipped, not toggled. It also stays within the current page when pagination is enabled.
// Default: range selection on
<DataTable selectableRows />
// Disable range selection
<DataTable selectableRows selectableRowsRange={false} />Uncontrolled selection (default)
Omit selectedRows and the table owns selection itself. Read it viaonSelectedRowsChange, and clear it imperatively through the ref. This is the demo at the top of this page, and it is what you want unless something outside the table needs to set the selection.
const ref = useRef<DataTableHandle>(null);
<DataTable
ref={ref}
keyField="id"
columns={columns}
data={data}
selectableRows
onSelectedRowsChange={({ selectedRows }) => setCount(selectedRows.length)}
/>
// clear it from outside
ref.current?.clearSelectedRows();Controlled selection
Pass selectedRows to drive selection from your own state. The table will render those rows as checked and call onSelectedRowsChange when the user toggles. Match rows by keyField, so the entries you pass in must include the key field.
Reach for this when something other than the table needs to set the selection: a "select all matching" action, selection restored from URL or saved state, or a second view that has to stay in sync. The demo below drives it from filter predicates.
Controlled selection
The buttons set selection from outside the table. Checkbox clicks write back to the same state.
The buttons write straight to the selectedRows state. Checking a box in the table calls onSelectedRowsChange, which writes back to that same state. Both paths stay in sync.
- none yet
function App() {
const [selected, setSelected] = useState<Employee[]>([]);
const selectWhere = (predicate) => setSelected(data.filter(predicate));
return (
<>
<button onClick={() => selectWhere(r => r.status === 'Active')}>Select Active</button>
<button onClick={() => selectWhere(r => r.role.includes('Engineer'))}>Select Engineers</button>
<button onClick={() => setSelected(data.filter(r => !selected.some(s => s.id === r.id)))}>
Invert
</button>
<button onClick={() => setSelected([])}>Clear</button>
<DataTable
keyField="id"
columns={columns}
data={data}
selectableRows
selectedRows={selected}
onSelectedRowsChange={({ selectedRows }) => setSelected(selectedRows)}
/>
</>
);
}Controlled selection is useful when selection lives in URL state, a Redux/Zustand store, or needs to survive remounts. Omit selectedRows to fall back to the table's internal state.
Write the emitted rows back to the same state you pass to selectedRows. The table computes each toggle against the prop, so a value you set from outside is preserved when the user checks another box.
Single select
Pass selectableRowsSingle to restrict to one row at a time. Shift-click range selection is automatically disabled in single-select mode.
Disable specific rows
Pass a predicate to selectableRowDisabled to prevent specific rows from being checked. Disabled rows render with a greyed-out checkbox that cannot be interacted with. Toggle "Disable 'On Leave' rows" in the demo above to see this in action.
// Prevent rows matching a condition from being selected
<DataTable selectableRows selectableRowDisabled={row => row.status === 'On Leave'} />;Pre-select rows
<DataTable selectableRows selectableRowSelected={row => row.status === 'Active'} />;Select only visible rows
When pagination is enabled, selectableRowsVisibleOnly makes the "select all" checkbox operate only on the current page rather than the full dataset.
It also changes what happens when the page changes: the selection is cleared. That keeps "select all" honest — it always refers to the rows in front of you — but it means a selection cannot be accumulated across pages. Leave the prop off if you need that.
<DataTable selectableRows pagination selectableRowsVisibleOnly />;Page-scoped vs dataset-scoped selection
Two tables over the same nine rows, three per page. Tick select all on page 1, then move to page 2.
Nine rows, three per page. In both tables: tick "select all" on page 1, then go to page 2.
Default
Select all takes every row in the dataset, and the selection survives paging.
selectableRowsVisibleOnly
Select all takes only the current page, and changing page clears the selection.
// Default: select all takes the whole dataset, selection survives paging
<DataTable keyField="id" columns={columns} data={data} selectableRows pagination paginationPerPage={3} />
// Page-scoped: select all takes the current page, paging clears the selection
<DataTable
keyField="id"
columns={columns}
data={data}
selectableRows
pagination
paginationPerPage={3}
selectableRowsVisibleOnly
/>onSelectedRowsChange
The callback receives { allSelected, selectedCount, selectedRows }.
<DataTable
selectableRows
onSelectedRowsChange={({ selectedCount, selectedRows }) => {
console.log(`${selectedCount} rows selected`, selectedRows);
}}
/>;Clearing selection (imperative API)
Use a ref to call clearSelectedRows(). This is the recommended approach that avoids the toggle-boolean bug present in older versions.
const tableRef = useRef<DataTableHandle>(null);
tableRef.current?.clearSelectedRows();Custom selection toolbar
v8 removed the built-in contextMessage and contextActions props. Use onSelectedRowsChange to drive your own toolbar rendered outside the table — you get full control over layout, copy, and actions. See thebulk-action toolbar recipe for a complete example.
Highlight selected rows
Add selectableRowsHighlight to apply the theme's selected-row background to checked rows, giving an obvious visual confirmation of selection state.
<DataTable selectableRows selectableRowsHighlight />;Hide "select all" checkbox
Pass selectableRowsNoSelectAll to remove the header checkbox entirely. Useful when you want per-row selection without a bulk-select affordance.
<DataTable selectableRows selectableRowsNoSelectAll />;Custom checkbox component
Replace the built-in checkbox with your own component via selectableRowsComponent. Extra props can be forwarded through selectableRowsComponentProps.
import Checkbox from '@mui/material/Checkbox';
<DataTable selectableRows selectableRowsComponent={Checkbox} selectableRowsComponentProps={{ color: 'primary' }} />;Your component receives checked, disabled, name,onClick, and a ref. Forward that ref to a real<input type="checkbox"> — DataTable sets .indeterminate on the node directly for the partial-selection state on the header checkbox. Keep the input in the DOM and style a sibling element off it, rather than replacing it, so keyboard and screen reader behaviour still work.
Fully custom checkbox
A spinning vinyl record standing in for the checkbox. Disc turns amber when the header selection is partial; the accent colour is forwarded through selectableRowsComponentProps.
const VinylCheckbox = forwardRef<HTMLInputElement, Props>(
({ accent = '#7c3aed', checked, disabled, ...rest }, ref) => (
<label className="vinyl-wrap" style={{ '--vinyl-accent': accent }}>
{/* the ref must land on a real input: DataTable sets .indeterminate on it */}
<input ref={ref} type="checkbox" checked={checked} disabled={disabled} {...rest} />
<span className="vinyl-disc" aria-hidden="true">
<span className="vinyl-label" />
</span>
</label>
),
);
<DataTable
keyField="id"
columns={columns}
data={data}
selectableRows
selectableRowsComponent={VinylCheckbox}
selectableRowsComponentProps={{ accent }}
selectableRowDisabled={r => r.id === 4}
/>
/* The input is visually hidden but still present and focusable.
Everything visible is a sibling driven by :checked / :indeterminate. */
.vinyl-wrap input { position: absolute; inset: 0; opacity: 0; }
.vinyl-wrap input:checked ~ .vinyl-disc { transform: rotate(180deg); }
.vinyl-wrap input:indeterminate ~ .vinyl-disc { box-shadow: 0 0 0 2px #f59e0b; }A prop value can also be a function — it is called with the checkbox's indeterminate state (true for the header checkbox when only some rows are selected) and its return value is passed to the component instead. Useful for components that express the indeterminate state through a different prop:
<DataTable
selectableRows
selectableRowsComponent={Checkbox}
selectableRowsComponentProps={{
indeterminate: (isIndeterminate: boolean) => isIndeterminate,
}}
/>;Prop reference
| Prop / method | Type | Default | Description |
|---|---|---|---|
selectableRows | boolean | false | Enable row checkboxes |
selectableRowsSingle | boolean | false | Allow only one row to be selected at a time. |
selectableRowsNoSelectAll | boolean | false | Hide the "select all" checkbox in the header. |
selectableRowsVisibleOnly | boolean | false | "Select all" only selects rows on the current page. |
selectableRowsHighlight | boolean | false | Highlight selected rows using the theme's selected color. |
selectableRowsRange | boolean | true | Enable Shift-click range selection. Disabled automatically in single-select mode. |
selectableRowDisabled | (row: T) => boolean | - | Disable selection for a specific row. |
selectableRowSelected | (row: T) => boolean | - | Pre-select rows that satisfy the predicate. |
selectedRows | T[] | - | Controlled selection. When supplied, drives selection state from the outside; matched against keyField. |
selectableRowsComponent | "input" | ReactNode | built-in checkbox | Custom checkbox component. |
selectableRowsComponentProps | object | - | Extra props forwarded to the custom checkbox component. Function values are called with the checkbox's indeterminate state and their return value is passed instead. |
onSelectedRowsChange | (state) => void | - | Called whenever selection changes. Receives { allSelected, selectedCount, selectedRows }. |
clearSelectedRows | boolean | - | Deprecated. Toggle to clear selection. Use ref.current.clearSelectedRows() instead. |
ref.clearSelectedRows() | DataTableHandle | - | Imperatively deselect all selected rows. See DataTableHandle. |