Files
atomic-design-poc/docs/project/readable-codebase/RD-37-a11y-suppressions.md
T
ehoandClaude Opus 5 097e8468e0 fix: keuzelijst rows become the <li> (RD-37)
app-choice-link rendered a component host between the keuzelijst <ul>
and its <li>. This broke the axe list/listitem rule for assistive
technology. Five story suppressions named WP-11 as the fix, but WP-11
closed with no open ticket left to own the defect.

choice-link now uses selector: 'li[app-choice-link]', the same
attribute-host pattern as application-link. The host carries the
keuzelijst__list-item class; the template drops its own <li>.
Position: relative stays on .keuzelijst__link so the stretched-link
overlay still resolves against the card, not the host.

aanvraag-block needed no component change: it renders a CIBG melding,
never an <li>. Only its story wrapped it in a <ul>, which is what axe
rejected. The wrapper is removed, and the four non-Concept stories are
deleted — the component's template only renders for status Concept, so
they rendered nothing.

All five a11y: { disable: true } suppressions are gone, with no
replacement. atomic-design.mdx now records that both molecules are the
<li>, kept separate for the vendored CSS they bind, not for list
semantics.

npm run ci --full passes, axe included.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 22:51:04 +02:00

6.8 KiB

RD-37 — Five a11y suppressions name a ticket that closed

Status: done Phase: 5 — fix the docs that describe this flow

Why

Five stories carry a11y: { disable: true }. Four of the reasons say "WP-11 (CIBG markup fidelity) reworks this markup". WP-11 is Status: done, and so is WP-13, the gap register that WP-11 handed its remainder to. No open ticket owns the defect. The README rule — "no check disabled without a reference to the ticket that removes it" — holds only in letter.

RD-30 archives docs/project/backlog/. This ticket runs first, so the archive move does not rewrite five paths that must disappear.

The defect is shipped, not story-only. app-choice-link renders a component host between the keuzelijst <ul> and its <li>. This breaks the axe list/listitem rule for assistive technology. display: contents does not repair it.

Read first

  • docs/project/readable-codebase/PLAN.md, phase 5 item 0 (line 790).
  • libs/shared/src/ui/molecules/choice-link/choice-link.component.ts — the defect.
  • libs/shared/src/ui/molecules/application-link/application-link.component.ts:18 — the precedent. WP-11 made the host be the <li>. That component is axe-clean today.
  • libs/shared/docs/atomic-design.mdx:113 — the convergence table row that calls the split deliberate.

The question this ticket had to answer first

Does an li[…] attribute host still match the vendored CIBG keuzelijst CSS?

Yes. Verified against public/cibg-huisstijl/css/huisstijl.css. Every keuzelijst rule keys off a bare class:

.keuzelijst__list{padding-left:0}
.keuzelijst__list-item{list-style:none;margin-bottom:1.5rem;position:relative}
.keuzelijst__link{…}
.keuzelijst__link:after{…}   .keuzelijst__link:focus,.keuzelijst__link:hover{…}

There is no ul > li child combinator and no li a descendant chain. This is the difference from the aanvragen pattern, whose vendored chain is .dashboard-block.applications li a. An attribute host on the <li> therefore keeps every keuzelijst selector matching, as long as the class keuzelijst__list-item moves to the host element.

Decisions (pre-made, do not relitigate)

  1. choice-link becomes selector: 'li[app-choice-link]'. The host carries the class through host: { class: 'keuzelijst__list-item' }. The template drops its outer <li>. :host { display: contents } goes away, because the host is now the list item.
  2. Keep position: relative on .keuzelijst__link. The title is a .stretched-link. Its ::after overlay must resolve against the card, not against the <li>. Do not move that rule to the host.
  3. aanvraag-block needs no component change. Its suppression reason is wrong. The component renders a CIBG melding (app-alert), never an <li>. Only the story's meta render wraps it in <ul class="keuzelijst__list">, and that wrapper is what axe rejects. Production agrees: mijn-aanvragen.section.ts:44 renders the block for concepten_() only, outside any list. Delete the wrapper from the meta render.
  4. Delete the four non-Concept stories in aanvraag-block.stories.ts. The whole template sits inside @if (aanvraag().status.tag === 'Concept'), so InBehandelingAuto, InBehandelingManual, Goedgekeurd and Afgewezen render nothing at all. Keep Concept, and give it the meta render. Submitted and resolved aanvragen render through application-link, which has its own stories.
  5. Correct atomic-design.mdx:113. After this ticket both molecules are the <li>. The pair stays separate because they bind different vendored patterns, not because of list semantics. Rewrite that half of the cell; keep the verdict.
  6. All five suppressions go. No suppression, no replacement ticket. If ci --full still reports a violation, stop and report it. Do not re-add a disable.

Files

  • libs/shared/src/ui/molecules/choice-link/choice-link.component.ts — host, class, template, the header comment.
  • libs/shared/src/ui/molecules/task-list/task-list.component.ts:25 — the one production call site: <app-choice-link …/> becomes <li app-choice-link …></li>.
  • libs/shared/src/ui/molecules/choice-link/choice-link.stories.ts — call site + suppression.
  • libs/shared/src/ui/molecules/choice-list/choice-list.stories.ts — call sites + suppression.
  • libs/shared/src/ui/molecules/task-list/task-list.stories.ts — suppression.
  • apps/ssp/src/app/registratie/ui/dashboard/wat-moet-ik-regelen.section.stories.ts — suppression.
  • apps/ssp/src/app/registratie/ui/aanvraag-block/aanvraag-block.stories.ts — wrapper, four stories, suppression.
  • libs/shared/docs/atomic-design.mdx — the convergence row.

Steps

  1. Convert choice-link to the li[…] host.
  2. Update the one production call site and the two story templates. An attribute host needs a closing tag: <li app-choice-link …></li>, not a self-closing element.
  3. Fix aanvraag-block.stories.ts per decisions 3 and 4.
  4. Delete all five a11y: { disable: true } blocks and their comments.
  5. Correct the atomic-design.mdx row.
  6. Run npm run ci --full.

Acceptance criteria

  • No a11y: { disable: true } remains in apps/ or libs/.
  • grep -rn "WP-11" apps libs returns nothing that points at the archived backlog.
  • npm run ci --full is green, axe included.
  • The keuzelijst still looks unchanged: chevron, hover accent, focus accent, and the non-interactive --static row.

Verification

  1. npm run ci --full.
  2. npm run storybook — Choice Link, Choice List, Task List and Wat Moet Ik Regelen run with the a11y addon on. Check the three Choice Link stories by eye: Navigatie, Actie and NietInteractief must keep their current appearance.
  3. npm start, open http://localhost:4200/dashboard — the "Wat moet ik regelen" list renders as before, and each row is still clickable over its whole surface.

Out of scope

  • application-link and application-list. Both are already axe-clean.
  • The --static modifier and the CIBG-gap register. This ticket moves a host element; it adds no new hand-rolled surface.

Risks

  1. A self-closing attribute host silently renders nothing. Angular needs <li app-choice-link></li>. The build does not fail; the row disappears. Check the dashboard by eye, per Verification step 3.
  2. stretched-link covers the wrong box. If position: relative lands on the host instead of on .keuzelijst__link, the whole <li> becomes the click target, including its margin-bottom. Keep the rule where it is.
  3. The choiceActions slot must stay above the overlay. It projects inside .keuzelijst__link and relies on its own position: relative; z-index: 2 at the call site. The host move must not change the projection point.