fix(a11y): give every wa-dialog an accessible name
Build & publish Arch package / arch-package (push) Successful in 2m0s
CI / check (push) Successful in 2m53s
Search index maintenance / maintain-index (push) Successful in 6s
CI / e2e (push) Successful in 4m47s

Eleven dialogs passed a `label` that never reached the accessibility
tree: Web Awesome renders it into an <h2 id="title"> in the same shadow
root as the native <dialog> and never points aria-labelledby at it, so
getByRole('dialog', {name}) matched nothing and a screen reader
announced an unnamed dialog. a11y.md lists all of them under "what is
already correct".

utils/name-dialog.ts sets the IDREF, with aria-label as the fallback for
without-header (first-run-wizard), called from each host's updated().
aria-labelledby rather than aria-label because three call sites compute
their label at render time, and the heading re-renders anyway. It waits
for the dialog's own first update: wa-dialog populates its shadow root
in its own update, so a query at the host's firstUpdated names nothing.

Reaching into another library's open shadow root is deliberate and the
failure is bounded — if the structure moves, the query misses and the
dialog is as unnamed as it was.
This commit is contained in:
2026-08-12 14:52:23 -04:00
parent d681a7223e
commit 287b6445fa
14 changed files with 492 additions and 14 deletions
+14 -4
View File
@@ -18,7 +18,7 @@ here has disappeared.
## Read this part before you fail
Nine things cost a cycle each the first time. They are here, not in a
Fourteen things cost a cycle each the first time. They are here, not in a
reference, because you need them *before* the failure, not after.
- **Time out every binding call.** A bound Go method called with wrong
@@ -49,9 +49,19 @@ reference, because you need them *before* the failure, not after.
reports hidden; the visible thing is the native `<dialog>` in its
shadow root. The slotted content is in the *host's* shadow root, not
in that dialog's subtree, so `toContainText` on the dialog sees only
its chrome. And the dialog has **no accessible name** — Web Awesome
never wires `label` to `aria-labelledby` — so
`getByRole('dialog', {name})` matches nothing.
its chrome. And it has an accessible name **only because
`utils/name-dialog.ts` gives it one** — Web Awesome does not wire
`label` to `aria-labelledby` — so a new dialog that forgets to call
the helper from `updated()` is invisible to
`getByRole('dialog', {name})`.
- **The a11y snapshot cannot check an accessible name on a dialog.**
`playwright-cli snapshot` prints `- dialog [ref=…]` with no name
whether the dialog is named by `aria-labelledby`, by `aria-label`,
or not at all — checked all three ways against the running app. Use
`getByRole('dialog', {name})` in a spec, or CDP
(`Accessibility.getFullAXTree`) for the browser's own computation,
which also reports *where* the name came from. A snapshot read as a
probe here reports failure on a working build.
- **Playwright's WebKit does not run on Arch** (Ubuntu-only libs).
`--browser=webkit` is CI-only; local work is Chromium. CI runs it
with `if: !cancelled()` so a chromium failure does not silently