gridcn1.2.2

Toolbar

The data-grid-toolbar add-on: a flex frame for search, filter menu, sort list, columns menu, and your own buttons.

data-grid-toolbar is a flex frame for the grid's toolbar slots: quick search, the filter menu, the sort list, the columns menu, and your own buttons. It renders children left to right in the order you write them, and each piece is independent. Drop one and the rest keep their relative order.

pnpm dlx shadcn@latest add @gridcn/data-grid-toolbar

Click a header to sort (click again to reverse, a third click clears it); shift-click another header to add it as a secondary sort. Use Filter in the toolbar for per-column filters.

Name
Email
Age
Role
Score
Silent Tiger
user0@example.com
63
Viewer
4
Quick Lion
user1@example.com
67
Viewer
89
Swift Wolf
user2@example.com
40
Manager
80
Eager Lion
user3@example.com
50
User
51
Eager Tiger
user4@example.com
45
Viewer
83
Silent Wolf
user5@example.com
24
User
15
Swift Lion
user6@example.com
34
Viewer
62
Eager Lion
user7@example.com
21
Manager
42
Eager Eagle
user8@example.com
25
Editor
35
Bold Tiger
user9@example.com
19
Editor
52
Bold Bear
user10@example.com
42
User
28
Swift Wolf
user11@example.com
47
Manager
9
Swift Bear
user12@example.com
41
Viewer
45
Eager Wolf
user13@example.com
35
Editor
44
Swift Eagle
user14@example.com
65
Viewer
51
Quick Lion
user15@example.com
22
Manager
28
Bold Wolf
user16@example.com
30
Editor
76
Swift Eagle
user17@example.com
19
Admin
26
Eager Eagle
user18@example.com
35
Manager
70
Eager Bear
user19@example.com
33
Editor
67
Silent Lion
user20@example.com
48
Manager
89
Silent Tiger
user21@example.com
62
Manager
40
Eager Tiger
user22@example.com
51
Manager
82
Silent Lion
user23@example.com
26
Admin
16
Bold Eagle
user24@example.com
26
Admin
38
Bold Lion
user25@example.com
49
Viewer
3
Quick Tiger
user26@example.com
26
Viewer
41
Bold Eagle
user27@example.com
50
Editor
10
Swift Tiger
user28@example.com
55
Editor
6
Silent Lion
user29@example.com
35
Viewer
42

Wiring

The toolbar sits between the provider and the root, so it needs the split form of the grid (DataGridProvider, DataGridRoot, DataGridHeader, DataGridBody) instead of <DataGrid>. useDataGridState comes from the undo & redo add-on. Any controlled data plus onDataChange works in its place:

"use client";

import {
  DataGridProvider,
  DataGridRoot,
  DataGridHeader,
  DataGridBody,
} from "@/components/data-grid/data-grid";
import { useDataGridState } from "@/components/data-grid-history/data-grid-history";
import {
  DataGridToolbar,
  DataGridSearch,
  DataGridFilterMenu,
  DataGridColumnsMenu,
} from "@/components/data-grid-toolbar/data-grid-toolbar";
import { columns, initialRows } from "./columns";

export function PeopleGrid() {
  const grid = useDataGridState(initialRows, { getRowId: (row) => row.id });

  return (
    <DataGridProvider {...grid} columns={columns}>
      <DataGridToolbar>
        <DataGridSearch />
        <DataGridFilterMenu />
        <DataGridColumnsMenu />
      </DataGridToolbar>
      <DataGridRoot className="h-[400px]">
        <DataGridHeader />
        <DataGridBody />
      </DataGridRoot>
    </DataGridProvider>
  );
}

Standard toolbar order

The gridcn convention is: DataGridSearch first (leftmost. Finding data comes before anything else), then the row-view modifiers DataGridFilterMenu and DataGridSortList, then the column configuration DataGridColumnsMenu, then data movement DataGridImportButton before DataGridExportButton (data flows in, then out), and your own utility buttons (shortcuts, density, and the like) last.

The pieces

  • DataGridSearch: quick search with match navigation. The behavior (capping, mod+F capture) is documented on Sorting, filtering & search: search.
  • DataGridFilterMenu: the per-column filter popover. The operator set and filter semantics are on Sorting, filtering & search: filtering. Pass operatorsForColumn(column) to replace the per-column operator list; resolution order is this prop, then the column's own filterOperators, then the cell type's filter.operators, then the built-in default for the type's name.
  • DataGridSortList: the sort management popover, its own page.
  • DataGridColumnsMenu: show and hide only. Pin controls live in the header menu, not here (see Columns: visibility).

Filter Name for only equals/not equals, or Score for only greater/less than. Sort by Email even though it is hidden from the grid.

Name
Score
Silent Tiger
4
Quick Lion
89
Swift Wolf
80
Eager Lion
51
Eager Tiger
83
Silent Wolf
15
Swift Lion
62
Eager Lion
42
Eager Eagle
35
Bold Tiger
52
Bold Bear
28
Swift Wolf
9
Swift Bear
45
Eager Wolf
44
Swift Eagle
51
Quick Lion
28
Bold Wolf
76
Swift Eagle
26
Eager Eagle
70
Eager Bear
67
Silent Lion
89
Silent Tiger
40
Eager Tiger
82
Silent Lion
16
Bold Eagle
38
Bold Lion
3
Quick Tiger
41
Bold Eagle
10
Swift Tiger
6
Silent Lion
42

Filter menu keyboard & accessibility

KeyAction
TabMoves through a row's controls, in order: column select, operator select, value, remove, drag handle
ArrowUp / ArrowDown (drag handle focused)Moves that filter row one position up/down
Space / Enter (drag handle focused)Picks up/drops the row for pointer-equivalent keyboard dragging (@dnd-kit/react's keyboard sensor)
Backspace / Delete (drag handle focused)Removes that filter row

Plain ArrowUp or ArrowDown on a focused drag handle always moves the row by exactly one position. The keyboard sensor of @dnd-kit/react also offers its own gesture for that key, but gridcn checks the plain arrow press first, and it wins. Every reorder announces the change through a polite aria-live region (labels.toolbar.filterReorderAnnouncement, for example "Name filter moved to position 2 of 3"). Focus never gets lost. After a reorder, focus stays on the moved row's drag handle. After deleting a row, focus moves to the next row's drag handle, or to the "Add filter" button once the list is empty.

Header filter

New

A column header can hold its own filter button. The button opens a popover with a searchable list of the column's values, and the user checks the values to keep. DataGridHeaderFilter is that button. Pass it to the renderHeaderFilter slot of DataGridRoot.

Name
Role
Age
Score
Silent Tiger
Viewer
63
4
Quick Lion
Viewer
67
89
Swift Wolf
Manager
40
80
Eager Lion
User
50
51
Eager Tiger
Viewer
45
83
Silent Wolf
User
24
15
Swift Lion
Viewer
34
62
Eager Lion
Manager
21
42
Eager Eagle
Editor
25
35
Bold Tiger
Editor
19
52
Bold Bear
User
42
28
Swift Wolf
Manager
47
9
Swift Bear
Viewer
41
45
Eager Wolf
Editor
35
44
Swift Eagle
Viewer
65
51
Quick Lion
Manager
22
28
Bold Wolf
Editor
30
76
Swift Eagle
Admin
19
26
Eager Eagle
Manager
35
70
Eager Bear
Editor
33
67
Silent Lion
Manager
48
89
Silent Tiger
Manager
62
40
Eager Tiger
Manager
51
82
Silent Lion
Admin
26
16
Bold Eagle
Admin
26
38
Bold Lion
Viewer
49
3
Quick Tiger
Viewer
26
41
Bold Eagle
Editor
50
10
Swift Tiger
Editor
55
6
Silent Lion
Viewer
35
42

The slot receives { column, index }. Spread it into the component:

import { DataGridHeaderFilter } from "@/components/data-grid-toolbar/data-grid-toolbar";

<DataGridRoot renderHeaderFilter={(ctx) => <DataGridHeaderFilter {...ctx} />}>
  <DataGridHeader />
  <DataGridBody />
</DataGridRoot>

The button shows on each column that offers isAnyOf or isNoneOf and is not filterable: false. By default these are the text, number, date, and select columns. The button stays hidden until hover or focus. While the column has a filter, the button stays visible and the header cell carries data-filtered.

The popover works like this:

  • The list holds one entry per distinct value, with the number of rows that have it. Values that differ only in case form one entry, and Apply writes the first spelling it meets. An empty value shows as "(Blanks)".
  • The search box New narrows the list. The popover opens with focus in it. "Select all" is a checkbox at the start of the search field. It checks or unchecks the values that match the typed text. It comes before the search input in the Tab order, and Space toggles it. Its box is empty when none of those values is checked, shows the indeterminate mark when some are, and shows a check when all are.
  • The status line New under the search reads "N of M selected". N is the number of checked values and M is the number of values in the list. Set the text with the toolbar.headerFilterSelectedCount label. The counts of the entries sit in a right-aligned column.
  • The counts include only the rows that pass every other filter. The filter of the column itself never hides its own values. If the join is or, the counts include all rows.
  • Apply stores the shorter list. If fewer values are checked than unchecked, it writes isAnyOf with the checked values. Otherwise it writes isNoneOf with the unchecked values. A value that enters the data later passes isNoneOf.
  • Apply keeps every other filter, including condition filters on the same column.
  • Apply is disabled while no value is checked, and while the stored list would hold more than 1,000 entries. A message explains the second case.
  • Clear removes every filter of the column. Escape or a press outside the popover discards the selection.
  • The list reads at most 100,000 distinct values. Past that limit it shows the first values and a message.

The filter works in a read-only grid, because filtering does not write data.

KeyAction
Shift+Alt+ArrowUpOpens the popover of the active cell's column. Focus returns to the grid when the popover closes
ArrowUp / ArrowDown / Home / End (list)Moves between values
Space (list)Checks or unchecks the value
Enter (list or search box)Applies the selection

The filter menu uses the same list for isAnyOf and isNoneOf on a column without choices.

DataGridHeaderFilter takes the two values that the slot passes:

PropTypeDefaultMeaning
columnAnyColumnDefrequiredThe column that the button filters.
indexnumberrequiredThe index of the column in the header.

With lazy rows or sortMode="server", the list shows only the values of the loaded rows. A value that exists only on the server is missing from the list. Your server decides which rows the grid loads, so it decides which values the list can show. The popover then shows the hint "From loaded rows". Server pagination with client sorting loads whole pages, so the hint stays hidden. Set the text with the toolbar.headerFilterLoadedRowsOnly label.

On this page