Toolbar Filter Table
A simple filter row above the table — dropdown selects and a search input, filters applied immediately. The 80% use case for filtering, and the default place to start.
npx shadcn add https://shad-table.dev/r/toolbar-filter-table.json| Title | Category | Priority | Status |
|---|---|---|---|
| Redesign onboarding flow | Design | High | In Progress |
| Fix pagination bug on export | Engineering | High | Open |
| Write Q3 newsletter draft | Marketing | Low | Done |
| Audit component color tokens | Design | Medium | Open |
| Migrate auth to new session store | Engineering | High | In Progress |
| Plan launch landing page copy | Marketing | Medium | Open |
| Ship dark mode for settings page | Design | Medium | Done |
| Add rate limiting to public API | Engineering | High | Open |
| Set up A/B test for pricing page | Marketing | Low | In Progress |
| Review accessibility on data table | Design | Medium | Open |
| Optimize bundle size for docs site | Engineering | Medium | Done |
| Coordinate partner co-marketing post | Marketing | Low | Done |
How it works
1.One search input, one filter per dropdown, no submit step
The search box writes to globalFilter and each Select writes to its own column filter on every change event — there's no Apply button and no debounce. getFilteredRowModel() re-runs on each keystroke and re-render, which is the entry-point pattern most tables need before anything fancier is worth reaching for.
const table = useReactTable({data,columns,getCoreRowModel: getCoreRowModel(),getFilteredRowModel: getFilteredRowModel(),onColumnFiltersChange: setColumnFilters,onGlobalFilterChange: setGlobalFilter,state: { columnFilters, globalFilter },})
2."All" is undefined, not a sentinel string left in the filter state
Radix Select can't represent an empty string as a value, so ToolbarSelect uses the literal 'all' for its placeholder item — but it converts that back to undefined before calling column.setFilterValue. An actual column filter is never set to the string 'all'; clearing a dropdown removes it from columnFilters entirely.
const value = (column?.getFilterValue() as string | undefined) ?? 'all'<Selectvalue={value}onValueChange={(next) =>column?.setFilterValue(next === 'all' ? undefined : next)}>
3.Global search and column filters compose for free
globalFilter (the search box) and columnFilters (the two Selects) are independent pieces of table state, but TanStack Table narrows the row model through both — typing in the search box while a Category filter is active searches only within that category, with no extra code to keep the two in sync.
<Inputplaceholder="Search tasks..."value={globalFilter}onChange={(e) => table.setGlobalFilter(e.target.value)}/><ToolbarSelect column={table.getColumn('category')} ... /><ToolbarSelect column={table.getColumn('status')} ... />