UI/UX audit — BIG-register wizard (Rijkshuisstijl alignment)
Phase A deliverable. Audits the registration wizard and the shared atoms/chrome it
relies on against the project's design system (@rijkshuisstijl-community, the NL Design
System Rijkshuisstijl theme — the canonical system here; CIBG's own DS is not installed)
and WCAG 2.1 AA. Each finding maps to the token / component / pattern that resolves it.
This is an alignment, not a redesign: fixes go through the token/theme layer and
existing components; no new libraries, no restricted Rijksoverheid assets.
Scope: registratie wizard + shared atoms/chrome (intake/herregistratie inherit the shared
fixes). Their own step copy/inline styles are out of scope.
Priority: H = blocks "professional/accessible" bar (a11y or broken token); M =
visible inconsistency; L = polish.
1. Tokens vs. hardcoded values
The app imports RHC design tokens but the wizard/chrome were written with raw inline
values (≈48 in wizard+chrome, ≈16 in atoms). RHC exposes a full token set we should map
onto: spacing --rhc-space-max-{sm..5xl}, type --rhc-text-font-size-* /
--rhc-text-font-weight-*, color --rhc-color-foreground-{default,subtle} /
--rhc-color-{lintblauw,donkerblauw,cool-grey}-* / --rhc-color-wit, radius
--rhc-border-radius-*.
| # |
Pri |
Finding |
Resolves to |
| 1.1 |
H |
var(--rhc-color-grijs-700) is referenced in 3 wizards but --rhc-color-grijs-* does not exist in the package → the "Stap X van Y" text falls back to the default color, not grey. |
--rhc-color-foreground-subtle |
| 1.2 |
H |
site-header/site-footer set bg to var(--rhc-color-lintblauw-700,#154273) / …-900,#01689b — lintblauw-900 doesn't exist (only 50–700), so the hex fallback renders. |
header --rhc-color-lintblauw-700; footer --rhc-color-donkerblauw-700; text --rhc-color-wit |
| 1.3 |
M |
Raw spacing everywhere: 0.25/0.5/0.75/1/1.5/2/3rem as gap/margin/padding (≈30 occurrences). |
--rhc-space-max-{sm,md,lg,xl,2xl,3xl,5xl} |
| 1.4 |
M |
Inconsistent form/content widths: 28rem, 30rem, 32rem, 64rem as raw max-width. |
app width tokens in styles.scss (--app-form-max, --app-content-max) mapped once |
| 1.5 |
M |
spinner & skeleton hardcode greys/accent (#cad0d6, #e8ebee, #f3f5f6) and the spinner accent via bogus --rhc-color-lintblauw-700,#154273 fallback. |
--rhc-color-cool-grey-{200,300}, --rhc-color-lintblauw-700 |
| 1.6 |
L |
site-header hardcodes font-weight:700/400, font-size:0.9rem, opacity:0.85. |
--rhc-text-font-weight-{bold,regular}, --rhc-text-font-size-sm |
2. Typography & heading hierarchy
| # |
Pri |
Finding |
Resolves to |
| 2.1 |
H |
Each page has one <h1> (page-shell) but the wizard steps have no <h2> — step title is a plain <p>. Screen-reader users get no step heading to navigate to. |
per-step <h2> via app-heading [level]="2" |
| 2.2 |
M |
Type scale not applied to bespoke text (header wordmark uses raw sizes). |
--rhc-text-font-size-*, app-heading |
3. Color & contrast
| # |
Pri |
Finding |
Resolves to |
| 3.1 |
H |
Because of 1.1/1.2 the actual rendered colors are partly accidental (default text color, hex fallbacks) — contrast is unverified. |
map to real tokens, then verify AA |
| 3.2 |
M |
Palette discipline: confirm a single primary blue (lintblauw) + neutrals + status-only accents (alert types ok/info/warning/error already map to Utrecht alert variants). |
keep alert variants; route blues to lintblauw/donkerblauw tokens |
4. Spacing & layout
| # |
Pri |
Finding |
Resolves to |
| 4.1 |
M |
Repeated inline idioms: display:flex;gap:0.5rem;margin-top:1rem (button rows, 6×), max-width:30rem (forms, 3×), margin:1rem 0 (summaries). |
utility classes .app-button-row, .app-form, .app-stack in styles.scss |
| 4.2 |
L |
shell uses a custom --app-content-max:64rem defined inline. |
promote to the styles.scss token layer |
5. Component reuse
| # |
Pri |
Finding |
Resolves to |
| 5.1 |
L |
intake-wizard review step uses bespoke <div><dt><dd> instead of app-data-row (out of scope here; note for later). |
app-data-row |
| 5.2 |
M |
Button rows / form containers are ad-hoc inline layout rather than a shared idiom. |
utility classes (4.1) |
6. Form, validation & status patterns
| # |
Pri |
Finding |
Resolves to |
| 6.1 |
H |
form-field renders the error with role="alert" but no id, and the projected input has no aria-describedby → error not programmatically linked. |
error id="${fieldId}-error"; input aria-describedby |
| 6.2 |
H |
text-input never sets aria-invalid even when invalid() is true. |
[attr.aria-invalid] |
| 6.3 |
H |
radio-group has role="radiogroup" but no accessible name and no invalid state. |
aria-labelledby="${name}-label", add invalid input → aria-invalid/aria-describedby |
| 6.4 |
M |
Async states (app-async) render but aren't announced (no live region) — SR users miss loading→loaded/empty/error. |
wrap in aria-live="polite" + aria-busy |
| 6.5 |
L |
No error-summary pattern; per-field inline errors only. Acceptable for short steps; revisit if steps grow. |
(defer) |
7. Page chrome
| # |
Pri |
Finding |
Resolves to |
| 7.1 |
M |
No breadcrumb / location context in the portal chrome. |
new app-breadcrumb (.rhc-breadcrumb-nav), wired via page-shell |
| 7.2 |
M |
Step progress is non-semantic text. |
new app-stepper (<ol>, aria-current="step") |
| 7.3 |
L |
Skip-link uses left:-999px (works) and landmarks (header/main/footer) are correct. |
keep; tokenize offset |
8. Copy & tone
| # |
Pri |
Finding |
Resolves to |
| 8.1 |
M |
Microcopy is functional but informal/uneven (labels, helper text, button text, the manual-diploma warning, the controle summary). |
formal, plain official Dutch; consistent with domain terms (registratie wizard + chrome only) |
9. Accessibility (WCAG 2.1 AA) — consolidated
| # |
Pri |
Finding |
Resolves to |
| 9.1 |
H |
No focus management: after Next/Back focus stays on the button; SR/keyboard users aren't moved to the new step. |
move focus to the step <h2> (tabindex="-1") on cursor change |
| 9.2 |
H |
Error/label/invalid association missing (6.1–6.3). |
as above |
| 9.3 |
M |
Step changes & async states not announced (6.4). |
aria-live |
| 9.4 |
M |
skeleton placeholders are announced as content. |
aria-hidden="true" |
| 9.5 |
L |
No automated a11y in CI; only Storybook addon-a11y (axe) exists. |
add a11y parameters in preview.ts; keep manual keyboard/SR pass; no new deps |
10. Responsive
| # |
Pri |
Finding |
Resolves to |
| 10.1 |
M |
Fixed rem widths and inline flex rows; verify reflow + ≥24px targets at narrow widths. |
token widths + flex-wrap on button rows; manual check |
Tooling note
No eslint/stylelint/axe-core in the repo, and the prime directive forbids adding
dependencies. Token compliance is enforced by a no-dep check:tokens npm script
(greps touched templates for raw #hex and bare rem/px outside var(...));
accessibility uses the already-installed Storybook addon-a11y plus a manual
keyboard/screen-reader pass.