axe across every merchant surface โ
- What this is: the first accessibility sweep of the merchant portal. Nineteen surfaces, including every step of the offer builder and both tabs of each per-placement page.
- Split, and why: FIXED inside the offer builder, which the operator carved out of her redesign herself, so work there cannot be deleted by a direction choice. REPORTED everywhere else, because those screens may be rebuilt and a findings list informs a redesign where a patch would be thrown away.
- Raw results:
harness/axe-sweep-before.jsonandharness/axe-sweep-after.json, plusharness/axe-placements-before.jsonand its after, from the earlier nested-interactive fix. - Server:
http://localhost:3013, a throwaway portal on this branch. The operator's stack was not touched.
Where did it land? โ
| Before | After | |
|---|---|---|
| Surfaces scanned | 19 | 19 |
| Surfaces with no violations | 3 | 12 |
| Distinct rules failing | 5 (plus nested-interactive, fixed earlier the same day) | 1, and it is a shared token |
One rule is left, color-contrast, and every instance of it is one of two SHARED TOKENS. โ
Every structural and semantic failure in the merchant portal is now fixed. What remains is a design-system defect that lands on the admin portal too, so it is not this session's to change (see R1 and R2, and the note on why the obvious swap is wrong).
That is the useful result rather than the count. The merchant surfaces' own accessibility is clean; the residue is design-system level and lands on the admin portal too.
What was fixed, in the builder โ
F1. A second <main> nested inside the shell's <main>, on all eleven builder surfaces. โ
OfferForm.jsxrendered<main className="offer-form-v2__main">inside.admin-main, which is already the page's<main>.- Two rules at once:
landmark-no-duplicate-mainandlandmark-main-is-top-level, both moderate, both on every builder surface. - It is a scroll container, not a landmark. It is a
divnow.
F2. Two unnamed <nav> landmarks, on all eleven builder surfaces. โ
- The builder's step rail and the shell's sidebar are both
<nav>with no accessible name, solandmark-uniquefired: a screen reader listing the page's navigations offered two identical entries. - The builder's own rail is now
aria-label="Offer sections". The shell's was left alone, since naming one is enough and the other is the shell's to name.
F3. The per-placement page's title was a div, so the outline jumped h1 to h3. โ
- Every other builder step titles itself with
<h2 className="offer-section__title">. The per-placement page used<div className="placement-header__title">, soheading-orderfailed on all four per-placement surfaces. - It is an
h2now. The existing.offer-form-v2 h2rule already sets the same 20px and 600 weight the class did, so nothing moved visually.
F4, earlier the same day: nested-interactive, impact SERIOUS, on all three placement rows. โ
- Recorded here because it came from the same tool and is the reason this sweep happened. Detail in
offer-builder-0828.md.
What is reported, and not fixed โ
R1. --muted-dark-2 fails contrast wherever it is used as text. Fourteen nodes, five surfaces, impact serious. โ
| Where | Ratio | Needs |
|---|---|---|
.char-counter--ok on a card | 2.46 | 4.5 |
.review-item__value on the Review card | 2.57 | 4.5 |
.page-tabs disabled labels and .page-tabs__soon | 2.91 | 4.5 |
- The token is
--muted-dark-2: #52585f, defined inapps/admin/src/shared/styles/styles.cssand applied by shared rules, so this is one token-level defect, not fourteen. --muted(#9aa6b2) is a drop-in that passes: 6.98 on a card, 7.30 on the Review surface, 8.27 on black. Measured, not estimated.- Not fixed because it lands on the admin portal, and because scoping an override to the builder would leave the same token failing everywhere else while making it mean two different things.
R2. --secondary-accent-blue-500 misses 4.5 by a whisker on a card. Four nodes, three surfaces, impact serious. โ
.design-variants__headingrenders#0582caon#18181bat 4.26, against 4.5 for 15px normal weight. Thedesignskill names this exact trap: large text starts at 24px, or 18.66px bold, so a 15px heading needs the full 4.5.--secondary-accent-blue-400(#00A6FB) passes at 6.62 on the same background.- Not fixed: same reason. The rule lives in the shared sheet.
R3, now FIXED. Four merchant routes skipped a heading level. โ
- Every one had the same shape:
PageHeaderrenders the page'sh1, and the first content card rendered anh3, with noh2between. - Three sites, two files, all merchant code:
DetailSectionandPlaceholderCardinmerchant/tabs/shared/MerchantDetailFields.jsx(which covers Venues, Notes and Photos at once) and the three section titles inmerchant/tabs/Overview.jsx. - Held back at first because they sit outside the builder. Fixed once it was pointed out that a heading outline is structure, not styling, so a redesign changes the treatment and not the level.
- Nothing moved visually, and that is measured rather than assumed. All three classes set
font-size,font-weightandmarginexplicitly, so no browser default forh2could leak in. Live: 16px/600 on the two card titles, 12px/600 on the dashboard section title, margins exactly as declared. - Account and Settings were always clean: they carry an
h1and nothing else.
R4. axe cannot measure the builder's step rail, on all eleven builder surfaces. โ
incomplete: color-contraston.offer-sidebar__bead-numand.offer-sidebar__label: "background color could not be determined due to a pseudo element."- This is neither a pass nor a fail. It needs a human eye, and it is the one thing in this sweep a tool cannot settle. It was present before the fixes too.
What is worth knowing about the sweep itself โ
The first "after" run reported two routes clean that are not, and the record was corrected before it was committed. โ
- The sweep waited for the word "Loading" to disappear. Venues and Notes render their page header BEFORE the content card arrives, so axe scanned an
h1with noh3yet and found no heading to complain about. - Re-checked by dumping the real heading outline on each route, which is what caught it. The harness now waits for a content element instead.
- The same flaw means the BEFORE numbers for the report-only routes are undercounts: Account reads clean in the before file and shows six contrast failures once the card is actually there. Before and after are comparable for the ELEVEN BUILDER surfaces, which used a reliable wait throughout, and the after column is the number to trust for the rest.
What is related? โ
offer-builder-0828.md, the end-to-end builder run this grew out of.merchant-defect-survey-0828.md, the per-screen defects.