Skip to content
Nizivo UI

TableSelection

Row checkboxes for Table's bulk actions. Native checkboxes rendered on the server submit the checked ids with their form before any JavaScript runs; one small client island adds select-all, Shift-click ranges and Escape.

Server component with client leaf TableSelection

Import

import { TableSelectAll, TableSelectRow, TableSelection } from '@nizivo/react/table-selection';

// Once per app (Getting started), after
// @nizivo/tokens/tokens.css and @nizivo/react/css/base.css and @nizivo/react/css/checkbox.css and @nizivo/react/css/table.css
import '@nizivo/react/css/table-selection.css';

Static or interactive

Server, with one client part. TableSelectAll and TableSelectRow render native checkboxes with Checkbox's look and no client JavaScript; TableSelection is one client island around the whole table. Import them from @nizivo/react/table-selection, next to Table from @nizivo/react/table: a page that does not use selection loads none of it, under Turbopack and webpack alike. How to choose.

In a client app (a Vite or React Router single-page app, or a 'use client' file) it is an ordinary React component: event handlers, refs and state work as usual. In a client app.

Bulk actions

Put a TableSelectRow in each row and a TableSelectAll in the header, and wrap the table in TableSelection. Each row's box is a native checkbox that submits its value under name (ids by default) with its form, so a bulk action works before any JavaScript runs: pass form with the id of a form outside the table, as the example's "Archive selected" does. Give every row's box its own name (aria-label="Select order 1042"). A checked row paints bg/brand-subtle through CSS, with no script.

TableSelection adds what a native checkbox cannot do: the header's box checks or clears every row and shows the mixed state when only some are selected, Shift-click selects a range, and Escape clears the selection. A client owner hears every change through onSelectionChange(ids); a Server Component reads the selection from the submitted form. Rows with disabled boxes are left out.

The action bar

BulkActionBar is the bar over the table, with how many rows are selected and the actions; TableSelection writes its count.

Examples

Basic

Bulk actions: each row's checkbox is a native input of the form in the BulkActionBar (form), so "Archive" submits the checked ids even before JavaScript runs. TableSelection adds the select-all box (checked, mixed or clear), Shift-click ranges, Escape to clear, and the bar's "1 selected".

1 selected
Products
ProductInventoryPrice
Stoneware mug124 in stock$28.00
Linen apron8 in stock$42.00
Ceramic coaster setSold out$18.50
Speckled bowl36 in stock$24.00
import type { JSX } from 'react';
import { Button } from '@nizivo/react/button';
import { Stack } from '@nizivo/react/stack';
import { Table } from '@nizivo/react/table';
import { BulkActionBar } from '@nizivo/react/bulk-action-bar';
import { TableSelectAll, TableSelection, TableSelectRow } from '@nizivo/react/table-selection';

const MONEY = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });

const PRODUCTS = [
  ['mug-014', 'Stoneware mug', '124 in stock', 28],
  ['apron-002', 'Linen apron', '8 in stock', 42],
  ['coaster-007', 'Ceramic coaster set', 'Sold out', 18.5],
  ['bowl-021', 'Speckled bowl', '36 in stock', 24],
] as const;

export function TableSelectionExample(): JSX.Element {
  return (
    <Stack gap="3">
      <TableSelection>
        <BulkActionBar defaultCount={1}>
          <form id="products-bulk" action="#">
            <Button type="submit" size="sm" variant="secondary">
              Archive selected
            </Button>
          </form>
        </BulkActionBar>
        <Table.Container>
          <Table.Root>
            <Table.Caption visuallyHidden>Products</Table.Caption>
            <Table.Header>
              <Table.Row>
                <Table.Head>
                  <TableSelectAll />
                </Table.Head>
                <Table.Head>Product</Table.Head>
                <Table.Head>Inventory</Table.Head>
                <Table.Head numeric>Price</Table.Head>
              </Table.Row>
            </Table.Header>
            <Table.Body>
              {PRODUCTS.map(([id, name, stock, price]) => (
                <Table.Row key={id}>
                  <Table.Cell>
                    <TableSelectRow
                      value={id}
                      form="products-bulk"
                      aria-label={`Select ${name}`}
                      defaultChecked={id === 'apron-002'}
                    />
                  </Table.Cell>
                  <Table.Head scope="row">{name}</Table.Head>
                  <Table.Cell>{stock}</Table.Cell>
                  <Table.Cell numeric>{MONEY.format(price)}</Table.Cell>
                </Table.Row>
              ))}
            </Table.Body>
          </Table.Root>
        </Table.Container>
      </TableSelection>
    </Stack>
  );
}

Props

