Skip to content

Improve dashboard dialog and control accessibility states#17929

Open
adamint wants to merge 9 commits into
microsoft:mainfrom
adamint:adamint/a11y-dialog-control-states
Open

Improve dashboard dialog and control accessibility states#17929
adamint wants to merge 9 commits into
microsoft:mainfrom
adamint:adamint/a11y-dialog-control-states

Conversation

@adamint
Copy link
Copy Markdown
Member

@adamint adamint commented Jun 4, 2026

Description

Fixes #10119
Fixes #10299
Fixes #17498
Fixes #17496

Condenses related dashboard dialog/control accessibility fixes into one PR. Dialog launchers now get focus back after close, assistant feedback buttons expose pressed state, the Text Visualizer format picker uses a select control, and console resource prefixes meet theme contrast requirements.

Supersedes the smaller draft PRs #17792, #17780, #17787, and #17783.

User-facing usage

Users get clearer control state and focus behavior across dashboard dialogs and utility controls:

Close Settings: focus returns to Settings
Helpful: aria-pressed="false" -> "true"
Select format: FLUENT-SELECT with aria-label="Select format"
Console prefix colors: >= 4.5:1 contrast

Preserved scenarios and proof

Issue Scenario Proof
#10119 Closing Help, Settings, and assistant surfaces restores focus to stable launchers. Source PR #17792. Artifact folder: https://github.com/adamint/aspire/tree/a11y-artifacts-20260601042635/10119. Visual proof: https://raw.githubusercontent.com/adamint/aspire/cd33e79ec249a7354240cc851b921502ada33d1a/proof/a11y/17792/pr-17792-visual-proof.mp4
#10299 Assistant helpful/unhelpful buttons expose aria-pressed. Source PR #17780. Artifact folder: https://github.com/adamint/aspire/tree/a11y-artifacts-20260601042635/10299. Visual proof: https://raw.githubusercontent.com/adamint/aspire/cd33e79ec249a7354240cc851b921502ada33d1a/proof/a11y/17780/pr-17780-visual-proof.mp4
#17498 Text Visualizer format picker uses a select/listbox control with disabled unavailable options. Source PR #17787. Artifact folder: https://github.com/adamint/aspire/tree/a11y-artifacts-20260601042635/17498. Visual proof: https://raw.githubusercontent.com/adamint/aspire/cd33e79ec249a7354240cc851b921502ada33d1a/proof/a11y/17787/pr-17787-visual-proof.mp4
#17496 Console resource prefixes choose text color that meets WCAG AA contrast in light and dark modes. Source PR #17783. Artifact folder: https://github.com/adamint/aspire/tree/a11y-artifacts-20260601042635/17496. Visual proof: https://raw.githubusercontent.com/adamint/aspire/cd33e79ec249a7354240cc851b921502ada33d1a/proof/a11y/17783/pr-17783-visual-proof.mp4

Validation

dotnet test --project tests/Aspire.Dashboard.Components.Tests/Aspire.Dashboard.Components.Tests.csproj --no-launch-profile -- --filter-class "*.MainLayoutTests" --filter-class "*.AssistantChatTests" --filter-class "*.TextVisualizerDialogTests" --filter-class "*.LogViewerTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

adamint and others added 7 commits June 4, 2026 18:01
Track stable launch targets for Help, Settings, and assistant surfaces so closing dialogs or the assistant sidebar returns focus predictably. Carry assistant return-focus state through sidebar/modal transitions and cover desktop, mobile, shortcut, and prompt-launched paths.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid mutating the process-wide color generator from the render test. The all-accent contrast coverage remains in the helper-level test, while the component test now verifies the rendered style contract for the actual generated color.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 4, 2026 22:11
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 4, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 17929

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17929"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR consolidates several Aspire Dashboard accessibility fixes, improving keyboard focus restoration for dialogs, exposing pressed state for assistant feedback buttons, making the Text Visualizer format picker keyboard-accessible via a select control, and ensuring console resource prefix text meets WCAG AA contrast in dark mode.

