Skip to content

Conversation

@adzhindzhi
Copy link
Contributor

@adzhindzhi adzhindzhi commented Jan 14, 2026

Resolves

UEPR-471

Proposed Changes

  • Make all elements in the library modal navigable through Tab navigation
  • Allow keyboard actions with the LibraryItems
  • Add ModalFocusContext for restoring the focus to the last focused element when navigating out of the modal

TODOs:

Reason for Changes

  • This PR is part of a larger initiative to make the Scratch Editor more accessible and navigable through keyboard.

@adzhindzhi adzhindzhi requested a review from Copilot January 14, 2026 16:02
@adzhindzhi adzhindzhi requested a review from a team as a code owner January 14, 2026 16:02
Copy link

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 enhances keyboard accessibility in the Scratch Editor's library modals by implementing comprehensive tab navigation and focus management. The changes support the broader initiative to make the Scratch Editor fully navigable via keyboard.

Changes:

  • Added ModalFocusContext to capture and restore focus when modals open/close
  • Implemented keyboard event handlers (Enter/Space) for library items
  • Made all modal elements keyboard-accessible with proper tabIndex and ARIA attributes

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
packages/scratch-gui/src/lib/navigation-keys.js Defines keyboard key constants for consistent navigation handling
packages/scratch-gui/src/contexts/modal-focus-context.jsx Implements focus restoration context for modal interactions
packages/scratch-gui/src/containers/target-pane.jsx Captures focus when opening sprite library
packages/scratch-gui/src/containers/stage-selector.jsx Captures focus when opening backdrop library
packages/scratch-gui/src/containers/sound-tab.jsx Wraps sound library click handler to capture focus
packages/scratch-gui/src/containers/library-item.jsx Changes from onKeyPress to onKeyDown with Enter/Space handling
packages/scratch-gui/src/containers/costume-tab.jsx Captures focus when opening costume/backdrop libraries
packages/scratch-gui/src/components/tag-button/tag-button.css Adds focus-visible outline with offset to prevent cropping
packages/scratch-gui/src/components/library/library.jsx Restores focus when closing library modal
packages/scratch-gui/src/components/library-item/library-item.jsx Updates event handlers and adds keyboard accessibility attributes
packages/scratch-gui/src/components/gui/gui.jsx Wraps main UI with ModalFocusProvider
packages/scratch-gui/src/components/filter/filter.jsx Converts clear button to focusable element with accessibility improvements
packages/scratch-gui/src/components/filter/filter.css Removes default button styling for clear button
packages/scratch-gui/src/components/extension-button/extension-button.jsx Captures focus when opening extensions modal
packages/scratch-gui/src/components/extension-button/extension-button.css Adds focus-visible outline styling

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@kbangelov
Copy link
Contributor

kbangelov commented Jan 15, 2026

I like the UX

Copy link
Contributor

@kbangelov kbangelov 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, left a few small comments.

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.

Clean work 🙌


const captureFocus = useCallback(() => {
lastFocusedElement.current = document.activeElement;
}, []);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: Isn't lastFocusedElement still technically a dependency?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Technically, yes, but lastFocusedElement is a ref, and changing .current wouldn’t trigger a re-render. And the ref object itself doesn’t change, so it shouldn’t affect anything. Or am I missing something? 😅

const initAudioContext = () => {
document.removeEventListener('mousedown', initAudioContext);
document.removeEventListener('touchstart', initAudioContext);
document.removeEventListener('keydown', initAudioContext);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

You can now navigate to the Sound Editor without either mousedown or touchstart firing

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.

The latest changes make sense to me. It's unfortunate that the logic is as custom for every feature callout, but addressing that will be a larger effort.

if (!isExtensionButtonVisible) return;

if (e.type === 'keydown') {
// Prevent focus from jumping to the next element in the tab order after keydown finishes
Copy link
Contributor

Choose a reason for hiding this comment

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

🥲

@adzhindzhi adzhindzhi merged commit 92c83be into scratchfoundation:develop Jan 19, 2026
6 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Jan 19, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants