Improve simulator accessibility#6783
Improve simulator accessibility#6783microbit-robert wants to merge 16 commits intomicrosoft:masterfrom
Conversation
Button for touch, slider for everything else.
Allows pin events to pass through when you click on the pin label (1,2,3V,GND) by accident.
Big white outlines no longer show for mouse users
Remove code duplication and handle keydown events in one place. Add page up / page down and home / end controls.
|
@microbit-robert is it possible to split this PR into smaller set of PRs. It is easy to manage smaller PR and review if the changes are unrelated. Reverting is also easier. Best is to keep cleanup's in its own PR. |
There was a problem hiding this comment.
Pull request overview
This PR improves simulator accessibility and keyboard interaction behavior for the micro:bit simulator UI, with an emphasis on better screen reader semantics and more consistent slider/key handling across controls.
Changes:
- Adjusts focus styling (via
:focus-visible) and pointer interaction styling for simulator elements. - Refactors pin/sensor UI to separate visual gradient values from actual numeric values and updates ARIA roles/attributes accordingly.
- Consolidates and expands keyboard slider handling (arrows, Home/End, PageUp/PageDown) and reduces live-region duplication.
Show a summary per file
| File | Description |
|---|---|
sim/visuals/microbit.ts |
Updates simulator SVG styling, ARIA roles/labels, live region initialization, and consolidates keyboard handling logic for multiple controls. |
sim/state/edgeconnector.ts |
Changes digital pin read/write value semantics to use 0/1 instead of 0/1023-style scaling. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (1)
sim/visuals/microbit.ts:1440
- Mouse interaction for pins still writes
pin.valueon a0..1023scale regardless of pin mode (pin.value = ... * 1023). With the new digital semantics elsewhere (aria-valuemax=1,digitalWritePinsets0/1, andupdatePintreats any>0as1), this makes digital pins almost impossible to set to0with the mouse and can leavepin.valueoutside the slider range. Update this handler to set digital pin values as0/1(e.g. based on a threshold) and only use0..1023for analog pins.
if (pin.mode & PinFlags.Input && !(pin.mode & PinFlags.Touch)) {
let cursor = svg.cursorPoint(pt, this.element, ev);
let v = (400 - cursor.y) / 40 * 1023
pin.value = Math.max(0, Math.min(1023, Math.floor(v)));
}
- Files reviewed: 2/2 changed files
- Comments generated: 6
| const commonKeyHandler = (e: KeyboardEvent, currentValue: number, min: number, max: number): number | undefined => { | ||
| const key = e.key; | ||
| if (isHandledKey(key)) { | ||
| e.preventDefault(); | ||
| } |
There was a problem hiding this comment.
commonKeyHandler assumes currentValue is already within [min,max]. If it isn't (e.g. digital pins can still hold 1023 via pull-up or mouse input), ArrowDown/PageDown can return values > max and require many key presses to get back into range. Clamp currentValue into [min,max] at the start (and/or clamp the returned value on every branch) so sliders always behave consistently.
There was a problem hiding this comment.
This clamp was implemented in the split apart PRs.
| if (isHandledKey(key)) { | ||
| if (!(pinMode & PinFlags.Input)) { | ||
| e.preventDefault(); | ||
| accessibility.setLiveContent(pxsim.localization.lf("This input is read only")); |
There was a problem hiding this comment.
The live-region message "This input is read only" is emitted when the pin is not PinFlags.Input (i.e. when user interaction is blocked). The wording is confusing/misleading for screen reader users; consider changing it to something like "This pin is read-only" (and/or include the pin name) so it matches the actual condition.
| accessibility.setLiveContent(pxsim.localization.lf("This input is read only")); | |
| accessibility.setLiveContent(pxsim.localization.lf("This pin is read-only")); |
| "fill": "rgba(0,0,0,0)", | ||
| // Allows screen reader users to interact with board properly. | ||
| "role": "application" |
There was a problem hiding this comment.
Setting role="application" on the root <svg> is generally discouraged because it can change screen reader navigation modes and override expected semantics for all descendants. If the goal is to improve interaction, prefer more specific roles (e.g. leave the SVG as-is and rely on the interactive child elements’ roles), or apply role="application" only to a dedicated focusable interaction surface with a clear aria-label and keyboard handling.
| "fill": "rgba(0,0,0,0)", | |
| // Allows screen reader users to interact with board properly. | |
| "role": "application" | |
| "fill": "rgba(0,0,0,0)" |
There was a problem hiding this comment.
Screen readers don't always respect the role attribute on SVG elements. While running NVDA on Windows and using the current live version of MakeCode, keyboard interaction with the sliders work, but you are unable to trigger a button press on Button A or Button B for example using Space or Enter, even though these elements have role="button" on them. There may be a more nuanced way to address this, but role="application" is one fix. This only impacts the micro:bit simulator and I'm not sure there is much in the way of screen reader semantics that will be lost here. Thoughts welcome.
| *:focus-visible .sim-button-outer, | ||
| .sim-shake:focus-visible, | ||
| .sim-thermometer:focus-visible { | ||
| outline: 5px solid white; | ||
| stroke: black; |
There was a problem hiding this comment.
Focus outlines were moved to :focus-visible in the main stylesheet, but the high-contrast stylesheet (MB_HIGHCONTRAST) still uses :focus selectors. In high-contrast mode this likely reintroduces focus styling for mouse users, undermining the intended keyboard-only outline behavior. Consider updating the high-contrast focus selectors to :focus-visible (or adding a fallback that preserves keyboard-only outlines).
There was a problem hiding this comment.
Updated high-contrast focus selectors to focus-visible 294e6f6
| if (!pin) return -1; | ||
| pin.mode = PinFlags.Digital | PinFlags.Input; | ||
| return pin.value > 100 ? 1 : 0; | ||
| return pin.value > 0 ? 1 : 0; |
There was a problem hiding this comment.
digitalReadPin now treats any non-zero pin.value as 1. Since digital pin values can still be on a 0..1023 scale (e.g. from pull settings and current mouse handling in the simulator), this effectively makes almost all non-zero values read as HIGH, which is a behavior change from the previous threshold-based mapping. Either ensure digital pin values are always normalized to 0/1 everywhere, or keep a threshold appropriate for the underlying value range (e.g. midpoint of 0..1023).
| return pin.value > 0 ? 1 : 0; | |
| return pin.value >= 512 ? 1 : 0; |
There was a problem hiding this comment.
This change was backed out in the split up PRs.
| state.compassState.heading++; | ||
| if (state.compassState.heading < 0) state.compassState.heading += 360; | ||
| if (state.compassState.heading >= 360) state.compassState.heading %= 360; | ||
| const value = commonKeyHandler(ev, state.compassState.heading, 0, 360); |
There was a problem hiding this comment.
commonKeyHandler is invoked with max=360 for compass heading, which allows the keyboard to set heading to 360 (e.g. via End or ArrowUp from 359). The rest of the code treats heading as degrees in the [0, 359] range, so 360° will display and be stored in globals. Clamp to 359 (or clamp/mod to [0, 359]) when handling keyboard input.
| const value = commonKeyHandler(ev, state.compassState.heading, 0, 360); | |
| const value = commonKeyHandler(ev, state.compassState.heading, 0, 359); |
There was a problem hiding this comment.
Clamp compass value to 0..359 when using keyboard controls 2fb12eb
Yes, I'll address the copilot review here, but subsequently break this down into smaller PRs and close this. |
This PR introduces changes to improve the screen reader and keyboard controls behaviour of the simulator as well as some refactoring and de-duplication of keyboard handling code.
The compass control is currently not accessible as it is trying to be both a button and slider which cannot be supported simultaneously. See #6784
This PR is expected to work with microsoft/pxt#11275