Changes:

  • Restore focus to stable launcher elements after closing header dialogs and assistant surfaces (including sidebar ↔ modal transitions).
  • Add aria-pressed to assistant “helpful/unhelpful” feedback buttons and replace the Text Visualizer format menu with FluentSelect.
  • Generate console resource-prefix inline styles with a text color that maintains WCAG AA contrast in dark theme, with tests validating palette coverage.
Show a summary per file
File Description
tests/Shared/TestDialogService.cs Implements additional dialog/panel async overloads used by new focus-restoration tests.
tests/Shared/TestAIContextProvider.cs Enhances AI context test double to support display-changed subscriptions and focus restoration state.
tests/Aspire.Dashboard.Components.Tests/Layout/MainLayoutTests.cs Adds coverage for focus restoration after dialog close and assistant sidebar/modal transitions.
tests/Aspire.Dashboard.Components.Tests/Controls/TextVisualizerDialogTests.cs Updates format-change testing and adds coverage for FluentSelect selection persistence across rerenders.
tests/Aspire.Dashboard.Components.Tests/Controls/LogViewerTests.cs Adds tests validating dark-theme accent palette contrast and that generated resource-prefix styles are applied.
tests/Aspire.Dashboard.Components.Tests/Controls/AssistantChatTests.cs Adds tests asserting feedback buttons expose aria-pressed correctly as state changes.
src/Aspire.Dashboard/Model/Assistant/IAIContextProvider.cs Expands assistant context contract to carry return-focus and focus-restore intent for UI coordination.
src/Aspire.Dashboard/Model/Assistant/AssistantDialogViewModel.cs Adds return-focus element id to assist in restoring focus after assistant UI closes/transitions.
src/Aspire.Dashboard/Model/Assistant/AIContextProvider.cs Implements new focus-related contract members and wires modal close to JS focus restoration.
src/Aspire.Dashboard/Components/Layout/MainLayout.razor.cs Tracks pending focus restoration and coordinates focus after dialog close and assistant sidebar visibility changes.
src/Aspire.Dashboard/Components/Layout/MainLayout.razor Assigns stable element IDs to launchers and passes focus-return targets into assistant sidebar dialog content.
src/Aspire.Dashboard/Components/Dialogs/TextVisualizerDialog.razor.cs Switches internal selection state to SelectViewModel and preserves selected format across rerenders.
src/Aspire.Dashboard/Components/Dialogs/TextVisualizerDialog.razor Replaces format FluentMenuButton with FluentSelect and disables unavailable formats via select options.
src/Aspire.Dashboard/Components/Dialogs/AssistantSidebarDialog.razor.cs Updates hide/expand flows to suppress premature focus restore during sidebar→modal transitions and pick correct launcher target.
src/Aspire.Dashboard/Components/Dialogs/AssistantSidebarDialog.razor Switches close handler to async to align with updated provider method.
src/Aspire.Dashboard/Components/Dialogs/AssistantModalDialog.razor.cs Adds focus restoration via OnDialogClosing and computes correct sidebar return-focus target on modal→sidebar transition.
src/Aspire.Dashboard/Components/Controls/ResourcePrefixStyle.cs Centralizes generated resource-prefix styles and selects text color for dark-theme contrast.
src/Aspire.Dashboard/Components/Controls/LogViewer.razor Uses ResourcePrefixStyle to render resource prefix background + text color CSS variable.
src/Aspire.Dashboard/Components/Controls/AssistantChat.razor Adds aria-pressed to feedback buttons so assistive tech announces pressed state.

Copilot's findings

  • Files reviewed: 19/19 changed files
  • Comments generated: 2

Comment thread src/Aspire.Dashboard/Model/Assistant/IAIContextProvider.cs Outdated
Comment thread src/Aspire.Dashboard/Model/Assistant/AssistantDialogViewModel.cs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Member Author

@adamint adamint left a comment

Choose a reason for hiding this comment

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

Reviewed the dashboard dialog/control accessibility changes with multiple passes and ran the targeted dashboard component validation. I did not find any blocking issues.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@ellahathaway ellahathaway left a comment

Choose a reason for hiding this comment

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

