Skip to content
Template Empire
Browse components

Table Bulk Selection

A free bulk selection table for React and Next.js, with accessible current-page selection.

0 selected

Wear Valley Railway fleet records
RecordTeamReadiness
76084 KirkstoneTrafficIn service
5643 GlyderEngineeringUnder overhaul
68011 StanhopeCollectionsStored

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-selection

Usage

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

PropTypeDefaultDescription
columns{ key: string; label: string; align?: "left" | "right" }[]requiredUnique column keys and labels in display order.
rows{ id: string; cells: Record<string, ReactNode> }[]requiredRows with stable, non-empty unique IDs and ReactNode cell content. Duplicate or empty IDs remain visible with disabled selection.
captionstringrequiredVisible caption that names the data table.
selectedIdsstring[]undefinedControlled selection. Omit this prop to use the component's internal selection state.
onSelectionChange(selectedIds: string[]) => voidundefinedCalled with deduplicated IDs in current row order followed by retained off-page IDs.
renderBulkActions(selectedIds: string[]) => ReactNodeundefinedOptional action slot, shown only when one or more IDs are selected.
classNamestringundefinedAdditional 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.

Related components

Want a complete product page?Template Empire templates pair production-ready interface sections with the same attention to detail.Browse Template Empire templates