Skip to content

Settings page keyboard navigation#8577

Merged
Samq64 merged 45 commits intoScratchAddons:masterfrom
Samq64:settings-a11y
Oct 15, 2025
Merged

Settings page keyboard navigation#8577
Samq64 merged 45 commits intoScratchAddons:masterfrom
Samq64:settings-a11y

Conversation

@Samq64
Copy link
Member

@Samq64 Samq64 commented Sep 21, 2025

Resolves #2532

Changes

  • The settings page and popup are now usable with the keyboard
  • Converts the more settings modal into an HTML <dialog> element
  • Consolidates the switch styles into its own file
  • Consolidates addon-group, btn-dropdown and back-button into a single class
  • Adds a dropdown compnent with arrow key navigation
  • Always shows the dismiss admin message link (regular comments are unaffected)

Reason for changes

It's good for accessibility and not being able to toggle addons with the keyboard is annoying.

Tests

Tested on Chromium

Todo:

  • Dropdows
  • Scratch messaging
  • Cloud games (already good)
  • Fix enabling addons with confirmations
  • Better focus borders
  • Re-order table rows with the keyboard?
  • Navigate between addons with the arrow keys?

@Samq64 Samq64 added scope: webpages Related to the web pages (settings page, pop-up, etc) scope: accessibility Addresses an accessibility issue labels Sep 21, 2025
Copy link
Member

@DNin01 DNin01 left a comment

Choose a reason for hiding this comment

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

Did some testing with Windows Narrator—there's still room for improvement with screen reader navigation, but, with a keyboard, things are at least usable now.

Some screen reader problems:

  • Many icon buttons don't have a name
  • Addon setting elements aren't linked to their labels
  • Item lists like the category panel don't function like lists

Some keyboard navigation problems:

  • Setting tooltips can't be accessed

@DNin01
Copy link
Member

DNin01 commented Sep 21, 2025

I added keyboard navigation to dropdowns by simply setting tabindex="0" on them.

The button that opens the dropdown still needs to have an aria-haspopup attribute.

@mxmou
Copy link
Member

mxmou commented Sep 21, 2025

The correct keyboard behavior for dropdowns is that up and down arrows focus the previous/next item and Tab focuses the next element outside the dropdown.

@DNin01
Copy link
Member

DNin01 commented Sep 21, 2025

The correct keyboard behavior for dropdowns is that up and down arrows focus the previous/next item and Tab focuses the next element outside the dropdown.

I have before made a library with this exact functionality, so I'll look into this.

@Samq64
Copy link
Member Author

Samq64 commented Sep 21, 2025

I have not done any screen reader testing myself yet.

I have before made a library with this exact functionality, so I'll look into this.

I was hoping not to add another libary but it might be worth it if it needs more than a few lines of JS.

@mxmou
Copy link
Member

mxmou commented Sep 21, 2025

Blockly's menu implementation might be useful as a reference: https://github.com/google/blockly/blob/139fa2b/core/menu.ts#L407-L462

@DNin01
Copy link
Member

DNin01 commented Sep 22, 2025

Maybe I should just tell you what I have in store...
const shiftAmountsByKey = {
  ArrowUp: -1,
  ArrowDown: 1,
  ArrowLeft: -1,
  ArrowRight: 1,
  Home: -1000,
  End: 1000,
};

/** Makes a list-like element arrow-key navigated. */
export default class ArrowKeyList {
  constructor(element) {
    element.addEventListener("keydown", (e) => {
      if (e.ctrlKey || e.metaKey || e.altKey) return;
      if (e.key === "Tab") {
        if (e.shiftKey) {
          // Focus the first child, then the default behavior of Shift+Tab will move to the previous element
          element.firstElementChild.focus();
        } else {
          // Focus the last child, then the default behavior of Tab will move to the next element
          element.lastElementChild.focus();
        }
      } else {
        const shiftBy = shiftAmountsByKey[e.key];
        if (shiftBy) e.preventDefault(); else return;

        const oldFocusIndex = Array.from(element.children).indexOf(document.activeElement);
        // Move,
        const adjustedFocusIndex = oldFocusIndex + shiftBy;
        // clamp,
        const newFocusIndex = Math.min(Math.max(adjustedFocusIndex, 0), element.childElementCount - 1);
        // set focus!
        const targetElement = element.children[newFocusIndex];
        targetElement.focus();
      }
    });
  }
}