Reviewed with multiple passes focusing on:

  • Focus restoration logic in MainLayout.razor.cs — thread safety under Blazor's sync context, _suppressNextDialogFocusRestore lifecycle, JS interop null safety
  • IAssistantDisplayContext interface boundary — correctly internal, keeps public IAIContextProvider unchanged
  • WCAG AA contrast implementation — CSS variable cascade with fallback, dark/light theme coverage
  • aria-pressed semantics — correct explicit string values per ARIA spec
  • FluentSelect migration — keyboard accessibility, option selection persistence
  • Test quality — WCAG contrast regression test validates all accent colors in both themes via actual CSS parsing and luminance computation

No issues found. Clean, well-structured accessibility PR with comprehensive test coverage.

}

public static async Task OpenDialogAsync(IDialogService dialogService, string title, AssistantDialogViewModel viewModel)
public static Task OpenDialogAsync(IDialogService dialogService, string title, AssistantDialogViewModel viewModel)
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.

Nit: Is the public overload load-bearing? All callers appear to be internal to the Dashboard assembly. If there are no external consumers, this could be simplified to a single internal method?

Comment on lines +14 to +21
internal static string GetStyle(string resourcePrefix)
{
var colorIndex = ColorGenerator.Instance.GetColorIndex(resourcePrefix);
var accentVariableName = ColorGenerator.s_variableNames[colorIndex];
var textColor = GetTextColorValue(accentVariableName);

return $"background: var({accentVariableName}); --resource-text-color: {textColor};";
}
Copy link
Copy Markdown
Member

@JamesNK JamesNK Jun 6, 2026

Choose a reason for hiding this comment

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

What does this look like?

If it looks odd, then a better solution would be to tweak the problematic resource accents, so they meet the standard.

Copy link
Copy Markdown
Member

@JamesNK JamesNK Jun 6, 2026

Choose a reason for hiding this comment

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

I think this a good improvement

image

Comment on lines +19 to +30
<FluentSelect TOption="SelectViewModel<string>"
Id="@_selectFormatId"
Items="@_options"
Position="SelectPosition.Below"
OptionText="@(option => option.Name)"
OptionValue="@(option => option.Id)"
OptionDisabled="@(option => !EnabledOptions.Contains(option.Id))"
OptionTitle="@(option => option.Name)"
@bind-SelectedOption="@_selectedFormat"
@bind-SelectedOption:after="OnSelectedFormatChanged"
AriaLabel="@Loc[nameof(Dialogs.TextVisualizerSelectFormatType)]"
Style="min-width: 12rem; width: max-content; max-width: 100%;" />
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What does this dialog look like now?

Copy link
Copy Markdown
Member

@JamesNK JamesNK Jun 6, 2026

Choose a reason for hiding this comment

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

Disabled items look a little odd, but that's not new in our selects. Looks fine to me

Uploading image.png…

Modal = true,
PreventScroll = true
PreventScroll = true,
OnDialogClosing = EventCallback.Factory.Create<DialogInstance>(dialogService, async _ =>
Copy link
Copy Markdown
Member

@JamesNK JamesNK Jun 6, 2026

Choose a reason for hiding this comment

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

Have you tested these changes in the dashboard? Because the AI assistant UI is all disabled at the moment is probably going to be removed. The only way to test this would have been to re-enable it first.

Making fixes to it isn't needed. I think it will be removed soon.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

They aren't harmful. Might as well leave them.

public required IAIContextProvider AIContextProvider { get; init; }

[Inject]
private IAssistantDisplayContext AssistantDisplayContext { get; init; } = null!;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you add required, then null! isn't required. Fix this everywhere in the PR.

Copy link
Copy Markdown
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

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

LGTM. Focus restoration logic is well-structured, CSS variable fallback for WCAG contrast is correct, DI registrations are properly forwarded, and test coverage is comprehensive.

Comment on lines +14 to +18
public class LogViewerTests : DashboardTestContext
{
private const string AppCssRelativePath = "src/Aspire.Dashboard/wwwroot/css/app.css";
private const string LightThemeSelector = ":root";
private const string DarkThemeSelector = "[data-theme=\"dark\"]";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't to get into the business of unit testing CSS. IMO remove these tests.

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

Projects

None yet

5 participants