TableSelectAll

The header's checkbox: checks or clears every row of its TableSelection, and shows the mixed state when some are selected. It submits nothing. Without TableSelection it does nothing.

Server component Renders data-slot="table-select-all" with the class nz-table-select-all. Props: TableSelectAllProps. Source: src/table-selection/table-selection.tsx.

NameTypeDefaultDescription
labelsPartial<TableSelectionLabels>–

An aria-label wins over labels.selectAll.

  • Also takes every attribute of <input> except checked, children, defaultChecked, type. (Omit<ComponentPropsWithRef<"input">, "type" | "children" | "checked" | "defaultChecked">)

TableSelectRow

A row's checkbox: a native input that submits value under name with its form (pass form to reach a form outside the table). The row shows bg/brand-subtle while it is checked, through CSS.

Server component Renders data-slot="table-select-row" with the class nz-table-select-row. Props: TableSelectRowProps. Source: src/table-selection/table-selection.tsx.

NameTypeDefaultDescription
aria-labelrequiredstring–

The checkbox's name, unique per row, for example "Select order 1042".

defaultCheckedbooleanfalse

Selected when the page loads.

namestring'ids'
valuerequiredstring–

The row's id: what a form receives under name when the row is selected.

  • Also takes every attribute of <input> except checked, children, defaultChecked, type; aria-label, name, value are redeclared above. (Omit<ComponentPropsWithRef<"input">, "type" | "children" | "checked" | "defaultChecked">)

TableSelection

Client component Renders data-slot="table-selection" with the class nz-table-selection. Props: TableSelectionProps. Source: src/table-selection/table-selection.client.tsx.

NameTypeDefaultDescription
onSelectionChange(ids: string[]) => void–

The selected rows' values, after every change (and once on mount when a row is already checked, for example by the browser's form restoration). For a client owner; a Server Component reads the selection from the form.

renderRenderProp<Record<string, never>>–
  • Also takes every attribute of <div> except ref. (Omit<ComponentPropsWithRef<"div">, "ref">)

render takes an element, or, in client code, a function of the props and no state.

Other exports

TableSelectAllProps type

import type { TableSelectAllProps } from '@nizivo/react/table-selection'

The props of TableSelectAll, listed above.

TableSelectRowProps type

import type { TableSelectRowProps } from '@nizivo/react/table-selection'

The props of TableSelectRow, listed above.

TableSelectionLabels type

import type { TableSelectionLabels } from '@nizivo/react/table-selection'

Row selection's own strings (§16.4): serialisable, English defaults.

NameTypeDefaultDescription
selectAllrequiredstring'Select all rows'

The select-all checkbox's name.

TableSelectionProps type

import type { TableSelectionProps } from '@nizivo/react/table-selection'

The props of TableSelection, listed above.

Data attributes and slots

Style parts by class (.nz-<slot>) and state by data attribute. Attribute meanings come from contracts/vocabulary.json.

SlotRendered byData attributes
table-select-all
.nz-table-select-all
TableSelectAll
root (no render)
none
table-select-all-indicator
.nz-table-select-all-indicator
TableSelectAll
inner element (CSS only)
none
table-select-all-input
.nz-table-select-all-input
TableSelectAll
inner element (CSS only)
none
table-select-row
.nz-table-select-row
TableSelectRow
root (no render)
none
table-select-row-indicator
.nz-table-select-row-indicator
TableSelectRow
inner element (CSS only)
none
table-select-row-input
.nz-table-select-row-input
TableSelectRow
inner element (CSS only)
none
table-selection
.nz-table-selection
TableSelection
root (takes render)
none

CSS hooks

Set a hook on the component's class or any ancestor to restyle it without specificity games; leave it unset for the default shown.

This stylesheet reads no public hook.

Layers: nz.components. Classes: .nz-table-selection.

Accessibility

Nothing in this component takes focus or keyboard input.

Keyboard

KeysAction
TabReaches the header's checkbox, then each row's, with the table's other controls in document order.
SpaceOn a row's checkbox, checks or clears it; on the header's, every row.
Shift + clickOn a row's checkbox, gives every row between it and the last one clicked the same state.
EscapeInside a TableSelection with rows checked, clears them; with none, does nothing, so an enclosing Dialog still closes.

Source: Native checkboxes; TableSelection's own listener for select-all, Shift-click and Escape. Checked by the browser lane (test/browser/table-selection.spec.tsx) in Chromium, Firefox and WebKit.

Figma

Node ids from design/component-map.json, which nz gen writes from component.json. The links open the Nizivo Figma library, which may ask you to sign in.

  • Canonical: none
  • Ghost masters: none

On this page