Search docs

Jump to any table example

Discord—

Export Selected Rows

Tick the rows you need, across as many pages as you like, and export only those to CSV or Excel. The selection count and the export button stay in sync, and nothing downloads until something is selected.

$ npx shadcn add https://www.shad-table.dev/r/export-selected-table.json
Select rows to export them
NameEmailCompanyPlanMRR
Ava Thompsonava@northwind.comNorthwindEnterprise$4,200
Liam Chenliam@globex.comGlobexPro$290
Sofia Patelsofia@initech.comInitechPro$290
Noah Garcianoah@umbrella.comUmbrellaFree$0
Mia Johnsonmia@hooli.comHooliEnterprise$3,800
Ethan Kimethan@vandelay.comVandelayPro$580
Isabella Rossiisabella@starkindustries.comStark IndustriesEnterprise$6,100
Lucas Martinlucas@wayneenterprises.comWayne EnterprisesPro$290

1–8 of 25

How it works

1.Selection is keyed by id, so it survives pagination

By default TanStack keys selection by row index, so row 3 on page 1 and row 3 on page 2 would share a checkbox. getRowId makes the key the customer's id, so ticking rows on several pages builds one selection.

src/components/export-selected/data-table.tsx
const table = useReactTable({
data,
columns,
getRowId: (row) => row.id,
state: { rowSelection },
onRowSelectionChange: setRowSelection,
// ...
})

2.The header checkbox covers the page, "Select all" covers the table

The header box uses toggleAllPageRowsSelected, so it only touches the rows the user can see, and shows a dash when some of them are selected. Once the whole page is ticked, a "Select all 25" link offers the rest, the same pattern Gmail uses.

src/components/export-selected/columns.tsx
checked={
table.getIsAllPageRowsSelected()
? true
: table.getIsSomePageRowsSelected() ? 'indeterminate' : false
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}

3.Export reads the selected row model, not the visible rows

getSelectedRowModel() returns every selected row across all pages, in table order. The select column is skipped, and values come from row.getValue() so the file gets 4200 rather than "$4,200".

src/components/export-selected/data-table.tsx
const selectedRows = table.getSelectedRowModel().rows
const rows = selectedRows.map((row) =>
exportColumns.map((column) => String(row.getValue(column.id) ?? '')),
)