Improve toolbar accessibility - #1173
Open
brunoprietog wants to merge 11 commits into
Open
Conversation
brunoprietog
force-pushed
the
toolbar-a11y
branch
from
July 1, 2026 23:47
8bfdd14 to
098f2d1
Compare
brunoprietog
force-pushed
the
toolbar-a11y
branch
from
July 9, 2026 05:52
4236bf1 to
bc0badf
Compare
brunoprietog
force-pushed
the
toolbar-a11y
branch
from
July 9, 2026 11:59
bc0badf to
07f765e
Compare
brunoprietog
force-pushed
the
toolbar-a11y
branch
from
July 9, 2026 12:29
07f765e to
0d4051a
Compare
Arrow keys move along the bar with left/right and through an open menu with all four arrows, wrapping and trapped so they never leak out. Escape returns focus to the trigger when the menu was opened from the keyboard, otherwise to the editor.
Disabled buttons use aria-disabled instead of the native attribute, so the keyboard reaches them and focus never falls to the body when a button disables itself. A command activated from inside a dropdown returns focus to the editor instead of being stranded on the hidden panel.
aria-pressed is not valid on role="menuitem", so screen readers ignored the active state of the format and color menus. Block formats become menuitemradio and colors menuitemcheckbox, both exposing their state through aria-checked. The highlight CSS follows the new attribute.
The highlight menu rendered all eighteen color swatches with the same
"Aa" accessible name, so a screen reader user couldn't tell them apart
or know whether a swatch changed the text or the background.
Each color now carries a readable label (Yellow, Magenta, Sand, and so
on), and the swatches split into "Text color" and "Background color"
groups. The color config moves from bare CSS values to { value, label }
pairs and still accepts plain strings, so existing configurations keep
working. The native toolbar's initialized event exposes the same label
next to its name and value.
The formatting and color menus had no accessible name, so a screen reader announced them only as "menu". They now carry their trigger's name, matching what the link dialog already did. The overflow control drops "Show more toolbar buttons" for "More options", shorter and in line with the other triggers. Now that it's an icon button like the rest, it uses title instead of aria-label, which was a leftover from when it was a summary element with visible text. The dummy's external-toolbar example still used that old details/summary form, so it moves to lexxy-toolbar-dropdown to match the real component, and both example toolbars adopt the new wording.
The toolbar's SVG icons carried no aria-hidden, so each one surfaced as an anonymous img node inside its button. The button's name already comes from its title, so the icon adds nothing but noise. Marking the SVGs aria-hidden removes the stray img and leaves each button announced by its name alone.
Opening a dropdown with the mouse left its first item focused without a visible focus ring: a plain focus() moving in from a mouse click inherits the mouse modality, so the browser paints no ring until a keyboard interaction happens. Pass focusVisible so the ring shows right away. Browsers that don't support the option ignore it and keep the current behavior.
The row and column "more" menus were a native <details>, so they had no menu roles, arrow keys escaped to the surrounding controls, and Escape dropped out of the whole toolbar. Reuse the ToolbarDropdown the main toolbar already uses: decouple it from lexxy-toolbar through a small host contract that lexxy-table-tools also implements, and build each more menu as a trigger with aria-haspopup and a role="menu" panel of menuitem actions. Opening now focuses the first item with a visible ring, arrows stay in the menu and wrap, Escape closes it and returns to the trigger, and activating an action returns focus to the editor, like the main toolbar.
brunoprietog
force-pushed
the
toolbar-a11y
branch
from
August 4, 2026 22:57
0d4051a to
dee2f55
Compare
brunoprietog
force-pushed
the
toolbar-a11y
branch
from
August 5, 2026 01:06
dee2f55 to
7771c81
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The toolbar was only partly usable for keyboard and screen reader users. Arrow keys let focus escape the toolbar instead of staying within it, open menus didn't confine navigation to their items, and Escape didn't return focus predictably. Active states were exposed with
aria-pressed, which isn't valid onmenuitemelements, and the eighteen color swatches all shared the same "Aa" accessible name, so there was no way to tell them apart or know whether one changed the text or the background.So this PR:
aria-disabledso they stay reachable.<details>, so they had no menu roles and arrow keys escaped to the surrounding controls; now theToolbarDropdownis decoupled fromlexxy-toolbarthrough a small host contract thatlexxy-table-toolsalso implements, and each more menu is a trigger witharia-haspopupand arole="menu"panel ofmenuitemactions, with arrow navigation that stays inside, Escape back to the trigger, and focus returning to the editor after an action.aria-checkedinstead ofaria-pressed:menuitemradiofor block formats, where one is always active, andmenuitemcheckboxfor color swatches, which toggle on and off.{ value, label }pairs and still accepts plain strings.Basecamp card: https://app.basecamp.com/2914079/buckets/47229151/card_tables/cards/9995277043