[dev-v5][DataGrid] Add master/detail row expansion#5011
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new master/detail row expansion feature to FluentDataGrid<TGridItem> via a RowDetails template, enabling per-row expandable detail content (including nested grids) without relying on the existing hierarchical (parent/child) model.
Changes:
- Introduces
RowDetailsrendering, toggle UI, and a programmatic expand/collapse API surface onFluentDataGrid<TGridItem>. - Adds
DataGridCellType.RowDetailsand styling adjustments so the details row can span all columns and size to content. - Adds bUnit coverage plus new demo documentation/examples for master/detail and multi-level nesting.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/Core/Components/DataGrid/FluentDataGridRowDetailsTests.razor | Adds bUnit tests covering RowDetails toggling, API methods, and Virtualize guard. |
| src/Core/Enums/DataGridCellType.cs | Adds RowDetails enum member for the new details cell type. |
| src/Core/Components/DataGrid/FluentDataGridRow.razor.cs | Prevents row click/double-click handlers from acting on the details row. |
| src/Core/Components/DataGrid/FluentDataGridCell.razor.css | Adds styling for the details cell and toggle container inline layout. |
| src/Core/Components/DataGrid/FluentDataGridCell.razor.cs | Skips fixed-height / grid-column styling for RowDetails cells. |
| src/Core/Components/DataGrid/FluentDataGridCell.razor | Renders RowDetails cells as <td> like default cells. |
| src/Core/Components/DataGrid/FluentDataGrid.razor.css | Restricts display: contents selector to avoid impacting nested grids. |
| src/Core/Components/DataGrid/FluentDataGrid.razor.cs | Adds RowDetails, OnRowDetailsToggle, Virtualize guard, and new expansion-state APIs. |
| src/Core/Components/DataGrid/FluentDataGrid.razor | Renders the toggle in the first column and conditionally renders the spanning details row. |
| examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Pages/DataGridMasterDetailPage.md | Adds documentation page describing the new master/detail feature. |
| examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMasterDetail.razor | Adds master/detail demo example with nested grid. |
| examples/Demo/FluentUI.Demo.Client/Documentation/Components/DataGrid/Examples/DataGridMasterDetailTwoLevels.razor | Adds two-level nested RowDetails demo example. |
|
@microsoft-github-policy-service agree |
|
Great addition. In fact this is something I was searching before as well. This isn't just limited to nested The only weird thing in my opinion is the mouse houver effect for the entire block. |
|
Thanks, glad it's useful! And good catch on the hover — you're right, that's a real issue. The hover highlight rule (FluentDataGridRow.razor.css) only excluded header/sticky-header/loading rows from the ShowHover background, so a RowDetails block (which can hold arbitrary, potentially large content) got the whole cell highlighted on hover instead of behaving like a normal thin row. Fixed in cb67cfa by excluding row-details-row the same way those other special rows already are. |
|
Following up on your comment about RowDetails not being limited to a nested FluentDataGrid — added a dedicated example illustrating exactly that in 0a419eb. New "Any content as detail" section on the /DataGrid/MasterDetail page: expanding a customer row now shows a contact card (FluentCard + FluentStack + FluentProgressBar) instead of a nested grid, bound to that row's item — no FluentDataGrid involved at all. Should make it clearer to readers that the template is a free-form RenderFragment, not something tied to grids specifically. |
|
I think I like this (have not had a chance to look at it closely and in detail). Perhaps you can add some screenshots to the PR description? One thing that worries me a bit is that we end up with two solutions for (sort of) the same thing. We need to think carefully about how we can/should position this once we get to the point where we want to merge this in (not saying we are going to, also not saying we are not going to) With .NET 11 we get the option to have inequal heights with Validation. We should investigate if we can then support this new option (and Multiline) in the that version (#ifdef's or something like that) |
|
Screenshots attached — one per example on /DataGrid/MasterDetail: Single-level (customers → orders) Hierarchical view: parent and children are the same TItem, rendered through the same columns in a single grid — it's for recursive/self-referential data (org charts, categories/subcategories). Good call on .NET 11 — I hadn't looked into the unequal-heights/Validation work there. I don't know that API surface yet, but I'm glad to dig into whether RowDetails (and MultiLine) could ride on it once it's available, if/when that becomes relevant here. |
|
I tried to merge latest dev-v5 branch because of the performance PR for DataGrid, but now I get some errors. Can you fix those in your branch? |
Adds a RowDetails template parameter that lets any row be expanded via a chevron toggle to reveal detail content spanning all columns directly below it, independent of the existing hierarchical view (which requires parent/child rows of the same item type and columns). The RowDetails template's context is the row's item, so it can host a child FluentDataGrid filtered by that item, nested to any depth. Also fixes a CSS isolation issue where the ::deep tr selector used for CSS Grid display mode could match rows of a nested/child FluentDataGrid, and introduces DataGridCellType.RowDetails so the detail cell isn't constrained by the fixed row height/grid-column styling applied to normal cells.
dev-v5 doesn't use Blazor's per-component CSS isolation (no [b-xxxx] scope attributes are emitted anywhere in the bundled stylesheet); DataGrid styles are plain global CSS instead. The ::deep combinator I had carried over from the dev branch fix is meaningless in that model and was left unprocessed by the build, silently invalidating the whole selector and breaking `display: contents` for every grid (not just nested ones). Replaced it with plain child combinators, which is all that's needed since there's no scope boundary to cross. Also fixes the RowDetails toggle button starting its own line instead of staying beside the first column's value: unlike the HierarchicalToggle container (which wraps the entire cell content), the RowDetails toggle is a standalone element rendered before the cell's own content, so it needs display: inline-flex rather than the block-level flex used for hierarchical-toggle-container.
- Give the RowDetails toggle button its own aria-label (DataGrid_ToggleRowDetails) instead of reusing "Toggle nesting", which described the unrelated hierarchical-view expander and was misleading for screen reader users. - Make ExpandAllRowDetailsAsync/CollapseAllRowDetailsAsync raise OnRowDetailsToggle for each row whose expansion state actually changes, matching the callback's documented "raised when a row is expanded or collapsed" contract (previously only the single-row toggle methods raised it). - Add tests for both, plus a regression test locking in the dedicated aria-label.
With ShowHover enabled, hovering anywhere inside a RowDetails block (which can contain arbitrary, potentially large content such as a nested grid) highlighted the entire details cell, since the hover background rule only excluded header/sticky-header/loading rows. Excluded row-details-row the same way.
Adds an "Any content as detail" example showing that the RowDetails template isn't limited to a nested FluentDataGrid: it can host any content bound to the master row's item. Here a customer row expands into a contact card (FluentCard/FluentStack/FluentProgressBar) instead of a child grid.
13d646b to
e33c611
Compare
|
Fixed — rebased onto the latest dev-v5 in e33c611. The conflict was with the perf PR (#5023, non-interactive cells as plain ), which touched the same rendering path as RowDetails. What changed on my side to accommodate it:
Rebuilt, reran the full suite (3756/3757 — same one pre-existing unrelated failure as before), and re-verified all three demo examples render identically to before the rebase, so the perf change and RowDetails coexist cleanly. Let me know if you'd like anything else adjusted. |
|
@sebguischr is there any way to get some sort of loading mechanism into it as well? For example: I expand the row which should trigger a load mechanism. <RowDetails Context="customer">
@if (ordersForCustomer is null)
{
<FluentSpinner />
return;
}
else
{
<h5 style="margin: 0 0 0.5rem 0;">Orders of @customer.Name</h5>
<FluentDataGrid Items="ordersForCustomer" ShowHover="true">
<PropertyColumn Title="Order #" Property="@(o => o.OrderNumber)" />
<PropertyColumn Title="Date" Property="@(o => o.OrderDate)" Format="yyyy-MM-dd" Sortable="true" InitialSortDirection="DataGridSortDirection.Descending" IsDefaultSortColumn="true" />
<PropertyColumn Title="Amount" Property="@(o => o.Amount)" Format="c" Align="DataGridCellAlignment.End" Sortable="true" />
<TemplateColumn Title="Status">
<FluentBadge Color="@(context.Status == "Delivered" ? BadgeColor.Success : BadgeColor.Subtle)">
@context.Status
</FluentBadge>
</TemplateColumn>
</FluentDataGrid>
}
</RowDetails>private async Task LoadOrdersAsync(Customer customer)
{
ordersForCustomer = null;
await Task.Delay(2000);
ordersForCustomer = OrdersOf(customer.Id);
}This would allow the user to achieve the same without the need to have all orders in memory. |
|
@MarvinKlein1508 Good addition! But I propose that if this needs extra code, to do it in a new PR so this stays manageable |
hierarchical-toggle-container/-expander/-expanded/-collapsed/-expand-button are now shared between the pre-existing HierarchicalToggle feature and the new RowDetails toggle, so the "hierarchical" prefix no longer describes what they're for. Renamed to row-toggle-container/-expander/-expanded/-collapsed/ -expand-button (the row-details-toggle-container modifier is unaffected). Updated the corresponding test selectors.
- Use a proper [!WARNING] admonition for the Virtualize restriction instead of an italic note. - Align the detail heading with the master row's text and factor the repeated inline style into a shared .detail-heading class in each example. - Add a customer with no orders (Tailspin Toys) to the single-level example, with EmptyContent on the child grid, to show what RowDetails looks like for a row without detail data. Updated the description to call this out.
|
All done, pushed in 4fad3ae and bec54e0 — thanks for the detailed feedback, it's a lot cleaner now! 😁
Reran the full suite (306/306 DataGrid tests, 3756/3757 overall — same pre-existing unrelated failure) and re-verified all examples visually, including the empty-row case and the admonition rendering with the site's standard warning style. |
FluentIcon.Width is a raw CSS value (string), so Width="12" produces the invalid declaration "width: 12" and gets ignored by the browser. Fixed both the RowDetails toggle and the pre-existing HierarchicalToggle chevron, which had the same issue and was the source I copied it from.


Pull Request
📖 Description
Adds a
RowDetailstemplate parameter that lets any row be expanded via a chevron toggle to reveal detail content spanning all columns directly below it, independent of the existing hierarchical view (which requires parent/child rows of the same item type and columns). TheRowDetailstemplate's context is the row's item, so it can host a childFluentDataGridfiltered by that item, nested to any depth.This is a new feature (additive, non-breaking): existing grids are unaffected unless
RowDetailsis set.New public API on
FluentDataGrid<TGridItem>:RowDetails(RenderFragment<TGridItem>) — the detail template parameter.OnRowDetailsToggle(EventCallback<TGridItem>) — raised when a row's details are expanded or collapsed.ToggleRowDetailsAsync,ExpandRowDetailsAsync,CollapseRowDetailsAsync,ExpandAllRowDetailsAsync,CollapseAllRowDetailsAsync,IsRowDetailsExpanded— programmatic control of expansion state.DataGridCellType.RowDetails— new enum member identifying the detail cell.RowDetailscannot be combined withVirtualize(throwsInvalidOperationException, matching the existingMultiLine/Virtualizeguard).📸 Screenshots
CSS notes for reviewers
FluentDataGrid'sdisplay: gridmode relies ondisplay: contentson<tr>so cells participate directly in the table's CSS Grid. The original selector matched anytrunder.grid, so a nestedFluentDataGridplaced insideRowDetailshad its own rows absorbed into the parent's grid layout instead of staying contained. Fixed by restricting the rule to this grid's own directthead/tbodychildren (.fluent-data-grid.grid > thead > tr,.fluent-data-grid.grid > tbody > tr) so a nested grid's rows — several DOM levels deeper — aren't matched.Also added
display: inline-flexfor the new toggle's container: unlikeHierarchicalToggle(which wraps the entire cell content, so being block-level doesn't matter), theRowDetailstoggle is a standalone element rendered before the cell's own content, so it needs to stay inline with it rather than starting its own line.👩💻 Reviewer Notes
Two demo pages are included under
/DataGrid/MasterDetail:/DataGrid/Hierarchicalexample where parent/child share the same item type and columns.FluentDataGridcan itself define aRowDetailstemplate, so nesting depth is unlimited.Suggested smoke test: open
/DataGrid/MasterDetail, expand a row in each example, confirm the child grid renders in its own area below the master row (not overlapping subsequent rows), confirm the toggle chevron sits beside the first column's value (not above/below it), collapse it again, and confirm sorting/columns in the child grid work independently of the master grid.Areas to focus review on:
FluentDataGrid.razor/.razor.cs— theRowDetailsrendering and the new expand/collapse API (state keyed byItemKey, so it survives sorting/refresh).FluentDataGrid.razor.css— the> thead > tr/> tbody > trselector change (previously a baretrdescendant selector).FluentDataGridCell.razor.cs—DataGridCellType.RowDetailsskips the fixed row-height andgrid-columnstyles so the detail cell's height follows its content.FluentDataGridRow.razor.cs— click/double-click handling now ignores therow-details-rowclass so interacting with a nested grid doesn't bubble upOnRowClick/OnRowDoubleClickon the master grid.📑 Test Plan
Added
tests/Core/Components/DataGrid/FluentDataGridRowDetailsTests.razor(6 new bUnit tests):ExpandAllRowDetailsAsync/CollapseAllRowDetailsAsyncaffect all loaded rows.ExpandRowDetailsAsync/CollapseRowDetailsAsync/IsRowDetailsExpandedon a single item (including idempotency).OnRowDetailsToggleis raised with the correct item.RowDetails+VirtualizethrowsInvalidOperationException.Full suite run locally: 3752 passed, 1 failed, 0 skipped — the one failure (
FluentNumberTests.FluentNumber_Culture_FR) is pre-existing on a cleandev-v5checkout with no changes (culture/environment-dependent) and unrelated to this change.Also manually verified in the running demo app (
FluentUI.Demo/FluentUI.Demo.Client) that: the nested child grid renders in its own row without overlapping the master grid's subsequent rows, the toggle chevron sits correctly beside the column value, and both demo pages behave correctly end to end.✅ Checklist
General
Component-specific
⏭ Next Steps
RowDetailsTemplatevariant that also receives the row's expanded/collapsed state if reviewers want the template itself to react to it (currently onlyOnRowDetailsToggleexposes this, from the grid side).aria-expandedattribute already set on the toggle button.