I'm not familiar with Vue at all, so I don't know how to work this code (which has been stripped down, by the way) into our project. (If you want to figure it out, feel free to use this snippet under the same license terms of our project.)

I think it's more important that we add ARIA attributes to our menus, though. role, aria-haspopup, and aria-expanded shouldn't be too hard to implement.

Or, I wonder if there's a library that handles ARIA attributes and navigation for us.

@Samq64
Copy link
Member Author

Samq64 commented Sep 23, 2025

Menu arrow key naviagtion works now. I'm not sure if the menus should stay open when tabbing away, currently they doesn't.

If you want to figure it out, feel free to use this snippet under the same license terms of our project.

Just to be clear you mean this repository's license, right?

@DNin01
Copy link
Member

DNin01 commented Sep 24, 2025

It works! The dropdowns, menus, and menu items are announced as such:

  • "Button, collapsed, has popup"
    • "Export settings, menu item, 1 of 2"
    • "Import settings, menu item, 2 of 2"

However, icon-buttons still need readable labels—currently most of the images don't have alt text, and when there's no text, the default name is just "graphic" or "button".

I'm not sure if the menus should stay open when tabbing away, currently they doesn't.

On GitHub, they collapse when you tab outside.

If you want to figure it out, feel free to use this snippet under the same license terms of our project.

Just to be clear you mean this repository's license, right?

Correct.

@DNin01
Copy link
Member

DNin01 commented Sep 28, 2025

Navigate between addons with the arrow keys?

I managed to get this working, but it relies on the addon cards' style attributes being an exact value (to skip over addons in collapsed groups).

addon-body.html

  <template>
- <div class="addon-body" v-show="shouldShow" :id="'addon-' + addon._addonId">
+   <div
+     class="addon-body"
+     v-show="shouldShow"
+     :id="'addon-' + addon._addonId"
+     tabindex="0"
+     @keydown="handleKeys"
+   >
      <div class="addon-topbar">

addon-body.js

        },
+       handleKeys(e) {
+         if (!e.target.classList.contains("addon-body")) return;
+         if (e.ctrlKey || e.metaKey || e.altKey) return;
+         const cards = document.querySelectorAll(".addons-container .addon-body:not([style='display: none;'])");
+         const index = Array.prototype.findIndex.call(cards, (i) => i === document.activeElement);
+         if (e.key === "ArrowUp") {
+           if (!cards[index - 1]) return;
+           e.preventDefault();
+           cards[index - 1].focus();
+         } else if (e.key === "ArrowDown") {
+           if (!cards[index + 1]) return;
+           e.preventDefault();
+           cards[index + 1].focus();
+         }
+       },
        toggleAddonRequest(event) {

There's probably a better way to do this. Maybe we could revisit it another time.

@DNin01

This comment was marked as resolved.

Samq64 and others added 3 commits September 28, 2025 18:36
Not sure if this is any better than just manully handling keypresses
- Fix borders
- Fix Windows Narrator cursor
- Designate parent element as a radiogroup
@DNin01
Copy link
Member

DNin01 commented Sep 29, 2025

Not sure if this is any better than just manully handling keypresses

That's even better! The options are announced as radio buttons by the screen reader.

Copy link
Member

@DNin01 DNin01 left a comment

Choose a reason for hiding this comment

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

Everything so far looks OK to me now. The remaining todos could easily be saved for another PR, if you want.

@Samq64

This comment was marked as resolved.

@Samq64 Samq64 added the status: needs review PR needs 1 more approval label Oct 3, 2025
@Samq64 Samq64 requested a review from mxmou October 3, 2025 20:04
@mxmou mxmou removed the status: needs review PR needs 1 more approval label Oct 5, 2025
@Samq64

This comment was marked as resolved.

@Samq64 Samq64 added this to the v1.45.0 milestone Oct 6, 2025
@Samq64 Samq64 merged commit 14be621 into ScratchAddons:master Oct 15, 2025
2 checks passed
@Samq64 Samq64 deleted the settings-a11y branch October 15, 2025 21:21
@DNin01
Copy link
Member

DNin01 commented Oct 16, 2025

Now that this is done, I'll try to work on improving screen reader support and some other more specific UI interactions.

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

Labels

scope: accessibility Addresses an accessibility issue scope: webpages Related to the web pages (settings page, pop-up, etc)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Addon settings page can not be used properly with a keyboard or other accessibility tools

3 participants