Accessibility Audit — YVV Form

Web Interface Guidelines & WCAG 2.2 AA · 2026-05-13T16:51:54Z · 0 critical · 1 warning · 2 info · 28 passed

Mchael Poncardas (m@poncardas.com)

Audit history: Initial (7 critical, 12 warnings)Post-fix #1 (0 critical, 2 warnings)This audit
Metric Initial Post-fix #1 This audit Total Δ
Critical 7 0 0 −7
Warnings 12 2 1 −11
Info 9 2 2 −7
Passed 15 27 28 +13
0
Critical
1
Warning
2
Info
28
Passed

Findings Distribution

Pass (28) Info (2) Warning (1) Critical (0)

Remaining Warnings 1

app/[locale]/form/_components/renderers/file-step.tsx

WARNING file-step.tsx:118–126
The visible "Choose files" Button triggers a hidden <input type="file"> via onClick={() => inputRef.current?.click()} with aria-controls="file-upload-input" now present. While this improves programmatic association, the hidden-input proxy pattern remains second-class for some assistive technologies. The file input is sr-only (positioned off-screen) and some AT may not follow the click-through to open the native file dialog.
WCAG 4.1.2 Name, Role, Value
Recommended action: Test with VoiceOver (macOS/iOS) and NVDA (Windows) to confirm the file dialog opens when activating the button. If issues arise, consider switching to a visible styled file input using ::file-selector-button pseudo-element as a more natively accessible alternative. This is the only remaining warning in the entire form.

Remaining Info / Best Practice 2

app/[locale]/form/_components/form-wizard.tsx

INFO form-wizard.tsx — URL state
The current wizard step is not reflected in the URL. Users cannot bookmark or share a deep link to a specific step. The browser back button navigates away from the form entirely rather than between steps. See docs/roadmap.md for the proposed implementation plan with nuqs.

app/[locale]/form/_components/renderers/review-step.tsx

INFO review-step.tsx:318–330
The submit action sends data permanently to the server with no confirmation dialog or undo window. The Review step acts as implicit confirmation, but there is no explicit modal guard before the final irreversible action. This is a product/UX decision — some teams consider double-confirmation excessive friction.

Passed Checks ✓ 28 passed

PASS Skip link present
form-wizard.tsx — <a href="#form-content"> with sr-only/focus-visible styling. ✓
PASS Live regions for step changes
progress-announcer.tsx — role="status" aria-live="polite" aria-atomic="true". ✓
PASS Validation errors use aria-live
navigation.tsx — role="alert" aria-live="assertive". ✓
PASS File upload errors announced
file-step.tsx — role="alert" aria-live="assertive". ✓
PASS Submit errors announced
review-step.tsx — role="alert" aria-live="assertive". ✓
PASS Decorative icons have aria-hidden
All Lucide icons include aria-hidden="true" where decorative. ✓
PASS Semantic HTML for controls
All interactive elements use <button>; links use <a>. No <div onClick>. ✓
PASS Headings are hierarchical
Intro: h1→h2. Steps: h2. Review: h3. Confirmation: h2→h3. ✓
PASS Radio labels have explicit htmlFor/id
radio-step.tsx, grouped-radio-step.tsx — explicit htmlFor/id linking. ✓
PASS Checkbox labels use htmlFor/id
checkbox-step.tsx, grouped-checkbox-step.tsx — explicit linking. ✓
PASS Grouped fieldsets have aria-labelledby
grouped-radio-step.tsx, grouped-checkbox-step.tsx — aria-labelledby on fieldsets. ✓
PASS External links disclose new-window behavior
intro-step.tsx, confirmation-step.tsx — all external links have sr-only "opens in a new window". ✓
PASS Brand names/URLs have translate="no"
confirmation-step.tsx, intro-step.tsx — all brand names and phone numbers wrapped. ✓
PASS Focus-visible rings on interactive elements
Button, Input, Textarea, Radio, Checkbox all have focus-visible:ring styles. ✓
PASS Step heading has focus-visible ring (not bare outline-none)
form-wizard.tsx — focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 rounded-sm. ✓
PASS File remove button has focus-visible ring
file-step.tsx — focus-visible:ring-2 focus-visible:ring-ring/50 focus-visible:outline-none. ✓
PASS Form inputs have name and autoComplete
text-step.tsx, radio-step.tsx, checkbox-step.tsx, grouped-*.tsx — all inputs have name + autoComplete="off". ✓
PASS Date picker has descriptive aria-label
date-step.tsx — aria-label={t("date_step.aria_pick_date")}. ✓
PASS Date hint associated with control
date-step.tsx — aria-describedby="date-step-hint". ✓
PASS File upload errors use i18n
file-step.tsx — t("file_upload.error_type"), t("file_upload.error_size"), t("file_upload.error_generic") with en/fi/sv translations. ✓
PASS Textarea placeholder uses i18n
text-step.tsx — t("text_step.placeholder_optional"). ✓
PASS File upload hint uses i18n
file-step.tsx — t("file_upload.hint"). ✓
PASS Loading states end with ellipsis character
en.json: "Sending…", fi.json: "Lähetetään…", sv.json: "Skickar…" (all use U+2026). ✓
PASS prefers-reduced-motion honored
globals.css — @media (prefers-reduced-motion: reduce) resets all animation/transition durations. ✓
PASS No transition-all anti-pattern in form/UI components
progress.tsx: transition-[width]. badge.tsx: transition-colors. Form step cards: transition-colors. ✓
PASS beforeunload guard for unsaved data
form-wizard.tsx — warns before navigating away when on a non-intro, non-confirmation step. ✓
PASS Destructive action has confirmation dialog
clear-data-button.tsx — AlertDialog with title, description, cancel, confirm. ✓
PASS touch-action: manipulation set
globals.css — on buttons, links, labels with for attribute. ✓
PASS AlertDialog has overscroll-behavior: contain
alert-dialog.tsx — overscroll-contain on content. ✓
PASS Number formatting uses Intl.NumberFormat
file-step.tsx — Intl.NumberFormat(locale) with non-breaking spaces. ✓
PASS Review dates locale-formatted
review-step.tsx — Intl.DateTimeFormat(locale, { dateStyle: "long" }). ✓
PASS Color contrast (WCAG AA)
CSS variables explicitly darkened — comments confirm WCAG compliance. ✓
PASS lang attribute on <html>
layout.tsx — <html lang={locale}>. ✓
PASS No user-scalable=no / maximum-scale=1
No viewport meta disabling zoom found. ✓
PASS color-scheme set
globals.css — color-scheme: light. ✓
PASS Theme-color meta tag present
layout.tsx — other: { "theme-color": "#fcfaf9" }. ✓
PASS Loading/skeleton state accessible
form-wizard.tsx — aria-busy="true" + aria-live="polite" + sr-only text. ✓
PASS Progress bar screen-reader accessible
progress-bar.tsx — <nav aria-label="Form progress"> + sr-only live region. ✓
PASS Programmatic focus on step change
form-wizard.tsx — heading receives .focus() on step change. ✓
PASS No paste blocking
No onPaste + preventDefault found anywhere. ✓
PASS No icon buttons without aria-label
All icon-only interactive elements have aria-label. ✓