Skip to content

Fix pagination + demo-page accessibility issues (no shipped-component changes for duplicate ids)#280

Open
FilisLiu wants to merge 4 commits into
mainfrom
FilisLiu/pa11y-form-a11y-fixes
Open

Fix pagination + demo-page accessibility issues (no shipped-component changes for duplicate ids)#280
FilisLiu wants to merge 4 commits into
mainfrom
FilisLiu/pa11y-form-a11y-fixes

Conversation

@FilisLiu

@FilisLiu FilisLiu commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What?

Resolves the remaining pa11y/axe findings on the form and component demo pages (follow-up to the code-highlighter contrast fix in #277).

  • Pagination (aria-prohibited-attr) — the disabled prev/next state rendered a role-less <span> carrying an aria-label, which is prohibited there. Removed it; the visible title plus visually-hidden text already provide the accessible name. (This is the only shipped-component change.)
  • Duplicate ids (duplicate-id-aria) — fixed in the dummy app only:
    • component_preview now prefixes element ids (and the attributes that reference them) per preview, so pages that preview the same component several times (e.g. notification_banner rendering multiple alerts/notices) no longer emit duplicate ids in the live DOM. The displayed code sample keeps the original ids.
    • assistants/_form demoed ds_hidden_field on :title, which already had a visible text field; it now uses a dedicated virtual reference_code attribute so the hidden field and its label get their own id.

Why?

Duplicate ids and prohibited ARIA attributes are WCAG failures (SC 4.1.1 / 4.1.2) and were failing the pa11y suite. The duplicate ids only arose because the demo pages preview the same component multiple times on one page — not a defect in real single-use of the components — so the fix is deliberately confined to test/dummy rather than changing the shipped builders.

How?

Pagination fix in the generic pagination renderer. Everything else is under test/dummy: a per-preview id-uniquifier in component_preview, plus a virtual demo attribute for the hidden-field example.

Testing?

  • Full test suite passes (318 runs, 0 failures); RuboCop adds no new offenses.
  • Verified with axe against a local build: notification_banner, checkboxes, assistants/new and pagination no longer report duplicate-id-aria / aria-prohibited-attr.

Anything Else?

Note on commit history: an earlier commit on this branch fixed the notification-banner duplicate id by giving the shipped component a random per-instance id; a later commit reverts that and moves the fix into the dummy app instead (the shipped notification builder is unchanged vs main). Squash-merging gives a single clean diff.

The radio/checkbox label colour-contrast items are intentionally left out — they are an axe needs-review false positive ("background could not be determined due to a pseudo element"); the label text passes and opaque backgrounds do not clear it.

FilisLiu and others added 3 commits July 2, 2026 10:33
Two axe/pa11y findings that reflect real component defects (not demo
artifacts):

- Pagination: the disabled prev/next state rendered a role-less <span>
  carrying an aria-label, which is prohibited (aria-prohibited-attr). The
  visible title plus visually-hidden text already provide the accessible
  name, so the aria-label is removed.
- Notification banner: ds_alert / ds_notice hardcoded the title id used by
  aria-labelledby (error-summary-title / <brand>-notification-banner-title),
  so two banners on one page produced duplicate ids (duplicate-id-aria).
  The id is now unique per instance and the aria-labelledby reference
  follows it.

Tests updated to assert the id/aria-labelledby linkage by prefix rather
than a hardcoded value. Verified with axe: both pages now report zero
violations and zero incompletes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dummy app pages rendered the same model attribute more than once on a
single page, producing duplicate DOM ids that axe/pa11y flagged as
duplicate-id-aria:

- components/checkboxes previewed `terms_agreed` twice. The second preview
  now uses a demo-only `scope:` so its generated ids differ; the override is
  stripped from the displayed source (via hide_demo_attributes) so the
  example code stays clean.
- assistants/_form demoed ds_hidden_field on `:title`, which already had a
  visible text field. It now uses a dedicated virtual `reference_code`
  attribute, so the hidden field and its label get their own id.

Verified with axe: assistants/new and components/checkboxes no longer report
duplicate-id-aria. Full test suite passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Earlier on this branch the notification component was changed to generate a
random per-instance title id. That is the wrong tradeoff: the duplicate ids
only arose because the demo pages preview the same component several times,
so the shipped component should not carry a random id.

This reverts the notification builder (and its tests) back to their original
hardcoded ids, and instead fixes the duplication in the dummy app only:
component_preview now prefixes element ids (and their references) per
preview, so a page that previews a component multiple times no longer emits
duplicate ids in the live DOM. The checkbox demo's scope: workaround is also
removed, since the preview helper now handles it generally.

Verified with axe: notification_banner and checkboxes report no
duplicate-id-aria. Full test suite passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@FilisLiu FilisLiu changed the title Fix form/component accessibility issues (duplicate ids, prohibited aria) Fix pagination + demo-page accessibility issues (no shipped-component changes for duplicate ids) Jul 6, 2026
…hors

The per-preview id-uniquifier introduced two regressions on pages it hadn't
been exercised against:

- Radio/checkbox values containing spaces (e.g. "Jacket potato") produce ids
  with spaces. Splitting the label's `for` on whitespace failed to remap it,
  orphaning the label (axe: "Form elements must have labels"). `for` (and
  other single-idref attributes) are now mapped as a whole value.
- The tabs component derives `aria-controls` from each tab's `href` in JS at
  runtime. Renaming the panel id without updating the in-page `href` left a
  dangling reference (axe: aria-valid-attr-value). In-page anchor hrefs that
  point at renamed ids are now remapped too.

Refactored into small helpers to keep complexity down. Verified with axe:
tabs and radios no longer report those errors; notification_banner and
checkboxes remain clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant