Skip to content

[dev-v5][DataGrid] Add master/detail row expansion#5011

Open
sebguischr wants to merge 5 commits into
microsoft:dev-v5from
sebguischr:feature/datagrid-master-detail-v5
Open

[dev-v5][DataGrid] Add master/detail row expansion#5011
sebguischr wants to merge 5 commits into
microsoft:dev-v5from
sebguischr:feature/datagrid-master-detail-v5

Conversation

@sebguischr

@sebguischr sebguischr commented Jul 10, 2026

Copy link
Copy Markdown

Pull Request

📖 Description

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.

This is a new feature (additive, non-breaking): existing grids are unaffected unless RowDetails is 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.

RowDetails cannot be combined with Virtualize (throws InvalidOperationException, matching the existing MultiLine/Virtualize guard).

📸 Screenshots

  • Single Master / Detail:
single-level
  • Two Levels:
two-levels
  • Custom Detail content:
custom-content

CSS notes for reviewers

FluentDataGrid's display: grid mode relies on display: contents on <tr> so cells participate directly in the table's CSS Grid. The original selector matched any tr under .grid, so a nested FluentDataGrid placed inside RowDetails had its own rows absorbed into the parent's grid layout instead of staying contained. Fixed by restricting the rule to this grid's own direct thead/tbody children (.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-flex for the new toggle's container: unlike HierarchicalToggle (which wraps the entire cell content, so being block-level doesn't matter), the RowDetails toggle 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:

  • Master / Detail — customers (master) → orders (detail), two structurally unrelated grids, to contrast with the existing /DataGrid/Hierarchical example where parent/child share the same item type and columns.
  • Two levels of detail — customers → orders → order lines, showing that a child FluentDataGrid can itself define a RowDetails template, 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 — the RowDetails rendering and the new expand/collapse API (state keyed by ItemKey, so it survives sorting/refresh).
  • FluentDataGrid.razor.css — the > thead > tr / > tbody > tr selector change (previously a bare tr descendant selector).
  • FluentDataGridCell.razor.csDataGridCellType.RowDetails skips the fixed row-height and grid-column styles so the detail cell's height follows its content.
  • FluentDataGridRow.razor.cs — click/double-click handling now ignores the row-details-row class so interacting with a nested grid doesn't bubble up OnRowClick/OnRowDoubleClick on the master grid.

📑 Test Plan

Added tests/Core/Components/DataGrid/FluentDataGridRowDetailsTests.razor (6 new bUnit tests):

  • Toggle renders and starts collapsed.
  • Clicking the toggle expands/collapses the details row.
  • ExpandAllRowDetailsAsync / CollapseAllRowDetailsAsync affect all loaded rows.
  • ExpandRowDetailsAsync / CollapseRowDetailsAsync / IsRowDetailsExpanded on a single item (including idempotency).
  • OnRowDetailsToggle is raised with the correct item.
  • RowDetails + Virtualize throws InvalidOperationException.

Full suite run locally: 3752 passed, 1 failed, 0 skipped — the one failure (FluentNumberTests.FluentNumber_Culture_FR) is pre-existing on a clean dev-v5 checkout 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

  • I have added tests for my changes.
  • I have tested my changes.
  • I have updated the project documentation to reflect my changes.
  • I have read the CONTRIBUTING documentation and followed the standards for this project.

Component-specific

  • I have added a new component
  • I have added Unit Tests for my new component
  • I have modified an existing component
  • I have validated the Unit Tests for an existing component

⏭ Next Steps

  • Could consider a RowDetailsTemplate variant that also receives the row's expanded/collapsed state if reviewers want the template itself to react to it (currently only OnRowDetailsToggle exposes this, from the grid side).
  • Happy to add ARIA guidance/docs updates if maintainers want specific accessibility wording beyond the aria-expanded attribute already set on the toggle button.

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.
Copilot AI review requested due to automatic review settings July 10, 2026 08:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 RowDetails rendering, toggle UI, and a programmatic expand/collapse API surface on FluentDataGrid<TGridItem>.
  • Adds DataGridCellType.RowDetails and 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.

Comment thread src/Core/Components/DataGrid/FluentDataGridCell.razor.cs
Comment thread src/Core/Components/DataGrid/FluentDataGrid.razor
- 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.
@sebguischr

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@MarvinKlein1508

Copy link
Copy Markdown
Collaborator

Great addition. In fact this is something I was searching before as well. This isn't just limited to nested FluentDataGrid components. In fact you can display any component/content there you want. I also tested DataGridDisplayMode.Table which works as I expect.

The only weird thing in my opinion is the mouse houver effect for the entire block.

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.
@sebguischr

Copy link
Copy Markdown
Author

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.

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.
@sebguischr

Copy link
Copy Markdown
Author

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.

@vnbaaij

vnbaaij commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

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)

@sebguischr

Copy link
Copy Markdown
Author

Screenshots attached — one per example on /DataGrid/MasterDetail:

Single-level (customers → orders)
Custom content as detail (contact card, no nested grid at all)
Two levels of nesting (customers → orders → order lines)
On the "two solutions for the same thing" concern — I think they actually target different shapes of data rather than overlapping:

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).
Master/Detail: the detail is a RenderFragment, so it can be a different item type, different columns, or non-tabular content entirely (as screenshot 2 shows) — it's for showing associated but differently-shaped data per row, not another view of the same tree.
So picking one over the other is really about the shape of the data, not a stylistic choice — but I get the discoverability worry (two different "expand a row" mechanisms to explain). Happy to make that distinction more prominent in the docs (I already cross-link the two pages) if that would help positioning this for a merge decision — let me know what would be useful, no pressure either way.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants