Improve dashboard dialog and control accessibility states#17929
Improve dashboard dialog and control accessibility states#17929adamint wants to merge 9 commits into
Conversation
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>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 17929Or
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 17929" |
There was a problem hiding this comment.
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-pressedto assistant “helpful/unhelpful” feedback buttons and replace the Text Visualizer format menu withFluentSelect. - 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
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
adamint
left a comment
There was a problem hiding this comment.
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>
ellahathaway
left a comment
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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?
| 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};"; | ||
| } |
There was a problem hiding this comment.
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.
| <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%;" /> |
There was a problem hiding this comment.
What does this dialog look like now?
| Modal = true, | ||
| PreventScroll = true | ||
| PreventScroll = true, | ||
| OnDialogClosing = EventCallback.Factory.Create<DialogInstance>(dialogService, async _ => |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
They aren't harmful. Might as well leave them.
| public required IAIContextProvider AIContextProvider { get; init; } | ||
|
|
||
| [Inject] | ||
| private IAssistantDisplayContext AssistantDisplayContext { get; init; } = null!; |
There was a problem hiding this comment.
If you add required, then null! isn't required. Fix this everywhere in the PR.
JamesNK
left a comment
There was a problem hiding this comment.
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.
| 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\"]"; |
There was a problem hiding this comment.
I don't to get into the business of unit testing CSS. IMO remove these tests.

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:
Preserved scenarios and proof
aria-pressed.Validation
Checklist
<remarks />and<code />elements on your triple slash comments?