Table Bulk Selection
A free bulk selection table for React and Next.js, with accessible current-page selection.
0 selected
| Record | Team | Readiness | |
|---|---|---|---|
| 76084 Kirkstone | Traffic | In service | |
| 5643 Glyder | Engineering | Under overhaul | |
| 68011 Stanhope | Collections | Stored |
Select all applies to the rows currently supplied to this table.
Customise
Code
const columns = [
{ key: "record", label: "Record" },
{ key: "team", label: "Team" },
{ key: "readiness", label: "Readiness" },
];
const rows = [
{ id: "76084", cells: { record: "76084 Kirkstone", team: "Traffic", readiness: "In service" } },
{ id: "5643", cells: { record: "5643 Glyder", team: "Engineering", readiness: "Under overhaul" } },
];
const [selectedIds, setSelectedIds] = useState<string[]>([]);
<TableBulkSelection
columns={columns}
rows={rows}
caption="Wear Valley Railway fleet records"
selectedIds={selectedIds}
onSelectionChange={setSelectedIds}
renderBulkActions={(ids) => <button>Archive {ids.length} records</button>}
/>Table Bulk Selection is a free data table component for Next.js and React, styled with Tailwind. Native checkboxes select records by stable ID, a mixed header checkbox selects only the rows currently supplied to the table, and an optional action slot receives the selected IDs.
Selection follows records when the supplied rows reorder and remains selected when a record is temporarily absent from the current page or filter. The selected count includes retained IDs, and the visible clear action resets the selection. Header selection applies to the rows currently supplied by the consumer; it does not select every record in a server query.
Install
Add the @te registry to your components.json once:
{
"registries": {
"@te": "https://templateempire.io/r/{name}.json"
}
}Then install the component:
npx shadcn@latest add @te/table-bulk-selectionUsage
import { useState } from "react";
import { TableBulkSelection } from "@/components/table-bulk-selection";
const columns = [
{ key: "record", label: "Record" },
{ key: "team", label: "Team" },
];
const rows = [
{ id: "76084", cells: { record: "76084 Kirkstone", team: "Traffic" } },
{ id: "5643", cells: { record: "5643 Glyder", team: "Engineering" } },
];
export default function Page() {
const [selectedIds, setSelectedIds] = useState<string[]>([]);
return (
<TableBulkSelection
columns={columns}
rows={rows}
caption="Fleet records"
selectedIds={selectedIds}
onSelectionChange={setSelectedIds}
renderBulkActions={(ids) => <button>Archive {ids.length} records</button>}
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| columns | { key: string; label: string; align?: "left" | "right" }[] | required | Unique column keys and labels in display order. |
| rows | { id: string; cells: Record<string, ReactNode> }[] | required | Rows with stable, non-empty unique IDs and ReactNode cell content. Duplicate or empty IDs remain visible with disabled selection. |
| caption | string | required | Visible caption that names the data table. |
| selectedIds | string[] | undefined | Controlled selection. Omit this prop to use the component's internal selection state. |
| onSelectionChange | (selectedIds: string[]) => void | undefined | Called with deduplicated IDs in current row order followed by retained off-page IDs. |
| renderBulkActions | (selectedIds: string[]) => ReactNode | undefined | Optional action slot, shown only when one or more IDs are selected. |
| className | string | undefined | Additional classes merged onto the table shell. |
Customisation
The table uses global semantic surface, border, foreground and muted tokens, with no component-specific CSS variables. Retheme those shared roles from a scoped ancestor and pass layout classes through className:
:root {
--surface: var(--background);
--border: var(--color-neutral-200);
}
<TableBulkSelection className="max-w-5xl" ... />Frequently asked questions
- Is Table Bulk Selection free for commercial use?
- Yes. Table Bulk Selection is MIT licensed and can be used in personal and commercial projects.
- Does the header select every result in a query?
- No. The header selects or clears only the rows currently supplied to the component, such as a visible page. Previously selected IDs outside those rows are retained and included in the count and callback.
- Can I control and customise the selected actions?
- Yes. Pass selectedIds and onSelectionChange for controlled state, or omit selectedIds to use internal state. renderBulkActions receives the selected record IDs whenever the selection is non-empty.