DataGrid
A powerful data grid with sorting, filtering, pagination, selection, inline editing, column pinning, resizable columns, row editing, layout persistence, context menus, and CSV/Excel/JSON export.
Installation
dotnet add package Lumeo.DataGrid
One-time app setup (AddLumeo(), CSS & JS) is covered in the
installation guide.
Usage
@using Lumeo <DataGrid />
- ClosedXML 0.104.2 (Excel
.xlsxexport) — MIT, fully free for any use. - QuestPDF 2024.12.3 (PDF export) — dual-licensed:
- Free for individual developers and for organizations with annual gross revenue under $1M USD (Community License).
- Paid ($699/dev/year as of writing) for everyone else — purchased direct from questpdf.com. Lumeo doesn't take a cut.
- CSV / JSON exports use built-in .NET serialization — no third-party dep, no licensing concern.
ExportPdfAsync triggers the licensing requirement.
Since v3.7.0: the export backend (ClosedXML + QuestPDF, ~1.65 MB brotli) ships as a separate bundled DLL inside
Lumeo.DataGrid and can be lazy-loaded on
Blazor WebAssembly — keep it out of the first paint and pull it in on the first export click.
See the lazy-loading setup guide.
When to Use
- Complex data grids with advanced features like sorting, filtering, and grouping
- Enterprise table views with inline editing and cell-level validation
- Column pinning and resizable columns for wide data sets
- Scenarios requiring row selection, context menus, and data export (CSV/Excel/JSON)
- Dashboards where the grid starts compact but the user can expand it to fullscreen for a full view
| Alice Johnson | Engineering | $95,000 | 2021-03-15 |
| Bob Smith | Marketing | $72,000 | 2020-07-01 |
| Carol White | Engineering | $105,000 | 2019-11-20 |
| David Brown | Sales | $68,000 | 2022-01-10 |
| Eve Davis | Engineering | $98,000 | 2020-05-05 |
A simple data grid with sortable columns and pagination.
| Alice Johnson | Engineering | $95,000 | Active | 2021-03-15 |
| Bob Smith | Marketing | $72,000 | Active | 2020-07-01 |
| Carol White | Engineering | $105,000 | Active | 2019-11-20 |
| David Brown | Sales | $68,000 | Inactive | 2022-01-10 |
| Eve Davis | Engineering | $98,000 | Active | 2020-05-05 |
Enable the toolbar for global search, column visibility, and CSV export. Column filters appear as inline popovers.
| Alice Johnson | Engineering | $95,000 | Active |
| Bob Smith | Marketing | $72,000 | Active |
| Carol White | Engineering | $105,000 | Active |
| David Brown | Sales | $68,000 | Inactive |
| Eve Davis | Engineering | $98,000 | Active |
| Frank Wilson | Marketing | $75,000 | Active |
| Grace Lee | Sales | $82,000 | Active |
| Henry Taylor | Engineering | $110,000 | Active |
| Ivy Martinez | Marketing | $71,000 | Inactive |
| Jack Anderson | Sales | $88,000 | Active |
The default (no OverlayScrollbar, no ScrollbarBelowHeader) single-table layout with a toolbar above and a fixed Height: the header band now runs the full width of the frame — including the native vertical scrollbar's gutter — instead of stopping at the last column, and in Chromium/Safari the scrollbar's own thumb starts below the header.
Toolbar customization
The toolbar is composed of independent slots — Search, Columns, Export, and Expand — each togglable with its own parameter. Defaults preserve the full toolbar; opt-out by setting any of ShowSearch, ShowColumnChooser, ShowExport, or Expandable to false. For fine-grained control over which file formats appear in the Export dropdown, use the ExportFormats flags enum. This is especially useful in Blazor WebAssembly, where PDF generators like QuestPDF throw PlatformNotSupportedException — hide PDF by setting ExportFormats="DataGridExportFormat.Csv | DataGridExportFormat.Excel".
| Alice Johnson | Engineering | $95,000 |
| Bob Smith | Marketing | $72,000 |
| Carol White | Engineering | $105,000 |
| David Brown | Sales | $68,000 |
| Eve Davis | Engineering | $98,000 |
Hide the Columns and Export buttons to give users a cleaner toolbar with just global search.
| Alice Johnson | Engineering | $95,000 |
| Bob Smith | Marketing | $72,000 |
| Carol White | Engineering | $105,000 |
| David Brown | Sales | $68,000 |
| Eve Davis | Engineering | $98,000 |
Hide the search input and Columns button to keep only the Export dropdown — useful for read-only, report-style grids.
| Alice Johnson | Engineering | $95,000 |
| Bob Smith | Marketing | $72,000 |
| Carol White | Engineering | $105,000 |
| David Brown | Sales | $68,000 |
| Eve Davis | Engineering | $98,000 |
Restrict the Export dropdown to CSV. Use this pattern in Blazor WebAssembly where PDF export libraries aren't supported.
Filtering Extensibility
Column filters pick sensible defaults from FilterType, but two extension points let you customize the experience per-column without forking the grid.
Restricting operators per column
Pass a List<FilterOperator> to the Operators parameter to limit which operators the built-in filter popover shows. The grid intersects this list with the defaults for the column's FilterType, so nonsensical combinations (e.g. StartsWith on a Number column) are filtered out automatically.
<DataGridColumnDef TItem="Employee"
Title="Name"
Field="Name"
Filterable="true"
Operators="@(new List<FilterOperator> { FilterOperator.Equals, FilterOperator.StartsWith })" />
Custom filter UI via FilterTemplate
For columns that need a bespoke experience (range sliders, relative-date pickers, tag pickers, ...), supply a FilterTemplate. The render fragment receives a DataGridFilterTemplateContext exposing the field name, the currently-applied FilterDescriptor (or null), and an Apply callback. Invoke Apply with a descriptor to commit the filter, or with null to clear it.
<DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Filterable="true">
<FilterTemplate Context="ctx">
<Slider Min="0" Max="200000" Value="_min" ValueChanged="v => _min = v" />
<Button OnClick="@(async () => await ctx.Apply(
new FilterDescriptor(ctx.Field!, FilterOperator.GreaterThanOrEqual, _min, FilterType: DataGridFilterType.Number)))">
Apply
</Button>
<Button Variant="Button.ButtonVariant.Ghost" OnClick="@(async () => await ctx.Apply(null))">
Clear
</Button>
</FilterTemplate>
</DataGridColumnDef>
What is not pluggable today (tracked for a future release):
registering brand-new FilterOperator values,
combining column filters with OR logic,
and LINQ-expression compilation for server-side evaluation.
Filters are always AND-combined across columns; within a column you get one operator + value at a time.
| Alice Johnson | Engineering | $95,000 | Active | |
| Bob Smith | Marketing | $72,000 | Active | |
| Carol White | Engineering | $105,000 | Active | |
| David Brown | Sales | $68,000 | Inactive | |
| Eve Davis | Engineering | $98,000 | Active |
Select multiple rows with checkboxes. The header checkbox toggles select all.
| Alice Johnson | Engineering | $95,000 | |
| Bob Smith | Marketing | $72,000 | |
| Carol White | Engineering | $105,000 | |
| David Brown | Sales | $68,000 | |
| Eve Davis | Engineering | $98,000 |
Click a row to select it. Useful for master-detail views.
| Alice Johnson | Engineering | $95,000 | Active | 2021-03-15 |
| Bob Smith | Marketing | $72,000 | Active | 2020-07-01 |
| Carol White | Engineering | $105,000 | Active | 2019-11-20 |
| David Brown | Sales | $68,000 | Inactive | 2022-01-10 |
| Eve Davis | Engineering | $98,000 | Active | 2020-05-05 |
Visual variants for different density needs.
| $95,000 | Active | |
| $72,000 | Active | |
| $105,000 | Active | |
| $68,000 | Inactive | |
| $98,000 | Active |
Use CellTemplate to render custom content like avatars, badges, and action buttons.
| Alice Johnson | Engineering | Active | |
| Bob Smith | Marketing | Active | |
| Carol White | Engineering | Active | |
| David Brown | Sales | Inactive | |
| Eve Davis | Engineering | Active |
Use DetailTemplate to show additional information when a row is expanded.
| Alice Johnson | Engineering | Active | $95,000 | 2021-03-15 | Priya Nair | EMEA | L5 | $9,500 | 2025-03-15 | |
| Bob Smith | Marketing | Active | $72,000 | 2020-07-01 | Diego Alvarez | AMER | L3 | $4,300 | 2025-07-01 | |
| Carol White | Engineering | Active | $105,000 | 2019-11-20 | Priya Nair | EMEA | L6 | $12,000 | 2025-11-20 | |
| David Brown | Sales | Inactive | $68,000 | 2022-01-10 | Mei Chen | APAC | L2 | $0 | 2025-01-10 | |
| Eve Davis | Engineering | Active | $98,000 | 2020-05-05 | Priya Nair | EMEA | L5 | $8,700 | 2025-05-05 |
With more columns than fit the viewport, the grid scrolls horizontally. DetailStickyToViewport (default true) keeps the expanded detail panel pinned to the visible viewport instead of stretching across the table's full scrolled width, so its content stays readable without scrolling right. Set DetailStickyToViewport=false to restore the pre-5.10.3 full-table-width behavior.
| Engineering | $95,000 | Active | 2021-03-15 | |
| Marketing | $72,000 | Active | 2020-07-01 | |
| Engineering | $105,000 | Active | 2019-11-20 | |
| Sales | $68,000 | Inactive | 2022-01-10 | |
| Engineering | $98,000 | Active | 2020-05-05 |
Pin columns to the left or right edge so they stay visible while scrolling horizontally.
| Alice Johnson | Engineering | $95,000 | Active |
| Bob Smith | Marketing | $72,000 | Active |
| Carol White | Engineering | $105,000 | Active |
| David Brown | Sales | $68,000 | Inactive |
| Eve Davis | Engineering | $98,000 | Active |
Every column here has a Width, so the grid lays out fixed: each column is exactly its width and a drag moves only that edge. Status is the FillWidth column, absorbing whatever the container has left over. Drag the column edge to resize, double-click it to auto-fit the width to the content, or focus the column header and use Ctrl+Arrow keys (Ctrl+Shift+Arrow = 1px). All columns are resizable by default; use MinWidth and MaxWidth to constrain sizing. Works with touch, and the handle mirrors to the left edge in RTL. Hook OnColumnResize to observe committed widths.
| Alice Johnson | Engineering | $95,000 | Active |
| Bob Smith | Marketing | $72,000 | Active |
| Carol White | Engineering | $105,000 | Active |
| David Brown | Sales | $68,000 | Inactive |
| Eve Davis | Engineering | $98,000 | Active |
ColumnSizing="FitWithMinimum" fills the available width exactly when there's room, and never shrinks a column below its own MinWidth — shrink the preview panel and the grid scrolls horizontally instead of squeezing Status to nothing. A resized or layout-restored width always wins over the declared one. The default (Auto) lays out table-layout: fixed at the sum of the declared widths, which does not honor CSS min-width on cells.
| Alice Johnson | Engineering | $95,000 | Active |
| Bob Smith | Marketing | $72,000 | Active |
| Carol White | Engineering | $105,000 | Active |
| David Brown | Sales | $68,000 | Inactive |
| Eve Davis | Engineering | $98,000 | Active |
AutoSizeAllColumnsAsync() fits every visible, resizable column to its content (header + rendered rows) in one pass — the bulk counterpart to the column menu's per-column "Fit to content" entry, also reachable from the Columns panel's own "Autosize all columns" button. ResetColumnWidthsAsync() puts widths back the way they were declared, without touching sort/filter/pin/order.
OverlayScrollbar="true" hides the native scrollbar and draws thin, theme-matched overlay scrollbars on top instead — no layout space reserved, appears on hover/scroll, draggable thumbs, RTL-safe. Shrink the preview or scroll horizontally to see it.
| Alice Johnson | Engineering | $95,000 | Active |
| Bob Smith | Marketing | $72,000 | Active |
| Carol White | Engineering | $105,000 | Active |
| David Brown | Sales | $68,000 | Inactive |
| Eve Davis | Engineering | $98,000 | Active |
| Frank Wilson | Marketing | $75,000 | Active |
| Grace Lee | Sales | $82,000 | Active |
| Henry Taylor | Engineering | $110,000 | Active |
| Ivy Martinez | Marketing | $71,000 | Inactive |
| Jack Anderson | Sales | $88,000 | Active |
ScrollbarBelowHeader="true" keeps the browser's own scrollbar but starts its vertical track below the header instead of running alongside it — the header renders in its own non-scrolling row, kept aligned with the body via a shared column-width source and a JS horizontal-scroll mirror. Requires every column to resolve to a fixed pixel width (Width, FitWithMinimum, or autosize); otherwise the grid falls back to the classic layout. Shrink the preview to see the vertical scrollbar start right at the header's bottom edge.
| Alice Johnson | Engineering | Active |
| Bob Smith | Marketing | Active |
| Carol White | Engineering | Active |
| David Brown | Sales | Inactive |
| Eve Davis | Engineering | Active |
Click a cell to edit it inline. Press Enter to commit, Escape to cancel.
| Alice Johnson | Engineering | Active |
| Bob Smith | Marketing | Active |
| Carol White | Engineering | Active |
| David Brown | Sales | Inactive |
| Eve Davis | Engineering | Active |
A custom EditTemplate receives a CellEditContext with Commit()/Cancel() so it can close the cell itself — without them a custom editor has no way to leave edit mode and every clicked cell stays open. Department is Editable=false, so it never opens an editor at all.
| Alice Johnson | Engineering | Active |
| Bob Smith | Marketing | Active |
| Carol White | Engineering | Active |
Edit cells inline as in Cell mode, but commits land in a per-row pending-changes buffer. A pending strip and Save all / Discard buttons appear above the grid. Optional + Add row trigger pushes new rows into the Added buffer. OnBatchSave receives Modified + Added when the user clicks Save all.
| Alice Johnson | Engineering | $95,000 | Active | |
| Bob Smith | Marketing | $72,000 | Active | |
| Carol White | Engineering | $105,000 | Active | |
| David Brown | Sales | $68,000 | Inactive | |
| Eve Davis | Engineering | $98,000 | Active |
Edit an entire row at once. Click the edit button to enter edit mode, then save or cancel.
Group by:
Expanded by default
Engineering
5 itemsSalary Avg: 102,000.00 | ||||
| Alice Johnson | Engineering | Active | $95,000 | 2021-03-15 |
| Carol White | Engineering | Active | $105,000 | 2019-11-20 |
| Eve Davis | Engineering | Active | $98,000 | 2020-05-05 |
| Henry Taylor | Engineering | Active | $110,000 | 2018-09-12 |
| Kate Thomas | Engineering | Active | $102,000 | 2021-02-14 |
Marketing
3 itemsSalary Avg: 72,666.67 | ||||
| Bob Smith | Marketing | Active | $72,000 | 2020-07-01 |
| Frank Wilson | Marketing | Active | $75,000 | 2021-08-22 |
| Ivy Martinez | Marketing | Inactive | $71,000 | 2022-04-18 |
Sales
4 itemsSalary Avg: 79,250.00 | ||||
| David Brown | Sales | Inactive | $68,000 | 2022-01-10 |
| Grace Lee | Sales | Active | $82,000 | 2019-06-30 |
| Jack Anderson | Sales | Active | $88,000 | 2020-12-03 |
| Leo Jackson | Sales | Active | $79,000 | 2019-10-08 |
Group rows by a field. Click the group header to expand or collapse each group. Set GroupsExpandedByDefault to control the initial state.
Group panel:
Levels:
Engineering
5 itemsSalary Avg: 102,000.00 | ||||
| Alice Johnson | Engineering | Active | $95,000 | 2021-03-15 |
| Carol White | Engineering | Active | $105,000 | 2019-11-20 |
| Eve Davis | Engineering | Active | $98,000 | 2020-05-05 |
| Henry Taylor | Engineering | Active | $110,000 | 2018-09-12 |
| Kate Thomas | Engineering | Active | $102,000 | 2021-02-14 |
Marketing
3 itemsSalary Avg: 72,666.67 | ||||
| Bob Smith | Marketing | Active | $72,000 | 2020-07-01 |
| Frank Wilson | Marketing | Active | $75,000 | 2021-08-22 |
| Ivy Martinez | Marketing | Inactive | $71,000 | 2022-04-18 |
Sales
4 itemsSalary Avg: 79,250.00 | ||||
| David Brown | Sales | Inactive | $68,000 | 2022-01-10 |
| Grace Lee | Sales | Active | $82,000 | 2019-06-30 |
| Jack Anderson | Sales | Active | $88,000 | 2020-12-03 |
| Leo Jackson | Sales | Active | $79,000 | 2019-10-08 |
Set ShowGroupPanel="true" to render the chip-strip panel above the grid. Flag columns Groupable="true" to make them addable. Use GroupByFields for ordered multi-level grouping — each level adds a nested row with its own aggregate.
| Alice Johnson | Engineering | $95,000 | Active |
| Bob Smith | Marketing | $72,000 | Active |
| Carol White | Engineering | $105,000 | Active |
| David Brown | Sales | $68,000 | Inactive |
| Eve Davis | Engineering | $98,000 | Active |
Grab a column header's grip, or drag from the title itself (a click still sorts; a small movement or a touch/pen press-and-hold arms the drag) — the column lifts slightly and translates in place with your pointer, no ghost image, while neighbouring columns live-shift aside to preview the final order and settle smoothly on release. One unified pointer-based path drives mouse, touch, and pen. Pinned columns can only be reordered within their own pinned/unpinned region. Alt+Arrow on a focused header, or the Columns panel, offer keyboard-accessible reorder paths. Enable with the Reorderable property on the grid.
| Alice Johnson | Engineering | $95,000 | Active |
| Bob Smith | Marketing | $72,000 | Active |
| Carol White | Engineering | $105,000 | Active |
| David Brown | Sales | $68,000 | Inactive |
| Eve Davis | Engineering | $98,000 | Active |
Apply dynamic CSS classes or inline styles to rows based on data. Useful for highlighting specific records.
| Alice Johnson | Engineering | $95,000 | Active |
| Bob Smith | Marketing | $72,000 | Active |
| Carol White | Engineering | $105,000 | Active |
| David Brown | Sales | $68,000 | Inactive |
| Eve Davis | Engineering | $98,000 | Active |
Save and restore column widths, order, visibility, sorts, filters, and the group panel (runtime group fields) to localStorage. The layout survives page refreshes. Use the Layouts dropdown in the toolbar to save named layouts under Personal, Global, or System Default scopes.
| Alice Johnson | Engineering | $95,000 | Active |
| Bob Smith | Marketing | $72,000 | Active |
| Carol White | Engineering | $105,000 | Active |
| David Brown | Sales | $68,000 | Inactive |
| Eve Davis | Engineering | $98,000 | Active |
Pass GlobalLayouts to surface pre-built layouts in the Layouts dropdown. Users can also save their own Personal layouts which are stored in localStorage. The panel shows Personal / Global / System Default scopes.
Saving and loading layouts
For full control over where layouts are stored (your own database, a REST API, cookies, etc.), use the programmatic JSON API. Capture a grid reference with @ref, then call ExportLayout() to get a JSON string and ApplyLayoutJsonAsync(json) to round-trip it later. The snapshot covers column order, widths, visibility, pin state, sorts, filters, global search, current page, page size, and the group panel (multi-level group fields) — both single and multi-level grouping levels round-trip.
<DataGrid @ref="_grid" TItem="Order" Items="_orders">
<DataGridColumnDef TItem="Order" Field="Id" Title="ID" />
<DataGridColumnDef TItem="Order" Field="Customer" Title="Customer" />
<DataGridColumnDef TItem="Order" Field="Total" Title="Total" Format="C2" />
</DataGrid>
<Button OnClick="SaveLayout">Save layout</Button>
<Button OnClick="LoadLayout">Load layout</Button>
@code {
private DataGrid<Order>? _grid;
[Inject] MyAppDbContext Db { get; set; } = default!;
private async Task SaveLayout()
{
if (_grid is null) return;
var json = _grid.ExportLayout();
await Db.UserLayouts.AddAsync(new UserLayout {
UserId = CurrentUserId,
GridKey = "orders",
Json = json,
SavedAt = DateTime.UtcNow
});
await Db.SaveChangesAsync();
}
private async Task LoadLayout()
{
if (_grid is null) return;
var latest = await Db.UserLayouts
.Where(l => l.UserId == CurrentUserId && l.GridKey == "orders")
.OrderByDescending(l => l.SavedAt)
.FirstOrDefaultAsync();
if (latest is not null)
await _grid.ApplyLayoutJsonAsync(latest.Json);
}
}
The JSON shape is the public DataGridLayoutSnapshot record. The snapshot is versioned (Version: 2 today) so future breaking changes can be migrated; v1 payloads still load — the legacy single-level GroupBy field is honoured as a fallback when GroupByFields is absent. Columns referenced in a snapshot that no longer exist on the grid are silently ignored; columns present on the grid but missing from the snapshot are appended at the end.
Reorder columns
Set Reorderable="true" on the grid to enable column reordering. Users can reorder columns two ways:
- Header drag: grab a column header's grip, or drag from anywhere in the header's title/sort area — a small movement threshold (mouse) keeps a plain click free to sort, while a brief press-and-hold arms the drag on touch/pen so a quick tap still sorts and a swipe still scrolls the page. The dragged header lifts slightly (a subtle scale + shadow) and follows your pointer in place — no ghost image, no drop-indicator line. Neighbouring columns live-shift aside as you cross their midpoint, continuously previewing the final order; release settles the column into its slot with a short animation, and the container auto-scrolls if you hold near its edge. Attempting to drag a locked (non-reorderable) column instead gives it a small nudge to signal it can't move. One unified pointer-based path drives mouse, touch, and pen — native HTML5 drag-and-drop is not used at all, for reordering OR for drag-to-group (see below): the same drag that reorders a column within the header row also groups it if you release over the group panel instead, which is what makes drag-to-group work on touch too (native HTML5 DnD never fired there).
- Keyboard: focus a column header (roving tab stop, like the rest of the grid) and press Alt+← / Alt+→ to move it one slot at a time within its pin partition. The move is announced to screen readers via a polite live region.
- Columns panel: open the Toggle Columns popover from the toolbar and drag the column rows to reorder them. The list is keyboard-reorderable, making it another pointer-free path for users who can't drag the header.
Reordering is constrained to a column's pin partition: a left-pinned column can only move among the left-pinned columns, an unpinned column among the unpinned columns, and so on — a cross-boundary drop is rejected. To lock an individual column's position entirely (for example, a required identifier), set Reorderable="false" on that DataGridColumnDef; both the header drag and the Columns panel respect the flag.
<DataGrid TItem="Order" Items="_orders" Reorderable="true">
<DataGridColumnDef TItem="Order" Field="Id" Title="ID" Reorderable="false" />
<DataGridColumnDef TItem="Order" Field="Customer" Title="Customer" />
<DataGridColumnDef TItem="Order" Field="Total" Title="Total" Format="C2" />
</DataGrid>| Alice Johnson | Engineering | $95,000 | Active |
| Bob Smith | Marketing | $72,000 | Active |
| Carol White | Engineering | $105,000 | Active |
| David Brown | Sales | $68,000 | Inactive |
| Eve Davis | Engineering | $98,000 | Active |
The ToolbarContent component slots your custom buttons next to the toolbar's filter chips. Use the Class attribute to tweak the wrapper (gap, flex-wrap, etc.).
| Alice Johnson | Engineering | $95,000 | Active |
| Bob Smith | Marketing | $72,000 | Active |
| Carol White | Engineering | $105,000 | Active |
| David Brown | Sales | $68,000 | Inactive |
| Eve Davis | Engineering | $98,000 | Active |
Mix your own buttons with built-in tools. When you provide ToolbarContent, the default tool stack is suppressed — you pick exactly which tools to include and in what order. Each tool picks up the grid's state via a cascading context — no props needed.
Grouping in ServerMode
GroupBy, GroupByFields, and the runtime group panel
(ShowGroupPanel="true") all work in ServerMode — including
drag-to-group, add-level dropdown, per-chip remove, and clear-all. Grouping is
applied client-side to whatever rows the server returned for the current page. For grouping
across the whole dataset, either set ShowPagination="false" (server returns all
rows) or have the server pre-aggregate groups itself.
For large datasets, use ServerMode to handle sorting, filtering, and pagination on the server. Sorting, filtering, search, and pagination all trigger OnServerRequest. Loading state and request cancellation are managed automatically.
Toggle the simulate error button to trigger a server failure. OnError receives the exception so you can display a message outside the grid.
| Engineering | Alice Johnson | $95,000 | 2021-03-15 |
| Marketing | Bob Smith | $72,000 | 2020-07-01 |
| Engineering | Carol White | $105,000 | 2019-11-20 |
| Sales | David Brown | $68,000 | 2022-01-10 |
| Engineering | Eve Davis | $98,000 | 2020-05-05 |
Click multiple column headers to sort by multiple fields. The grid applies sorts in the order they were added. Click a column again to cycle through ascending, descending, and unsorted.
Virtualization & large datasets
Lumeo's DataGrid supports two distinct strategies for large datasets — pick the one that matches where your data lives:
- Client virtualization — items are already in memory. Set
ShowPagination="false"and pass all rows; once the row count exceedsVirtualizeThreshold(default 500), Blazor's built-in<Virtualize>renders only the rows in the viewport. TuneVirtualItemSizeif your rows are denser/taller than the 41 px default — an inaccurate value makes the scrollbar drift. Paged grids never trigger this path because they only renderPageSizerows at a time. - Server-side paging — items live on a backend. Set
ServerMode="true", handleOnServerRequest(Skip/Take/Sort/Filter), and reportTotalCount. The grid shows numbered pages and thePageSizeOptionsselector. Use this when you have many pages of data but only want to fetch one page at a time.
- Virtualised server mode (infinite scroll) — the third mode. Set
Virtualized="true"+ provide anOnRangeRequestcallback. The grid drops pagination, renders one tall scrollable body, and fetches a sliding window of rows from your backend as the user scrolls. Sort, column filters and global search all trigger an automatic refetch viaVirtualize.RefreshDataAsync()— no manual wiring needed. Use this when you have hundreds of thousands of rows and want a grid that feels native, not paged.
Pass a large in-memory list with ShowPagination=false and a fixed Height. Once the row count exceeds VirtualizeThreshold (500) the grid renders only the rows in the viewport via Blazor's <Virtualize>, so scrolling stays smooth with tens of thousands of rows and the DOM never holds more than a small window.
Set Virtualized=true + provide OnRangeRequest. The grid fetches a sliding window of rows from your backend as the user scrolls, hides pagination, and re-fetches automatically when sort/filter/search change. Demo fakes a 50000-row backend with an in-memory list.
| Alice Johnson | Engineering | $95,000 | Active |
| Bob Smith | Marketing | $72,000 | Active |
| Carol White | Engineering | $105,000 | Active |
| David Brown | Sales | $68,000 | Inactive |
| Eve Davis | Engineering | $98,000 | Active |
| Frank Wilson | Marketing | $75,000 | Active |
| Grace Lee | Sales | $82,000 | Active |
| Henry Taylor | Engineering | $110,000 | Active |
| Ivy Martinez | Marketing | $71,000 | Inactive |
| Jack Anderson | Sales | $88,000 | Active |
Pass a custom array to the rows-per-page selector — useful for grids with many rows where 100 isn't enough. Empty array hides the selector.
| Alice Johnson | Engineering | $95,000 | Active | 2021-03-15 |
| Bob Smith | Marketing | $72,000 | Active | 2020-07-01 |
| Carol White | Engineering | $105,000 | Active | 2019-11-20 |
| David Brown | Sales | $68,000 | Inactive | 2022-01-10 |
| Eve Davis | Engineering | $98,000 | Active | 2020-05-05 |
| Frank Wilson | Marketing | $75,000 | Active | 2021-08-22 |
| Grace Lee | Sales | $82,000 | Active | 2019-06-30 |
| Henry Taylor | Engineering | $110,000 | Active | 2018-09-12 |
| Ivy Martinez | Marketing | $71,000 | Inactive | 2022-04-18 |
| Jack Anderson | Sales | $88,000 | Active | 2020-12-03 |
| Kate Thomas | Engineering | $102,000 | Active | 2021-02-14 |
| Leo Jackson | Sales | $79,000 | Active | 2019-10-08 |
Set a fixed height on the grid to enable vertical scrolling for long lists.
Show skeleton rows while data is loading.
No employees found. Try adjusting your filters. | |
Custom content when the grid has no data.
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
| David Brown | Sales |
| Eve Davis | Engineering |
EmptyTemplate receives HasActiveFilters so the empty state can tell 'no rows because a filter excluded everything' apart from 'the source really has none' — with a Clear filters action to reset in one click. Open the Name column's filter and type a value that matches nothing.
Showing 12 of 12 employees
| Alice Johnson | Engineering |
| Bob Smith | Marketing |
| Carol White | Engineering |
| David Brown | Sales |
| Eve Davis | Engineering |
FilteredRowCount/TotalRowCount and OnRowCountChanged give a host the same count the grid's own pagination summary uses, so a custom 'Showing n of m' header follows filters and search without a second, independently-computed count. Type in the search box or filter the Name column to see it update.
| Alice Johnson | Engineering | $95,000 | Active | 2021-03-15 | |
| Bob Smith | Marketing | $72,000 | Active | 2020-07-01 | |
| Carol White | Engineering | $105,000 | Active | 2019-11-20 | |
| David Brown | Sales | $68,000 | Inactive | 2022-01-10 | |
| Eve Davis | Engineering | $98,000 | Active | 2020-05-05 |
Enable the Expandable feature to add a fullscreen icon in the toolbar. Click it to pop the grid into a fullscreen modal overlay. Selection, filters, sorts, and pagination all survive the transition. Press Esc or click the close button to exit.
AL | Engineering | $95,000 | Active | 2021-03-15 | |
BO | Marketing | $72,000 | Active | 2020-07-01 | |
CA | Engineering | $105,000 | Active | 2019-11-20 | |
DA | Sales | $68,000 | Inactive | 2022-01-10 | |
EV | Engineering | $98,000 | Active | 2020-05-05 |
All features combined: toolbar, search, filtering, sorting, selection, column visibility, export, conditional styling, and aggregation.
API Reference
DataGrid<TItem>
| Property | Type | Default | Description |
|---|---|---|---|
| Items | IEnumerable<TItem>? | -- | Data source for the grid. |
| Columns | List<DataGridColumn<TItem>>? | -- | Programmatic column definitions (alternative to DataGridColumnDef). |
| PageSize | int | 10 | Number of rows per page. |
| ShowPagination | bool | true | Show pagination controls. |
| ShowToolbar | bool | false | Show toolbar with search, export, and column visibility. |
| ShowSearch | bool | true | Show the global search box in the toolbar. Requires ShowToolbar. |
| ShowColumnChooser | bool | true | Show the Columns (visibility/reorder) button in the toolbar. |
| ShowExport | bool | true | Show the Export button and its format dropdown. Set false to hide export entirely; for per-format control see ExportFormats. |
| ShowLayouts | bool | true | Shows the saved-layouts menu when EnableLayoutPersistence is on. Off, persistence still runs; only the menu goes, so a host page can render DataGridToolbarLayouts elsewhere or drop it. |
| Grid | DataGrid<TItem>? | null | On the toolbar tool components (DataGridToolbarColumns, Export, CopySelected, Layouts, Fullscreen): the grid a tool rendered outside the grid's own toolbar belongs to. Inside the toolbar the cascaded context wins and this is not needed. |
| ExportFormats | DataGridExportFormat | All | Flags enum controlling which formats appear in the Export dropdown. Combine with bitwise OR, e.g. DataGridExportFormat.Csv | DataGridExportFormat.Excel. When no flags are set the Export button is hidden. |
| SelectionMode | DataGridSelectionMode | None | Row selection: None, Single, or Multiple. |
| EditMode | DataGridEditMode | None | Inline editing: None, Cell, Row, or Batch (buffered). |
| OnBatchSave | EventCallback<DataGridBatchSaveEventArgs<TItem>> | -- | Fires when the user clicks "Save all" in EditMode=Batch. Carries Modified + Added rows. The grid's pending-changes buffer is cleared automatically on a clean return. |
| ShowAddRow | bool | false | Renders a "+ Add row" trigger below the body in batch mode. Requires NewItemFactory. |
| NewItemFactory | Func<TItem>? | -- | Factory used by the "+ Add row" trigger. Each click produces a fresh row that lives in the batch buffer until OnBatchSave commits it. |
| HasPendingChanges | bool (get) | -- | True when the batch buffer contains a buffered edit or a new row. Bind via @ref for warn-on-navigate workflows. |
| BatchSaveAllText | string? | "Save all" | Batch-mode toolbar "Save all" button label. Override for localization. |
| BatchDiscardText | string? | "Discard" | Batch-mode toolbar "Discard" button label. Override for localization. |
| BatchAddRowText | string? | "Add row" | Batch-mode "+ Add row" trigger label. Override for localization. |
| ColumnVirtualize | bool | false | Simplified column virtualization — caps the number of horizontally-rendered data columns at MaxVisibleColumns. Pinned columns always render. Useful for very wide grids. |
| MaxVisibleColumns | int | 30 | Column cap used when ColumnVirtualize=true. |
| Striped | bool | false | Alternate row background colors. |
| Bordered | bool | false | Show cell borders. |
| OverlayScrollbar | bool | false | Hides the native scrollbar and draws thin, theme-matched overlay scrollbars (both axes, draggable, appear on hover/scroll, RTL-safe, prefers-reduced-motion-aware) instead — reserving no layout space. Native wheel/trackpad/touch/keyboard scrolling is unaffected. |
| ScrollbarBelowHeader | bool | false | Keeps the native scrollbar but starts its vertical track below the header (a split header/body layout, kept aligned via a JS scroll mirror). Requires every column to resolve to a fixed pixel width — falls back to the classic single-table layout otherwise. Ignored when OverlayScrollbar is also on. |
| Compact | bool | false | Smaller row height and text. |
| Hoverable | bool | true | Highlight rows on hover. |
| Height | string? | -- | Fixed height with scrollable body (e.g., "400px"). |
| Reorderable | bool | false | Enable drag-and-drop column reordering. |
| ColumnSizing | DataGridColumnSizing | Auto | Auto is the historic table-layout: fixed behavior, where a FillWidth column can be squeezed to 0px once enough other columns are visible (fixed layout ignores CSS min-width on cells). FitWithMinimum measures the grid's scroll container and computes an exact pixel width per column (a resized or LayoutStorageKey-restored width always wins, clamped to MinWidth/MaxWidth), then renders table-layout: fixed at those widths — columns fill the container exactly when there's room, never shrink below their own MinWidth, and long content truncates with an ellipsis instead of forcing the column wider; once the visible columns' combined floor exceeds the container, the grid scrolls horizontally instead. |
| ColumnMenu | bool | false | A header click opens the column menu (sort, fit to content, pin, move) instead of toggling the sort, as the shadcn/ReUI data grid does. |
| OnHeaderClick | EventCallback<ColumnHeaderClickEventArgs> | — | Fires before a header click's default response; set PreventDefault on the args to handle the click yourself. |
| ColumnMenuContent | RenderFragment<DataGridColumn<TItem>>? | null | Entries appended to the column menu after a separator; receives the column, so the host's own commands sit next to sort, fit, pin and move. |
| ShowReorderHandle | bool | true | Renders the reorder grip in each header cell. false keeps the header clean; dragging the header and the column menu's move entries still reorder. |
| ShowPinButton | bool | true | Renders the pin button in each pinnable header cell. false leaves pinning to the column menu and the column chooser. |
| SortIconTemplate | RenderFragment<SortDirection>? | null | Your own sort glyphs: receives None, Ascending or Descending and renders the icon for that state. |
| FooterTemplate | RenderFragment<DataGridFooterContext<TItem>>? | null | A footer strip below the rows with RowCount, TotalCount and the displayed Items, rendered with or without column aggregates. |
| RowReorderable | bool | false | Enable row reordering via a dedicated pointer-driven drag handle (mouse, touch, and pen — dragging is initiated from the handle only, since the row itself is a click-to-select surface). Live sibling shift previews the drop position as you drag; Escape or releasing off-grid cancels. Only active for flat, non-virtualized grids — grouped, tree-grid, and virtualized bodies show the handle but leave it inert (no stable row↔index mapping to drag against). |
| RowClass | Func<TItem, string>? | -- | Function returning CSS class(es) for each row. |
| RowStyle | Func<TItem, string>? | -- | Function returning inline style for each row. |
| PageSizeOptions | int[] | [10, 25, 50, 100] | Page-size options shown in the rows-per-page selector. Pass new[] { 10, 50, 100, 200, 300 } for larger datasets, or an empty array to hide the selector entirely. |
| VirtualizeThreshold | int | 500 | Row count above which Blazor's <Virtualize> kicks in. Only relevant when ShowPagination=false — paged grids never reach the threshold and never virtualize. |
| VirtualItemSize | float | 41 | Estimated row height in CSS pixels. Tune if rows are denser/taller than the default. |
| VirtualOverscanCount | int | 3 | Extra rows rendered above/below the viewport to hide pop-in during fast scrolling. |
| Virtualized | bool | false | Enables ItemsProvider-based infinite-scroll mode. Hides pagination and fetches a sliding row window via OnRangeRequest as the user scrolls. Sort/filter/search refresh the virtualizer automatically. |
| OnRangeRequest | Func<DataGridRangeRequest, ValueTask<DataGridRangeResponse<TItem>>>? | -- | Range-fetch callback for virtualised server mode. Receives StartIndex, Count, current Sort/Filter/Search context; must return the slice plus current TotalCount. |
| ServerMode | bool | false | Enable server-side sorting, filtering, and pagination. |
| TotalCount | int | 0 | Total item count for server-side pagination. |
| IsLoading | bool | false | Show loading skeleton overlay. |
| Culture | CultureInfo? | -- | Culture used for cell value formatting (dates/numbers) and exports. Falls back to CultureInfo.CurrentCulture when null — useful when your app sets a different culture than the UI locale (e.g. force de-DE formatting in an English UI). |
| EnableLayoutPersistence | bool | false | Auto-save/restore column layout to localStorage. Enables the Layouts dropdown in the toolbar. |
| LayoutStorageKey | string? | auto | Custom localStorage key for layout persistence. |
| SavedLayout | DataGridLayout? | -- | Externally provided layout to apply on load. |
| GlobalLayouts | List<DataGridNamedLayout>? | -- | Pre-built named layouts shown in the Global scope of the Layouts panel. |
| OnSaveNamedLayout | EventCallback<DataGridNamedLayout> | -- | Fires when the user saves a named layout. Personal layouts are also persisted to localStorage automatically. |
| OnDeleteNamedLayout | EventCallback<string> | -- | Fires when the user deletes a named layout. Receives the layout Id. |
| DetailTemplate | RenderFragment<TItem>? | -- | Template for expandable row details. |
| DetailStickyToViewport | bool | true | Pins the expanded detail panel to the grid's visible scroll viewport instead of the table's full, possibly horizontally-scrolled, width. Set false to restore the pre-5.10.3 full-table-width behavior. |
| EmptyContent | RenderFragment? | -- | Custom content when no data. Superseded by EmptyTemplate when that is also set. |
| EmptyTemplate | RenderFragment<DataGridEmptyContext>? | -- | Typed alternative to EmptyContent: receives HasActiveFilters (true when the grid is empty because a filter/search excluded every row, as opposed to the source having none) and a ClearFilters callback. Takes precedence over EmptyContent. |
| <ToolbarContent> | component | -- | Nested component placed inside ChildContent. Takes Class for wrapper styling and ChildContent for custom buttons / built-in toolbar tools. |
| RowContextMenu | RenderFragment<TItem>? | -- | Right-click context menu template for rows. |
| OnRowClick | EventCallback<TItem> | -- | Callback when a row is clicked. |
| SelectOnRowClick | bool | true | Whether a click anywhere in a row toggles its selection. false keeps selection on the checkbox column and leaves the row click to OnRowClick, e.g. to open a detail view. |
| OnRowDoubleClick | EventCallback<TItem> | -- | Callback when a row is double-clicked. |
| OnCellEdit | EventCallback<CellEditEventArgs<TItem>> | -- | Callback when a cell edit is committed. |
| OnRowEdit | EventCallback<RowEditEventArgs<TItem>> | -- | Callback when a row edit is committed with all changed values. |
| OnColumnReorder | EventCallback<ColumnReorderEventArgs> | -- | Callback when columns are reordered (header drag, touch, or the Columns panel). |
| OnColumnResize | EventCallback<ColumnResizeEventArgs> | -- | Callback when a column is resized (pointer drag, Arrow-key nudge, or double-click auto-fit). Args carry the column id, committed width, and an AutoFit flag. |
| OnRowReorder | EventCallback<RowReorderEventArgs<TItem>> | -- | Callback when a row is reordered via the drag handle (mouse, touch, or pen). |
| OnLayoutSave | EventCallback<DataGridLayout> | -- | Fires when the layout is saved (for external persistence). |
| SelectedItems | IReadOnlyList<TItem>? | -- | Two-way bindable selected items collection. |
| SelectedItemsChanged | EventCallback<IReadOnlyList<TItem>> | -- | Callback when selection changes. |
| SelectionKeySelector | Func<TItem, object?>? | -- | Optional key projector that identifies items across re-fetches. In server mode where Items is a fresh instance per page-load, the default reference-based stale-selection cleanup wipes selections that survived a page. When set, the grid compares keys instead of references, so a selection on page 1 stays applied after a page-2 fetch returns a fresh wrapper for the same logical row. Returning null for any item falls back to reference equality for that row. |
| OnServerRequest | EventCallback<DataGridServerRequest> | -- | Server-side data request callback with sorts, filters, page info. |
| OnError | EventCallback<Exception> | -- | Fires when a server request throws an exception. |
| GroupBy | string? | -- | Single-level group field. Works in both client and ServerMode; use GroupByFields for multi-level. |
| Expandable | bool | false | Adds an expand icon to the toolbar for toggling fullscreen mode. |
| IsExpanded | bool | false | Two-way bindable fullscreen state. Set from code to programmatically expand/collapse. |
| IsExpandedChanged | EventCallback<bool> | -- | Fires whenever the fullscreen state toggles. |
| FullscreenTitle | string? | -- | Optional title shown in the fullscreen header bar. Defaults to "Data Grid". |
| Class | string? | -- | Additional CSS classes for the root element. |
| ExportLayout() | string | method | Serializes the current layout (column order, widths, visibility, pin state, sorts, filters, global search, page, page size, group-by) to a JSON string. Call via @ref. |
| ApplyLayoutJsonAsync(json) | Task | method | Deserializes a JSON layout produced by ExportLayout and applies it. Throws JsonException on malformed input; silently ignores columns that no longer exist. |
| ApplyLayoutAsync(layout) | Task | method | Applies a DataGridLayout object directly (column order, widths, visibility, pin, sorts, filters, page size, global search, group-by), the strongly-typed counterpart to ApplyLayoutJsonAsync. Call via @ref. |
| ResetLayoutAsync() | Task | method | Restores the layout captured at first render (or, absent a snapshot, default column order/visibility/width, cleared sorts/filters/search, page size 10), and clears any persisted layout when EnableLayoutPersistence is on. |
| AutoSizeColumnAsync(field) | Task | method | Auto-sizes one column (by Field) to fit its content — header + the currently rendered body cells, so under virtualization just the rendered window — clamped to MinWidth/MaxWidth. Commits through the same width state a manual resize does. The column menu's "Fit to content" entry is the equivalent UI action for a single column. |
| AutoSizeAllColumnsAsync() | Task | method | Auto-sizes every currently visible, resizable column in one pass — the bulk counterpart to AutoSizeColumnAsync. Also reachable from the Columns panel's "Autosize all columns" button. |
| ResetColumnWidthsAsync() | Task | method | Resets every column's width back to its declared width (the same baseline ResetLayoutAsync restores), without touching sort, filter, visibility, pin or column order — narrower than ResetLayoutAsync, which resets the whole layout and clears any persisted one. |
| GetPersonalLayoutsAsync() | Task<List<DataGridNamedLayout>> | method | Returns the personal-scope named layouts persisted for this grid's storage key. |
| MoveRow(oldIndex, newIndex) | void | method | Programmatically moves a row from oldIndex to newIndex, the same commit path as a completed drag via RowReorderable; raises OnRowReorder when bound. |
| SetColumnVisibility(columnKey, visible) | void | method | Shows or hides a column identified by its Field (or Id when Field is unset) without going through the Columns panel. Refreshes the visible-columns cache and, in client mode, reprocesses data. |
| UpdateColumnWidth(columnId, width, autoFit) | void | method | Commits a new pixel width for the column identified by columnId, clamped to its Min/Max. Persists via layout auto-save and raises OnColumnResize. autoFit defaults to false. |
| ToggleExpanded() | Task | method | Toggles fullscreen expand mode (the same action as the toolbar's expand icon). No-op when Expandable is false. Preserves selection, filters, sorts, and pagination across the transition. |
| RefreshVirtualizedAsync() | Task | method | Forces the virtualized server view (Virtualized="true") to discard its window cache and refetch from the current scroll position. Called automatically on sort/filter/search changes; call this manually after an external data mutation. |
| SelectRangeAsync(int from, int to) | Task | method | Selects every row from one index to another inclusive, in the current sort, filter and search; what a Shift-click does. In virtualized server mode the rows are fetched through OnRangeRequest, so the range can reach beyond what is loaded. |
| PendingModifiedItems | IReadOnlyCollection<TItem> (get) | -- | The items currently buffered as edited in EditMode=Batch, before OnBatchSave commits them. Bind via @ref. |
| PendingAddedItems | IReadOnlyList<TItem> (get) | -- | The rows created via the "+ Add row" trigger that are still buffered in EditMode=Batch, before OnBatchSave commits them. Bind via @ref. |
| GroupsExpandedByDefault | bool | true | Initial expand state for group headers when grouping is active. |
| HasActiveFilters | bool (get) | -- | True when at least one column filter is applied or the global search box has text. Also available inside EmptyTemplate via its DataGridEmptyContext. |
| ClearFiltersAsync() | Task | method | Clears every active column filter and the global search box in one call, then reprocesses/refetches. The action behind EmptyTemplate's ClearFilters callback; also callable directly via @ref. |
| FilteredRowCount | int (get) | -- | Rows that survive filters/search (and, client-side, grouping/row visibility), before paging — the same count the built-in pagination summary and filtered-empty state use. In ServerMode this is the last TotalCount from the server. Also available on DataGridEmptyContext and the toolbar context. |
| TotalRowCount | int (get) | -- | Unfiltered source row count: Items's count in client mode, or the server total in ServerMode (where no separate unfiltered count is knowable). Also available on DataGridEmptyContext and the toolbar context. |
| OnRowCountChanged | EventCallback<DataGridRowCountChanged> | -- | Fires once whenever FilteredRowCount or TotalRowCount actually changes, carrying both as a (Filtered, Total) record — render a "Showing n of m" summary without polling. |
ToolbarContent<TItem>
Place inside <ChildContent> of a DataGrid. Accepts custom buttons or built-in toolbar tool components (see below) that automatically pick up the grid's state via a cascading context.
| Property | Type | Default | Description |
|---|---|---|---|
| Class | string? | -- | CSS classes applied to the wrapper around ChildContent + the toolbar's filter chips. |
| ChildContent | RenderFragment? | -- | Your custom buttons and any of the built-in toolbar tool components. |
Toolbar Tool Components
Built-in tool components you can drop inside <ToolbarContent>. Each takes only TItem and reads everything else (selection, columns, export formats, layout config, Interop, Localizer) from a cascading DataGridToolbarContext<TItem>.
| Component | Renders | Visible when |
|---|---|---|
| <DataGridToolbarFullscreen> | Fullscreen toggle icon button. | Expandable="true" on the grid. |
| <DataGridToolbarCopySelected> | Copy-to-clipboard button (tab-separated, selected rows). | At least one row is selected. |
| <DataGridToolbarColumns> | Column visibility + reorder dropdown. | Always (gate on the grid via ShowColumnChooser). |
| <DataGridToolbarExport> | Export dropdown (CSV / Excel / PDF per ExportFormats). | ExportFormats != None. |
| <DataGridToolbarLayouts> | Saved-layouts panel (personal + global). | EnableLayoutPersistence="true". |
Omit <ToolbarContent> and all five tools auto-render in the default order (Fullscreen, CopySelected, Columns, Export, Layouts) gated by their Show* flags on the DataGrid. Provide <ToolbarContent> and the default stack is suppressed entirely — you pick which tools to include and where, no duplicates.
DataGridColumnDef<TItem>
| Property | Type | Default | Description |
|---|---|---|---|
| Title | string? | -- | Column header text. |
| Field | string? | -- | Property name to bind to on TItem. |
| FieldSelector | Func<TItem, object?>? | -- | Custom value accessor (alternative to Field). |
| Width | double? | -- | Column width in pixels. |
| MinWidth | double? | -- | Minimum column width in pixels during resize. |
| FillWidth | bool | false | The column that absorbs the grid's free space (ReUI's meta.fillWidth): with every column sized, the others keep their exact width when one is resized and the grid still spans its container. |
| DefaultSort | SortDirection | None | The sort the grid starts with for this column. A saved layout still wins; in server mode the initial request carries it. |
| MaxWidth | double? | -- | Maximum column width in pixels during resize. |
| Sortable | bool | false | Enable sorting for this column. |
| Filterable | bool | false | Enable filtering for this column. |
| Resizable | bool | true | Allow column resizing via drag handle. |
| Pin | PinDirection | None | Pin column: None, Left, or Right. |
| Pinnable | bool | false | Whether column can be pinned/unpinned via the UI. |
| Groupable | bool | false | Whether column can be used for grouping. |
| Reorderable | bool | true | Whether this column can be reordered (via header drag-and-drop or the Toggle Columns menu arrows). Set to false to pin an individual column's position even when the grid-level Reorderable is true. |
| FilterType | DataGridFilterType | Text | Filter input type: Text, Number, Date, Select, Boolean. |
| FilterOptions | List<FilterOption>? | -- | Options for Select filter type (label + value pairs). |
| Operators | List<FilterOperator>? | -- | Optional whitelist of operators to show in the filter UI for this column. When null, defaults for the FilterType are used. |
| FilterTemplate | RenderFragment<DataGridFilterTemplateContext>? | -- | Custom filter UI for this column. Replaces the built-in operator + value inputs when provided. Invoke the context's Apply callback with a FilterDescriptor to commit, or null to clear. |
| Format | string? | -- | Format string (e.g., "C0", "yyyy-MM-dd", "N2"). |
| Aggregate | AggregateType | None | Footer aggregate: None, Sum, Average, Count, Min, Max. |
| FooterFormat | string? | -- | Optional .NET format string for the footer aggregate strip — overrides Format for that one value when set. |
| CustomSort | Comparison<object?>? | -- | Custom sort comparison function for this column. |
| CellClass | Func<TItem, string>? | -- | Function returning dynamic CSS class(es) for each cell. |
| HeaderCssClass | string? | -- | Custom CSS class for the header cell. |
| CellTemplate | RenderFragment<TItem>? | -- | Custom render template for cell content. |
| HeaderTemplate | RenderFragment? | -- | Custom render template for header. |
| EditTemplate | RenderFragment<CellEditContext<TItem>>? | -- | Custom template for inline editing input. Call the context's Commit()/Cancel() to close the cell — without it, in EditMode.Cell/Batch, the cell has no built-in way to leave edit mode. |
| Editable | bool | true | Whether this column's cells open an editor in EditMode.Cell/Batch. Set false to lock a single column (e.g. a computed or id column) while the rest of the row stays editable. |
| Visible | bool | true | Whether the column currently renders. Toggled by the Columns panel; bind with @bind-Visible to control it externally. |
| VisibleChanged | EventCallback<bool> | -- | Fires when Visible changes, e.g. via the Columns panel. |
DataGridColumnGroup<TItem>
Groups a run of DataGridColumnDef children under a shared spanning header cell (see "Grouped column headers" above).
| Property | Type | Default | Description |
|---|---|---|---|
| Label | string | "" | Text shown in the spanning group header cell. |
| HeaderClass | string? | -- | Additional CSS class(es) for the group header cell. |
Event Args
| Type | Properties | Description |
|---|---|---|
| CellEditEventArgs<TItem> | Item, Field, OldValue, NewValue | Fired when a cell edit is committed. |
| CellEditContext<TItem> | Item, Column, Value, ValueChanged, Commit(), Cancel() | Passed to a column's EditTemplate. Commit() writes Value and closes the cell (same path as the built-in editor's blur/Enter); Cancel() discards and closes (Escape). Both are Func<Task> — call from the template's own Enter/Escape/blur handling or a button. |
| RowEditEventArgs<TItem> | Item, ChangedValues | Fired when a row edit is committed. ChangedValues is a Dictionary<string, object?>. |
| ColumnReorderEventArgs | ColumnId, OldIndex, NewIndex | Fired when a column is drag-reordered. |
| ColumnResizeEventArgs | ColumnId, Width, AutoFit | Fired when a column is resized. Width is the committed pixel width (clamped to Min/Max); AutoFit is true only for the double-click auto-fit path. |
| RowReorderEventArgs<TItem> | Item, OldIndex, NewIndex | Fired when a row is drag-reordered. |
| DataGridServerRequest | Page, PageSize, Sorts?, Filters?, GlobalSearch?, GroupBy?, CancellationToken | Sent to OnServerRequest with current grid state. GroupBy contains the current group field if set. CancellationToken should be passed to your HTTP client to abort superseded requests. |
| DataGridLayout | Columns, Sorts?, Filters?, PageSize?, GlobalSearch? | Serializable layout snapshot for persistence. |
| DataGridNamedLayout | Id, Name, Scope, Layout | A named layout entry. Scope is "Personal", "Global", or "SystemDefault". |
| DataGridLayoutSnapshot | Version, Columns, Sorts, Filters, GlobalSearch?, CurrentPage, PageSize, GroupBy? | Public, JSON-serializable shape used by ExportLayout/ApplyLayoutJsonAsync. Version is bumped on schema changes. |
| DataGridColumnLayout | Field, Order, Visible, Width?, Pin? | Per-column entry inside a DataGridLayoutSnapshot. |
Enums
| DataGridSelectionMode | |
|---|---|
| None | No selection. |
| Single | Select one row at a time. |
| Multiple | Select multiple rows with checkboxes. |
| PinDirection | |
|---|---|
| None | Column scrolls normally. |
| Left | Sticky to left edge. |
| Right | Sticky to right edge. |
| DataGridEditMode | |
|---|---|
| None | No editing. |
| Cell | Click a cell to edit inline. |
| Row | Edit entire row at once. |
| AggregateType | |
|---|---|
| None | No aggregation. |
| Sum | Sum of numeric values. |
| Average | Average of numeric values. |
| Count | Count of non-null values. |
| Min | Minimum value. |
| Max | Maximum value. |
DataGridFilterType
| Value | Description |
|---|---|
| Text | Text filter with contains, starts with, equals operators. |
| Number | Numeric filter with comparison and between operators. |
| Date | Date filter with date picker and comparison operators. |
| Select | Multi-select checkbox filter from predefined options. |
| Boolean | True/false toggle filter. |
Tree-grid mode (hierarchical rows)
Set ChildItemsSelector to a function that returns a row's children
(null/empty for leaves) and the grid renders a recursive tree-grid:
the first visible column gets an expand/collapse chevron indented by depth, the
<table> uses role="treegrid", and expandable rows carry
aria-expanded / aria-level. Sorting, filtering and paging apply
to the root items; a paged-in root shows its whole expanded subtree. Tree-grid
mode and DetailTemplate master-detail are mutually exclusive — tree mode wins.
| Parameter | Type | Description |
|---|---|---|
| ChildItemsSelector | Func<TItem, IEnumerable<TItem>?>? | When set, enables tree-grid mode; returns a row's child rows. |
| TreeGridDefaultExpanded | bool | Whether tree nodes start expanded. Default false. |
| TreeColumnField | string? | Field of the column carrying the chevron + indentation. Null = first visible column. |
| OnTreeNodeExpand | EventCallback<TItem> | Raised when a tree node is expanded or collapsed. |
Group panel & multi-level grouping
Set ShowGroupPanel="true" to render a strip above the grid listing the active
grouping levels as removable chips, plus an "add level" dropdown of every column flagged
Groupable. Use GroupByFields (an ordered list of column fields) for
nested multi-level grouping — the grid renders nested group rows with increasing indentation
and a per-group aggregate row (the same Column.Aggregate values as the grand-total
footer, computed over each group). A single-element list (or the legacy GroupBy
string) keeps the classic single-level grouping.
A Groupable column can also be added by dragging its header onto the
panel — the exact same drag that reorders columns within the header row (see
Reorder columns above):
release over a sibling header and it reorders, release over the group panel instead and it
groups. Because it's one unified pointer-based engine (mouse, touch, and pen) rather than
native HTML5 drag-and-drop, drag-to-group works on touch devices too — long-press the grip or
title to arm the drag, then drag up into the panel. A non-Groupable column
dropped on the panel is simply a no-op (it glides back, same as releasing anywhere else
invalid); pressing Escape mid-drag always cancels, whether you're hovering the row
or the panel.
Works in ServerMode too. Both single-level GroupBy
and multi-level GroupByFields are applied client-side to whatever rows the server
returned, and the runtime panel handlers (drag-to-add, add-level dropdown, per-chip remove,
clear-all) trigger an in-place regroup without an extra server round-trip. Grouping operates
over the current page only — for cross-page grouping, either disable pagination
(ShowPagination="false") so the server delivers all rows in one response, or have
the server pre-aggregate groups itself.
| Parameter | Type | Description |
|---|---|---|
| ShowGroupPanel | bool | Shows the grouping chip strip + add-level dropdown. Default false. |
| GroupByFields | IReadOnlyList<string>? | Ordered column fields for multi-level grouping; takes precedence over GroupBy. Works in both client and ServerMode (grouping runs over the rows the server returned for the current page). |
| GroupPanelText | string? | Placeholder hint shown in the panel when nothing is grouped. |
| DataGridColumnDef.Groupable | bool | Gates whether a column can be added to the group panel. |
Related Components
- DataTable — A simpler table component for basic tabular data without advanced grid features
- Table — Low-level table building blocks for fully custom table layouts
- Pagination — Standalone pagination control for navigating through paged data