Skip to content

fix(a11y): landmarks, names, focus order and contrast fixes from the accessibility scan - #53

Merged
lannodev merged 10 commits into
lannodev:mainfrom
DuncanFaulkner:fix/a11y-scan-findings
Oct 19, 2026
Merged

lannodev merged 10 commits into
lannodev:mainfrom
DuncanFaulkner:fix/a11y-scan-findings

Conversation

@DuncanFaulkner

Copy link
Copy Markdown
Contributor

Follows up on #52. This covers the findings from the scan of the demo (/dashboard/nfts, /auth/sign-in, /components/table). Each fix is its own commit so you can take or drop them separately.

What changed

  • Landmarks and headings: Routed content is wrapped in <main> in the layout, auth and error shells, and the footer is now a <footer>. The page title is an h1 on the dashboard, table and cards pages. The NFT price in the dual card was the only h1 on the dashboard, so it is now an h3 with the same classes. "Showing 08 of 100 users" is a <p> instead of an h3.
  • Accessible names: Added aria-label to the icon-only buttons (sidebar collapse, sidebar and mobile submenu toggles, table row actions, pagination arrows, auction "view" buttons), the table checkboxes, the search input and the status, sort and page-size selects. Decorative SVG icons inside them are aria-hidden="true". The submenu toggles and the mobile menu button also expose aria-expanded, which is why menuService in navbar.component.ts is now public. The app has no i18n, so the labels are plain English strings like the rest of the templates.
  • Empty table headers: Added sr-only text to the five empty headers (select, actions, and the chart card's time, price and change columns).
  • Closed menus out of the tab order: Collapsed sidebar submenus, the mobile menu panel and the top bar dropdowns were hidden with max-height or opacity only, so their links could still be tabbed to. They now also get visibility: hidden while closed (invisible), so the open and close animation still runs. The top bar dropdowns open on keyboard focus (.dropdown:has(:focus-visible)) as well as hover, so they can be used from the keyboard. A mouse click on a trigger does not keep the menu open. There is no Escape to close them yet, since they are opened by CSS only. The mobile panel's transition is now transition-[opacity,transform,visibility] so visibility changes after the fade.
  • Contrast: see below.
  • Test: The navbar e2e test looked up a button named "Auth". The sidebar toggle now has that name too, so the lookup is scoped to app-navbar.

Contrast changes

  • Text that used text-muted-foreground/50 or /70 now uses text-muted-foreground (sidebar group titles, top bar items, footer, creator names, emails). Before: 1.97:1 light, 2.70:1 dark for /50; 2.71:1 light, 4.29:1 dark for /70. After: at least 6.08:1 light (on white or card), at least 7.43:1 dark (on the dark background or card).
  • The time range labels in the chart card used text-gray-400 (2.60:1 on white). They now use text-muted-foreground.
  • Light theme --muted-foreground goes from #64748B to #4F5D75. The old value was 4.34:1 on the card background and 3.33:1 on the muted background. The new value is 6.08:1 on card and 4.66:1 on muted (6.66:1 on white). The dark theme token is unchanged.
  • Top bar items now use text-muted-foreground at rest, so their hover colour is hover:text-foreground to keep a visible change on hover.
  • The hobby badges use dark:text-yellow-500 on the dark theme. Before: 2.50:1. After: 8.94:1. The light theme is unchanged.

Not changed: text in the primary colour (#E11D48) on the dark background (4.21:1), the version badge (4.00:1 light, 3.98:1 dark) and the "Forgot your password?" link in dark mode (4.21:1). Fixing these means changing the brand colour or adding a separate text colour token, so I left that as your call. Icons at text-muted-foreground/50 (the sidebar collapse button, menu icons, chevrons) are also unchanged.

I also added type="button" to the buttons I touched. None are in forms, so it only makes the intent explicit.

How it was checked

An automated axe-core scan (via Playwright) of the three routes in light and dark mode, on main and on this branch, plus @ngbracket/a11y-devtools report mode. Scripted keyboard checks with Playwright: Tabbing through the dashboard, opening and closing the sidebar submenus with Enter, opening the top bar dropdowns with focus, and checking a mouse click on a trigger doesn't leave the menu open. The existing Playwright e2e tests pass (8 of 8). No manual or screen reader testing was done.

Scan (3 routes) main This branch
Light mode violations 201 6
Dark mode violations 215 10
Tab stops that are hidden (dashboard) 35 0

What is left: the contrast items above, the dev-only responsive helper badge (not rendered in production) and the decorative panel on the auth page, both reported by the region rule.

ng lint is not configured in the repo and ng test does not start on main (/polyfills must be array), so those were not run. ng build passes.

Follow-up

These came up while testing but are outside the scan findings, so they're not in this PR. Happy to open a follow-up PR for them if you'd like:

  • The sidebar's parent menu entries are divs with routerLink and tabindex="0", but Enter doesn't activate them.
  • The profile menu items and the theme, mode and direction choosers are clickable divs or lis that can't be reached or used with the keyboard. The profile button has no aria-expanded.
  • The chart card's range pills and its menu icon are clickable divs.
  • The logo link has no href, so it isn't focusable.
  • The password visibility toggle on sign-in can't be reached with Tab.
  • The top bar dropdowns can't be closed with Escape.
  • The primary colour text below 4.5:1 listed above.

🤖 Generated with Claude Code

DuncanFaulkner and others added 10 commits October 17, 2026 16:55
Wrap routed content in <main> in the layout, auth and error shells.
Make the page title an h1 on the dashboard, table and cards pages
(the NFT price in the dual card was the only h1, now h3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add aria-label to icon-only buttons (sidebar toggle and submenu toggles,
table row actions, pagination arrows, auction view buttons), to the
table checkboxes, search input and filter, sort and page-size selects.
Mark decorative icons aria-hidden, expose aria-expanded on the menu
toggles and give the empty table headers screen reader text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closed sidebar submenus, the mobile menu and the top bar dropdowns were
only hidden with max-height or opacity, so their links stayed focusable.
Add visibility: hidden while closed (the transition keeps the animation
working) and open the top bar dropdowns on :focus-within as well as
hover so they remain usable from the keyboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add screen reader text to the three empty headers in the chart card
table. The 'Showing 08 of 100 users' text on the table page is a status
line, not a heading, so make it a paragraph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use the full muted-foreground colour instead of /50 and /70 opacity
variants and text-gray-400 for text, darken the light theme
muted-foreground token from #64748B to #4F5D75 so it also passes on the
card and muted backgrounds, and use yellow-500 for the hobby badges in
dark mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sidebar toggle buttons now have accessible names, so the 'Auth'
button name matched in both the sidebar and the navbar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use :has(:focus-visible) instead of :focus-within, so a mouse click on
a trigger doesn't leave its menu open after the pointer leaves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its label already says whether it collapses or expands the sidebar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lannodev
lannodev merged commit 590e146 into lannodev:main Oct 19, 2026
3 checks passed

This branch was successfully deployed

1 active deployment
Preview — 8cb2a0b7 Deployed Oct 19, 2026 by vercel[bot]
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.

2 participants