Configure the grid toolbar with search and actions.
The toolbar provides a header area for search functionality and custom actions. It appears above the grid and can be customized with search options and action buttons.
interface GridToolbarConfig<TData = any> {
hidden?: boolean;
className?: string;
search?: {
hidden?: boolean;
placeholder?: string;
className?: string;
debounceMs?: number;
};
actions?: ReactNode | ((table: Table<TData>) => ReactNode);
}<ActiveGrid
columns={columns}
data={data}
toolbar={{
search: {
placeholder: 'Search...',
},
actions: <Button>Add New</Button>,
}}
{...}
/>booleantoolbar={{
hidden: true,
}}stringtoolbar={{
className: 'bg-muted/10 border-b p-4',
}}booleantoolbar={{
search: {
hidden: true,
},
}}string'Search...'toolbar={{
search: {
placeholder: 'Search records...',
},
}}stringtoolbar={{
search: {
className: 'w-64',
},
}}number300Provide React elements directly:
New in v5.1
Use a function to access the table instance:
The search input filters across all columns by default (global filter):
For custom search logic, handle it programmatically:
The toolbar always appears above the grid. For bottom positioning, use custom layout:
Ensure toolbar is accessible:
toolbar={{
search: {
debounceMs: 500, // Wait 500ms after typing stops
},
}}toolbar={{
actions: (
<div className="flex gap-2">
<Button onClick={handleAdd}>
<Plus className="mr-2 h-4 w-4" />
Add New
</Button>
<Button variant="outline" onClick={handleExport}>
Export
</Button>
</div>
),
}}toolbar={{
actions: (table) => {
const selectedCount = table.getSelectedRowModel().rows.length;
if (selectedCount > 0) {
return (
<div className="flex gap-2">
<Badge>{selectedCount} selected</Badge>
<Button onClick={() => handleBulkDelete(table)}>
Delete Selected
</Button>
</div>
);
}
return (
<Button onClick={handleAdd}>
<Plus className="mr-2 h-4 w-4" />
Add New
</Button>
);
},
}}<ActiveGrid
toolbar={{
search: {
placeholder: 'Search...',
},
}}
{...}
/><ActiveGrid
toolbar={{
className: 'border-b bg-muted/30 p-3',
search: {
placeholder: 'Search users...',
className: 'w-80',
debounceMs: 400,
},
actions: (table) => {
const selectedCount = table.getSelectedRowModel().rows.length;
return (
<div className="flex gap-2 items-center">
{selectedCount > 0 ? (
<>
<Badge variant="secondary">{selectedCount} selected</Badge>
<Button
size="sm"
variant="destructive"
onClick={() => handleBulkDelete(table)}
>
Delete
</Button>
<Button
size="sm"
variant="outline"
onClick={() => table.resetRowSelection()}
>
Clear Selection
</Button>
</>
) : (
<>
<Button size="sm" onClick={handleAdd}>
<Plus className="mr-2 h-4 w-4" />
Add User
</Button>
<Button size="sm" variant="outline" onClick={handleImport}>
<Upload className="mr-2 h-4 w-4" />
Import
</Button>
<Button size="sm" variant="outline" onClick={() => handleExport(table)}>
<Download className="mr-2 h-4 w-4" />
Export
</Button>
</>
)}
</div>
);
},
}}
{...}
/>import { DropdownMenu } from '@workspace/ui';
<ActiveGrid
toolbar={{
search: {
placeholder: 'Search...',
},
actions: (table) => (
<div className="flex gap-2">
<Button onClick={handleAdd}>Add New</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
Columns
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{table.getAllColumns()
.filter(col => col.getCanHide())
.map(col => (
<DropdownMenuCheckboxItem
key={col.id}
checked={col.getIsVisible()}
onCheckedChange={(value) => col.toggleVisibility(!!value)}
>
{col.id}
</DropdownMenuCheckboxItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
),
}}
{...}
/>toolbar={{
actions: (table) => {
const columnFilters = table.getState().columnFilters;
const hasFilters = columnFilters.length > 0;
return (
<div className="flex gap-2 items-center">
<Button onClick={handleAdd}>Add New</Button>
{hasFilters && (
<>
<Badge variant="secondary">
{columnFilters.length} filter{columnFilters.length !== 1 ? 's' : ''}
</Badge>
<Button
variant="ghost"
size="sm"
onClick={() => table.resetColumnFilters()}
>
Clear Filters
</Button>
</>
)}
</div>
);
},
}}// Searches in all text columns
toolbar={{
search: {
placeholder: 'Search all columns...',
},
}}import { useActiveGrid } from '@workspace/active-grid';
function CustomToolbar() {
const table = useActiveGrid();
const [search, setSearch] = useState('');
const handleSearch = useMemo(
() =>
debounce((value: string) => {
// Custom search logic
table.setGlobalFilter(value);
}, 300),
[table]
);
return (
<input
value={search}
onChange={(e) => {
setSearch(e.target.value);
handleSearch(e.target.value);
}}
placeholder="Custom search..."
/>
);
}import { exportToCSV } from '@workspace/active-grid';
toolbar={{
actions: (table) => (
<Button
onClick={() => {
const rows = table.getFilteredRowModel().rows;
const columns = table.getAllColumns();
exportToCSV(rows, columns, 'export.csv');
}}
>
Export to CSV
</Button>
),
}}toolbar={{
className: 'flex-col sm:flex-row gap-2',
search: {
className: 'w-full sm:w-80',
},
actions: (
<div className="flex flex-wrap gap-2">
<Button size="sm">Add</Button>
<Button size="sm" variant="outline">Export</Button>
</div>
),
}}toolbar={{
search: {
hidden: true,
},
actions: <Button>Add New</Button>,
}}<div className="flex flex-col">
<ActiveGrid
toolbar={{
search: { placeholder: 'Search...' },
}}
{...}
/>
{/* Custom bottom toolbar */}
<div className="border-t p-2">
<Button>Bottom Action</Button>
</div>
</div>toolbar={{
className: 'bg-gradient-to-r from-blue-50 to-purple-50 border-b-2 border-primary p-4',
search: {
className: 'border-primary focus-visible:ring-primary',
},
}}toolbar={{
className: 'sticky top-0 z-10 bg-background/95 backdrop-blur',
actions: (
<div className="flex gap-2 items-center">
<Button size="sm" className="gap-2">
<Plus className="h-4 w-4" />
Add
</Button>
</div>
),
}}toolbar={{
search: {
placeholder: 'Search records',
// Automatically includes aria-label
},
actions: (
<Button aria-label="Add new record">
<Plus className="h-4 w-4" />
Add
</Button>
),
}}import { GridToolbarConfig } from '@workspace/active-grid';
import { Table } from '@tanstack/react-table';
type User = {
id: string;
name: string;
};
const toolbarConfig: GridToolbarConfig<User> = {
search: {
placeholder: 'Search users...',
debounceMs: 300,
},
actions: (table: Table<User>) => {
const selectedRows = table.getSelectedRowModel().rows;
return <Button>Actions ({selectedRows.length})</Button>;
},
};
<ActiveGrid<User>
toolbar={toolbarConfig}
{...}
/>const actions = useMemo(
() => (table: Table<Data>) => {
// Expensive computation
return <Button>Action</Button>;
},
[/* dependencies */]
);
<ActiveGrid
toolbar={{ actions }}
{...}
/>import { Plus, Download, Upload, Filter } from 'lucide-react';
import { exportToCSV } from '@workspace/active-grid';
function UsersTable() {
const handleAdd = () => {
// Add logic
};
const handleExport = (table: Table<User>) => {
const rows = table.getFilteredRowModel().rows;
const columns = table.getAllColumns();
exportToCSV(rows, columns, 'users.csv');
};
return (
<ActiveGrid
columns={columns}
data={users}
toolbar={{
className: 'border-b bg-muted/30 p-3',
search: {
placeholder: 'Search users by name or email...',
className: 'w-96',
debounceMs: 400,
},
actions: (table) => {
const selectedCount = table.getSelectedRowModel().rows.length;
const hasFilters = table.getState().columnFilters.length > 0;
return (
<div className="flex gap-2 items-center">
{selectedCount > 0 ? (
<>
<Badge>{selectedCount} selected</Badge>
<Button
size="sm"
variant="destructive"
onClick={() => handleBulkDelete(table)}
>
Delete Selected
</Button>
</>
) : (
<>
<Button size="sm" onClick={handleAdd}>
<Plus className="mr-2 h-4 w-4" />
Add User
</Button>
<Button
size="sm"
variant="outline"
onClick={() => handleExport(table)}
>
<Download className="mr-2 h-4 w-4" />
Export
</Button>
<Button size="sm" variant="outline">
<Upload className="mr-2 h-4 w-4" />
Import
</Button>
</>
)}
{hasFilters && (
<Button
size="sm"
variant="ghost"
onClick={() => table.resetColumnFilters()}
>
<Filter className="mr-2 h-4 w-4" />
Clear Filters
</Button>
)}
</div>
);
},
}}
/>
);
}