Skip to content

[Improvement]: A11y — focus-visible ring, aria-label on icon buttons, tablist for ActivityBar and TabBar#176

Open
matiaspalmac wants to merge 1 commit intoTrixtyAI:mainfrom
matiaspalmac:improvements/a11y-focus-tablist
Open

[Improvement]: A11y — focus-visible ring, aria-label on icon buttons, tablist for ActivityBar and TabBar#176
matiaspalmac wants to merge 1 commit intoTrixtyAI:mainfrom
matiaspalmac:improvements/a11y-focus-tablist

Conversation

@matiaspalmac
Copy link
Copy Markdown
Contributor

Description

The app's shell had three overlapping a11y gaps that together made the chrome unusable for keyboard and screen-reader users:

  1. Icon-only buttons (window controls in TitleBar, BottomPanel's close, every ActivityBar entry) had no accessible name — AT announced them as "button" with no further context.
  2. No visible focus indicator anywhere. Every control styled hover but nothing on keyboard focus, so keyboard users could not tell which control they had landed on.
  3. ActivityBar and TabBar looked like tablists but were not — no role=tablist / role=tab, no aria-selected, no grouping — so AT got no structural cue and announced each button in isolation.

Related Issue

Closes #104

Change

  • ActivityBar — container is role="tablist" aria-orientation="vertical" with an aria-label; each entry (dynamic plugins, Extensions, Settings) is role="tab" with aria-label and aria-selected. Decorative active-indicator pip and hover tooltips are aria-hidden="true".
  • TabBarrole="tablist" container, each open file is a role="tab" with aria-selected, an aria-label that appends an unsaved/modified cue, tabIndex={0}, and Enter/Space activation. The per-tab close button carries aria-label="Close {file}".
  • TitleBararia-label on the right-panel toggle (plus aria-pressed so AT reflects the state), minimize, maximize/restore (label flips with state), and close. The vertical separator line is aria-hidden="true".
  • BottomPanelaria-label on the close button.
  • Focus style — every touched control now carries focus-visible:ring-2 focus-visible:ring-white/40 (inset on titlebar controls and tab-bar items so the ring stays inside its rail). focus-visible (not focus) keeps the ring out of the way on mouse click.

New i18n keys: activitybar.label, tabbar.label, tab.close_aria (EN + ES).

Trade-offs

  • The WAI-ARIA tablist pattern technically wants arrow-key roving-tabindex between tabs. I deliberately did not introduce that here: both bars already contain buttons that are toggles (clicking the active ActivityBar tab closes the sidebar; clicking a TabBar tab may be preceded by other keyboard affordances), and roving-tabindex would have required refs into dynamic plugin lists plus custom keyboard handlers per bar. The current result — Tab lands on each entry, Enter/Space activates, aria-selected announces state — is a strict improvement that's safe to land now; arrow navigation is a clean follow-up.
  • Kept title={...} tooltips in parallel with aria-label={...} so hover tooltips still render for sighted users (title alone is not exposed reliably to AT, which is why aria-label had to be added).

Verification

  • pnpm --filter @trixty/desktop lint passes clean.
  • npx tsc --noEmit passes clean.
  • Manually walked the chrome with Tab/Shift+Tab and verified the ring shows up on every stop; announced names/states in the accessibility inspector match the intended labels.

Checklist

  • I have followed the project's coding guidelines.
  • Documentation has been updated (if applicable).
  • My changes generate no new warnings or errors.
  • This change is a minor improvement (for major new features use the Feature template).

…tablist for ActivityBar and TabBar

The app's shell had three overlapping a11y gaps in keyboard and AT
land:

1. Icon-only buttons (window controls, bottom-panel close,
   ActivityBar entries) had no accessible name — AT announced them
   as "button" with no further context.
2. There was no visible focus indicator. Every button used
   `transition-colors` on hover but nothing on keyboard focus, so
   keyboard users could not tell where they were.
3. ActivityBar and TabBar looked like tablists but lacked roles, so
   AT had no grouping and no selection state.

Changes:
- **ActivityBar**: the container is `role=tablist aria-orientation=vertical`
  with an `aria-label`; each entry (plugins, Extensions, Settings) is
  `role=tab` with `aria-label` and `aria-selected`. Decorative
  indicator pip and hover tooltips are `aria-hidden`.
- **TabBar**: `role=tablist` container, each open file is a `role=tab`
  with `aria-selected`, `aria-label` (including an "unsaved" suffix
  for modified files), `tabIndex=0`, and Enter/Space activation.
  The per-tab close button now has `aria-label="Close {file}"`.
- **TitleBar**: `aria-label` on the right-panel toggle (with
  `aria-pressed`), minimize, maximize/restore, and close buttons.
  Decorative separator is `aria-hidden`.
- **BottomPanel**: `aria-label` on the close button.
- Global focus style: all five touched buttons/tabs now carry
  `focus-visible:ring-2 focus-visible:ring-white/40` (inset on
  titlebar controls and tab-bar items to stay inside their rail).
- New i18n keys: `activitybar.label`, `tabbar.label`, `tab.close_aria`
  (EN + ES).

Closes TrixtyAI#104
@github-actions github-actions bot added documentation Improvements or additions to documentation enhancement New feature or request labels Apr 21, 2026
@github-actions
Copy link
Copy Markdown

Thanks for the contribution! I'll review it as soon as possible. If you have still changes, please mark this PR as draft and all reviews will be cancelled. Tests reviews will be re-run only when the PR is marked as ready for review.

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

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Improvement]: A11y — focus ring, aria-label on icon buttons, and tablist pattern for ActivityBar / TabBar / BottomPanel

1 participant