Global search
The search box in the toolbar matches what you type against every searchable column at once, with a fuzzy matcher that forgives word order, prefixes and missing letters. It is on by default.
| Nia Silva | nia.silva@mail.test | Design | Intern | Active | Nigeria | — |
| Meera Smith | meera.smith@team.dev | Design | Analyst | Invited | Australia | — |
| Kavya Nair | kavya.nair@example.com | Design | Analyst | Invited | Canada | — |
| Freya Müller | freya.muller@example.com | Platform | Designer | Active | Singapore | — |
| Mateo Khan | mateo.khan@example.com | Growth | Designer | Paused | Singapore | Asked about invoices. |
| Liam Nair | liam.nair@mail.test | Design | Intern | Active | Singapore | — |
| Liam Tanaka | liam.tanaka@team.dev | Growth | Designer | Invited | Japan | Speaks at meetups. |
| Zara Sharma | zara.sharma@corp.example | Design | PM | Active | Brazil | — |
| Liam Reddy | liam.reddy@mail.test | Platform | Designer | Active | Japan | — |
| Sofia Patel | sofia.patel@example.com | Design | Lead | Active | Japan | — |
import { DataGrid, createDataGridColumns } from "@/components/ui/data-grid";
import { makePeople, STATUS_OPTIONS, type Person } from "@/lib/data-grid-demo-data";
const col = createDataGridColumns<Person>();
const columns = col.columns([ col.text("name", { header: "Name", size: 180 }), col.text("email", { header: "Email", size: 240 }), col.text("team", { header: "Team", size: 120 }), col.text("role", { header: "Role", size: 120 }), col.enum("status", { header: "Status", options: STATUS_OPTIONS, size: 120 }), col.text("country", { header: "Country", size: 130 }), // Free text that should not make a row match: keep it out of the search. col.text("notes", { header: "Notes", enableGlobalFilter: false, size: 220 }),]);
const data = makePeople(150, 14);
/** Starts with "design" typed: the Design team and every Designer match. Try "dsg" (a subsequence) or a surname. */export default function DataGridSearchDemo() { return ( <DataGrid data={data} columns={columns} getRowId={(row) => row.id} selection={false} initialState={{ globalFilter: "design" }} toolbar={{ grouping: false, density: false, export: false, fullscreen: false }} /> );}Use it
Section titled “Use it”Type. The grid waits 200 ms after the last keystroke, then filters. The × in the box, or Esc while the box has focus, clears it.
<DataGrid data={data} columns={columns} /> // fuzzy, the default<DataGrid data={data} columns={columns} globalFilter="includesString" /> // plain substring<DataGrid data={data} columns={columns} globalFilter={false} /> // no search boxWhat matches
Section titled “What matches”With the default "fuzzy" matcher, each cell’s text is scored against the query by rankMatch:
| Tier | The cell… | Example for ann |
|---|---|---|
| 6 | equals the query | Ann |
| 5 | starts with it | Annette |
| 4 | has a word that starts with it (after a space, _, -, ., / or () |
Mary Ann |
| 3 | contains it | Joanna |
| 2 | has initials that start with it (the first letter of each word) | Aarav Nair Nguyen |
| 1 | contains its letters in order, with gaps | Ananya |
| 0 | none of the above: no match |
A row passes when any searchable cell scores above 0. The tiers decide what matches; they do not reorder the rows, so the sort you have chosen still decides the order. Matching is case-insensitive.
"includesString" is TanStack’s built-in: the cell contains the query, case-insensitively, and
nothing looser. Choose it when a short query on a big grid matches too much by subsequence.
Which columns take part
Section titled “Which columns take part”Every column with an accessor, except:
- the utility columns (
select,expander,rowNumber,actions); - a column with
enableGlobalFilter: false; displaycolumns, which have no value to search.
col.text("notes", { enableGlobalFilter: false }) // free text that should not make a row matchHow it behaves
Section titled “How it behaves”- Search sees the raw value, not the formatted cell: a date is matched as its ISO string, a
currency as the bare number, an
enumas its value rather than its label, an array as its items joined by spaces. Searching for “$1,200” finds nothing; “1200” does. - Search and column filters combine: a row must pass both.
- A change takes the pager back to page 1. With grouping on, groups that end up empty disappear.
- The value lives in
grid.table.state.globalFilter;grid.table.setGlobalFilter("ann")sets it from your own control and the box follows.initialState={{ globalFilter: "ann" }}starts with a query, and thestateadapter (globalFilter,onGlobalFilterChange) lets the URL own it. - With server-side data, the grid passes the query on through the state and leaves the rows alone.
- The box is the
DataGridSearchpart. Put it anywhere insideDataGridRootwhen you compose your own toolbar;toolbar={{ search: false }}removes it from the built-in one.
Keyboard
Section titled “Keyboard”| Key | Where | What it does |
|---|---|---|
| Esc | in the search box | Clear the query |
Options
Section titled “Options”| Option | Type | Default | What it does |
|---|---|---|---|
globalFilter |
string or false |
"fuzzy" |
The matcher, "fuzzy" or "includesString", or no search at all. |
enableGlobalFilter (column) |
boolean | true |
false keeps the column out of the search. |
initialState.globalFilter |
string | "" |
The starting query. |
state.globalFilter |
adapter | With state.onGlobalFilterChange, another owner for the query. |
|
search |
the toolbar | true |
Show the box: toolbar={{ search: false }} hides it. |
labels.search |
string | "Search…" |
The placeholder and accessible name. |
See also
Section titled “See also”- Filtering: per-column filters with operators, ranges and facets.
- URL state: keep the query in the address bar.
- Server-side data: search on the server.