Files
yellowjacket/frontend/src/styles/wa-slider-label.css.ts
T
logan b7831e3f15 fix(a11y): name the sliders and the progress bar where the role is
`a11y.md` lists `seek-bar` and `volume-control` under "what is already
correct" because both pass `aria-label`. Measured with
Accessibility.getFullAXTree against the running app on all eleven
views, both sliders compute a name of "": `wa-slider` puts
role="slider" on a div inside its own shadow root, pointing
aria-labelledby at an empty internal <label>, and that IDREF outranks
the host's aria-label. `volume-control` did not have the aria-label the
audit credits it with at all.

The name comes from `label` now, which is the library's own API — and
for a slider that is visible, so `styles/wa-slider-label.css.ts` hides
it by part. Preferred over reaching into the shadow root the way
name-dialog.ts must: if Web Awesome renames the part the label becomes
visible rather than silently nameless. The second rule in that file is
load-bearing — `#slider` takes an 8px margin the moment a label exists,
which grows the bar from 6px to 14px and moves the transport with it.

a11y.25 is the same family: wa-progress-bar maps `label` onto its inner
aria-label, falling back to the localised word "progress" — so it was
named after the widget rather than after the work, not unnamed.

The existing transport test asserted the host's aria-label and called
it an accessible name, so it was pinning the bug.
2026-08-13 01:48:43 -04:00

42 lines
1.9 KiB
TypeScript

import { css } from 'lit';
/**
* A `wa-slider` is named by its `label`, and its `label` is visible.
*
* The same trap as `utils/name-dialog.ts`, one component over, and this
* one the audit filed under "what is already correct": `a11y.md` lists
* `seek-bar` and `volume-control` as exemplary because they pass
* `aria-label`. The role is not on the host. Web Awesome renders a
* `<div id="slider" role="slider" aria-labelledby="label">` inside its
* own shadow root, pointing at an internal `<label id="label">` that is
* empty unless the `label` property is set — and `aria-labelledby`
* outranks the host's `aria-label`, which the AX tree never sees. Both
* sliders in this app computed a name of `""`. Measured with
* `Accessibility.getFullAXTree` against the running app, on all eleven
* views; `volume-control` did not even have the `aria-label` the audit
* credits it with.
*
* So the name comes from `label`, which is the library's own API, and
* this hides it. That is preferred over reaching into the shadow root
* the way `name-dialog.ts` has to, for the failure mode: if Web Awesome
* renames these parts the label becomes *visible* — wrong-looking and
* correctly named — rather than silently nameless again.
*
* The second rule is not decoration. `#slider` takes an 8px
* `margin-block-start` as soon as a label exists, so hiding the label
* alone still grows the control from 6px to 14px and moves the transport
* bar. `display: none` on the label is deliberate and safe: an element
* referenced by `aria-labelledby` contributes its text even when it is
* hidden, which is exactly the accname rule this relies on (verified —
* the slider reports "Seek" with the label displaying nothing).
*/
export const waSliderLabel = css`
wa-slider::part(label) {
display: none;
}
wa-slider::part(slider) {
margin-block-start: 0;
}
`;