Skip to content

[UEPR-483] "Choose a Sprite" and "Choose a Backdrop" keyboard accessibility#433

Open
kbangelov wants to merge 38 commits intoscratchfoundation:release/UEPR-297-accessibility-improvementsfrom
kbangelov:task/uepr-483-choose-a-sprite-and-backdrop-accessibility
Open

[UEPR-483] "Choose a Sprite" and "Choose a Backdrop" keyboard accessibility#433
kbangelov wants to merge 38 commits intoscratchfoundation:release/UEPR-297-accessibility-improvementsfrom
kbangelov:task/uepr-483-choose-a-sprite-and-backdrop-accessibility

Conversation

@kbangelov
Copy link
Contributor

@kbangelov kbangelov commented Feb 3, 2026

Resolves

https://scratchfoundation.atlassian.net/browse/UEPR-483

Proposed Changes

  • Adds keyboard navigation logic.
  • UI bug: tooltips don't refresh in case of first open or shift+tab-ing back to the menu.

Reason for Changes

Part of accessibility initiative for Scratch.

@kbangelov kbangelov requested a review from a team as a code owner February 3, 2026 14:18
@adzhindzhi adzhindzhi changed the base branch from develop to release/accessibility-improvements February 6, 2026 09:27
@kbangelov kbangelov force-pushed the release/accessibility-improvements branch 2 times, most recently from e37597f to f2902ce Compare February 13, 2026 09:00
@kbangelov kbangelov changed the base branch from release/accessibility-improvements to release/UEPR-297-accessibility-improvements February 13, 2026 10:10
@kbangelov kbangelov requested a review from adzhindzhi February 16, 2026 11:20
@kbangelov kbangelov requested a review from adzhindzhi February 17, 2026 11:00
@kbangelov kbangelov requested a review from adzhindzhi February 18, 2026 10:55
Copy link
Contributor

@adzhindzhi adzhindzhi left a comment

Choose a reason for hiding this comment

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

Looks good to me! 👍

Copy link
Contributor

@KManolov3 KManolov3 left a comment

Choose a reason for hiding this comment

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

Looks mostly good, left a comment on an edge case. Thanks for migrating the action menu to React hook syntax!

}, CLOSE_DELAY);
}, []);

const clickDelayer = useCallback(
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like it slightly differs in behavior from production:

  • on production, when we open and close the sprite modal, the action menu is closed
  • with this change, when we open and close the sprite modal, the action menu is still open

I am not averse to the updated behavior, as it aligns with the "return focus to previously selected element when you navigate out of a modal".

However there are some visually unpleasant cases:

  • if I open the "Upload a sprite" menu from the action button, the menu closes after a timeout, but if I navigate out of the upload dialog which it opens, it reopens the menu, but the label shows on the bottom
  • same for other actions

I am open to suggestions for what the most consistent behavior (and the least effort change) would be.

As a fallback, keeping the production behavior (and possibly making sure the inner menu items are also unselected, so that no inconsistent label appears) can be an option for this case (so, blur out of the focused action menu, but keep the focus, so that TAB would return to the same / next item).

Image

Copy link
Contributor

Choose a reason for hiding this comment

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

Additionally, the behavior when clicking "Surprise" has changed here - it unfocuses the action menu entirely, while on production it blurs, but keeps the focus on the menu item.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the first comment, I have added a timer that blurs and refocuses on the menu item so to avoid the visual unpleasantries of the positioning of the tooltip upon expansion (the issue was that it needed time to expand so when it refocused the menu was not open yet and the tooltip got stuck in the middle of the core button and the right one)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For the second comment I altered the behavior of the former clickDelayer and removed all the unfocus logic since it came from the idea of closing down the menu upon click which we don't want anymore. This, in my opinion, remains more consistent with the idea of keeping focus on the selected item after its effect has been fulfilled, that we have implemented so far in the accessibility tasks.

What do you think @KManolov3? cc: @adzhindzhi

Copy link
Contributor

Choose a reason for hiding this comment

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

FYI, we still have an issue related to the first case. If you select Choose a Sprite, then close the modal, the tooltip is displayed twice - once for the main button and once for the Choose a Sprite item in the menu.

Regarding the clickDelayed issue, I don't have a strong opinion, but I think the current production behavior, where the menu closes on click while focus remains on the main button, feels more convenient than keeping the menu open. That said, I can see benefits to both approaches.

In general, the behavior when clicking on menu items feels a bit inconsistent right now. For example:

  • For Surprise and Paint, the menu stays open and the selected item remains focused.
  • For Choose a Sprite and Upload, the menu closes, and when the modal is closed, the menu is re-opened and the main button is focused (and in the case of Choose a Sprite, two tooltips are shown).

It might be better to standardize this behavior across all menu items. For example, the menu could always close while keeping focus on the main button, or we could choose another consistent option if this one doesn't make sense or introduces technical complications. What do you think?

@kbangelov kbangelov requested a review from KManolov3 February 23, 2026 09:00
@kbangelov
Copy link
Contributor Author

kbangelov commented Feb 26, 2026

The latest commit fixes the following 3 bugs that existed:

  • Escape used to not show the tooltip for the core element
  • if we selected a sprite from inside the menu, it showed a double tooltip on the way back
  • clicking escape on the tooltip selector library used to also close the action menu on the way back

What I did was remove onBlur logic and replace it in the following way - onBlur unites a few of the following cases:

  • clicking somewhere else (mousedown works now, unlike touchstart)
  • we were also already handling tab
  • the one blur motion we don't handle because we don't want to is after clicking on a menu option that takes us to library or file upload, where we now don't close the menu, which avoids most (hopefully all) bugs.

@kbangelov kbangelov requested a review from adzhindzhi February 26, 2026 08:42
Copy link
Contributor

@KManolov3 KManolov3 left a comment

Choose a reason for hiding this comment

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

Left a few minor comments, but otherwise the behavior looks very smooth to me now! 🙌
There's a lot of logic "under the hood" which may end up being finicky, but hopefully this doesn't end up getting changed very often.

} else if (e.key === KEY.TAB) {
setIsExpanded(false);
focusItem(buttonRef.current);
} else if (e.key === KEY.ESCAPE) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I see some possibly unintended behavior when clicking ESCAPE, after which attempting to TAB- I need to TAB twice for something to happen. It's more of a corner case, but might be worth fixing if it isn't too hard. Otherwise I am fine with leaving as is for now.

Copy link
Contributor Author

@kbangelov kbangelov Feb 27, 2026

Choose a reason for hiding this comment

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

Hmm, it seems to be another issue that appears on firefox but not chrome. Upon reconsideration, I am not sure if Escape behavior is something we want at all here. I mean there are other intuitive ways to focus away as a keyboard or mouse user, such as tab or clicking outside of the menu. What do you think?

There is the "we want to be able to focus the core option" argument but the bottom one already duplicates that behavior on every use of action menu.

A compromise would be "focus on the main button on escape but keep the menu open, since we can still enter it with arrows". Collapsing seems a little unnatural to me now.

}), []
);

const handleItemMouseEnter = useCallback(index => () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you leave a comment why this is needed?

@kbangelov kbangelov requested a review from KManolov3 February 27, 2026 09:00
@@ -222,12 +196,10 @@ const ActionMenu = ({

const handleClick = useCallback(e => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Having a useCallback inside of a map is an antipattern - https://react.dev/reference/rules/rules-of-hooks#only-call-hooks-at-the-top-level. We should be able to extract this on top level.

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