Skip to content

FluentDataGrid column header menu button does not expose expanded state #5026

Description

@adamint

Repro environment

Observed downstream in the Aspire Dashboard with Microsoft.FluentUI.AspNetCore.Components 4.14.1. I also checked v4.14.3 and current main; ColumnBase.razor still renders the same header trigger without disclosure state.

The grid uses:

<FluentDataGrid HeaderCellAsButtonWithMenu="true"
                ResizableColumns="true"
                ResizeType="DataGridResizeType.Discrete" />

Downstream issue: microsoft/aspire#17466

Steps to reproduce

  1. Render a FluentDataGrid with HeaderCellAsButtonWithMenu="true" and at least two column actions.
  2. Inspect a generated column-header button in the accessibility tree.
  3. Open its menu.
  4. Inspect the same button again.

Expected behavior

The header trigger exposes aria-haspopup="menu" and aria-expanded="false" while closed, then aria-expanded="true" while its menu is open. A screen reader can announce the expanded/collapsed state.

Actual behavior

A Playwright browser repro against Aspire's Resources grid found one button named Name, but aria-expanded was absent both before and after opening the menu:

Before: - button "Name"
aria-expanded: <null>

After:  - button "Name"
aria-expanded: <null>
visible menu items: 2

The host and shadow button remain unchanged while the menu is open:

<fluent-button class="col-sort-button stealth" type="button" aria-label="Name">...</fluent-button>
<button class="control" part="control" type="button" aria-label="Name">...</button>

Root cause

The default HeaderCellAsButtonWithMenu template in ColumnBase.razor owns both the generated FluentButton and private _isMenuOpen state. It binds _isMenuOpen to FluentMenu.Open, but does not expose that state on the trigger:

<FluentButton Id="@_columnId"
              Class="col-sort-button"
              @onclick="@HandleColumnHeaderClickedAsync"
              aria-label="@tooltip">
    ...
</FluentButton>

<FluentMenu Anchor="@_columnId" @bind-Open="@_isMenuOpen">
    ...
</FluentMenu>

Downstream consumers cannot safely add the attribute because _isMenuOpen, _columnId, and the click/menu lifecycle are private. Replacing HeaderCellItemTemplate also replaces the complete trigger/menu implementation.

Suggested fix

Add aria-haspopup="menu" and aria-expanded="@_isMenuOpen" to the generated header FluentButton, with browser/component coverage for falsetruefalse.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions