Accessibility¶
Status: Audit v0.1 (2026-05-09).
1. WCAG compliance posture¶
No documented WCAG compliance level is claimed. The codebase has no a11y conformance statement, no documented WCAG audit, and no published VPAT.
For the master TDD, the working assumption should be: no formal WCAG conformance has been verified. Remediation is required before claiming "WCAG 2.1 AA" to enterprise customers.
2. Automated checks¶
| Tool | Configured? | Source |
|---|---|---|
eslint-plugin-jsx-a11y |
No (and would be inapplicable — codebase is Vue, not React) | package.json deps grep |
eslint-plugin-vuejs-accessibility |
No | Same |
axe-core / vitest-axe |
No | Same |
pa11y / pa11y-ci |
No | Same |
| Lighthouse a11y in CI | No | .github/workflows/dev_app.yml runs no Lighthouse |
Finding [WC-A11Y-1] (Severity: High): There is zero automated accessibility tooling in the codebase. Every accessibility issue is one that survives manual review.
Recommendation (Phase 0): install eslint-plugin-vuejs-accessibility and add the a11y/recommended rule set. This catches the lowest-hanging fruit (missing alt, label/control association, role usage). Phase 0 effort.
3. Manual testing¶
There is no documented evidence of:
- Keyboard-only testing
- Screen-reader testing (NVDA, JAWS, VoiceOver, TalkBack)
- Color-contrast audit
- Reduced-motion audit
- Browser-zoom (200%) testing
grep -rEn "prefers-reduced-motion\|prefers-color-scheme" assets pages components 2>/dev/null \| head — [VERIFY-A11Y-1] check whether any media queries handle reduced-motion. If not, the snowfall plugin (plugins/snowfall.js, vue-niege dep) plays animations that trigger vestibular issues for some users.
4. Image alt text¶
grep -rE "<img" pages components 2>/dev/null \| wc -l → 494 image tags (the lenient regex captures both single-line <img src=...> and multi-line <img\n attr=...> patterns; the strict-spaced <img regex returns 357 because Vue templates often break attributes onto subsequent lines).
grep -rE "alt=" pages components 2>/dev/null \| wc -l → 263 alt attributes.
So roughly 231 images (47%) lack alt= attributes at first inspection. The actual figure is likely worse because:
- Some
alt=matches are inside string templates, not on<img>tags. - Some images use
:srcbinding with computed alt; the grep doesn't distinguish.
Finding [WC-A11Y-2] (Severity: High): Roughly half of images have no alt text. Screen-reader users hear "image" or get the filename. WCAG 2.1 1.1.1 (Non-text Content) violation.
Recommendation (Phase 0):
- Add
vuejs-accessibility/alt-textESLint rule. - Sweep all
<img>and add eitheralt="..."oralt=""(for purely decorative).
5. Form labels¶
The codebase uses bootstrap-vue heavily. <b-form-input> and friends accept a label/label-for pattern. grep -rE "<b-form-input\|<input" pages components 2>/dev/null \| wc -l returns thousands of matches — too many to manually verify.
[VERIFY-A11Y-2]: spot-check 10 representative forms (login, signup, content creation, billing, settings) and confirm each input has either <label for="..."> or aria-label.
pages/index.vue:1-100 (the login form) is one such candidate.
6. Semantic HTML¶
Common signs of div-soup:
<div role="button">instead of<button>—grep -rEn 'role=\"button\"' pages components 2>/dev/null \| wc -l— [VERIFY-A11Y-3]<div onclick=...>—grep -rEn '<div [^>]*@click' pages components 2>/dev/null \| wc -l— [VERIFY-A11Y-4]
These are common in Vue templates; expect non-trivial counts.
7. ARIA usage¶
grep -rEn 'aria-' pages components 2>/dev/null \| wc -l — [VERIFY-A11Y-5] count and review.
The Chatbot modal (Chatbot.vue:200-260 already inspected for v-html finding) does include aria-label on close buttons and other action buttons — good. The pattern needs to extend across the app.
8. Focus management¶
| Concern | Status |
|---|---|
| Skip-to-content link | [VERIFY-A11Y-6] — likely absent; no <a href="#main"> pattern observed in layouts/default.vue |
| Modal focus trap | bootstrap-vue's <b-modal> traps focus by default — likely OK for <b-modal> users; custom modals (pModal.vue, custom modals) need verification |
| Route-change focus shift | No observed code. Vue 2 + Nuxt 2 don't shift focus to the page heading on route change by default. SPA users hit "your page changed but my screen reader didn't notice." |
| Keyboard traps | [VERIFY-A11Y-7] check the editor (Polotno bundle) for keyboard access |
Finding [WC-A11Y-3] (Severity: High): Route-change focus is not managed. Screen-reader users will not perceive page transitions. Single-page apps with multi-route navigation must announce route changes.
Recommendation (Phase 1):
- Add a
mountedhook in the default layout that shifts focus to the page H1 on route change (or use a live region announcement). - Add a "Skip to content" link as the first focusable element.
- Audit modals for focus trap.
9. Color contrast¶
assets/css/style.css, assets/scss/_variables.scss, and the Bootstrap CSS define the color palette. The brand color (#ED3F56 per inline styles in Chatbot.vue:1430) is a strong red-pink.
Common Bootstrap pitfalls:
- text-muted on light backgrounds — typically fails WCAG AA contrast at 4.5:1.
- Brand-coloured buttons with white text — depends on the exact shade.
[VERIFY-A11Y-8]: Run an automated contrast check against the deployed UAT (axe-core browser extension or pa11y-ci).
10. Forms — error association¶
If form errors are shown next to fields, they need aria-describedby linking the input to the error. [VERIFY-A11Y-9] spot-check the signup and content-creation forms.
11. Snowfall plugin¶
plugins/snowfall.js registers vue-niege (a snowfall effect). It runs animation regardless of prefers-reduced-motion. Finding [WC-A11Y-4] (Severity: Low): this should be guarded by a prefers-reduced-motion: reduce check, or made opt-in.
12. Polotno editor accessibility¶
The Polotno editor is a third-party SDK — its a11y conformance is what Polotno provides. [VERIFY-A11Y-10]: check Polotno docs for stated a11y conformance. As an embedded React app, it has its own focus/keyboard model that the host Vue page does not control.
13. Findings summary¶
| ID | Finding | Severity |
|---|---|---|
| WC-A11Y-1 | No automated a11y tooling | High |
| WC-A11Y-2 | ~50% of images lack alt | High |
| WC-A11Y-3 | No route-change focus management | High |
| WC-A11Y-4 | Snowfall plugin ignores prefers-reduced-motion |
Low |
| (Pending verify) | Form-label coverage | TBD |
| (Pending verify) | Color-contrast issues | TBD |
| (Pending verify) | Skip-to-content link | TBD |
| (Pending verify) | Reduced-motion media-query usage | TBD |
14. Recommendations¶
Phase 0a / Phase 0¶
- Install
eslint-plugin-vuejs-accessibilitywith recommended ruleset; wire to alintscript (which doesn't exist yet — seearchitecture-overview.mdFinding WC-ARCH-3). - Add an ESLint rule blocking
vue/no-v-html(already a Vue ESLint rule in some configs). - Run
axe-corebrowser extension against UAT; capture top 10 issues. - Sweep
<img>for missing alt. - Add a "Skip to content" link in
layouts/default.vue. - Gate the snowfall plugin behind
prefers-reduced-motion: no-preference.
Phase 1¶
- Add route-change focus management.
- Run
pa11y-ciin CI on a representative route set; gate on no new issues. - Color-contrast audit and remediation.
- Form-label sweep.
Phase 2¶
- WCAG 2.1 AA audit by external accessibility vendor.
- Document conformance for enterprise procurement.
15. Open questions¶
Tracked in ./verify-markers.md:
- [VERIFY-A11Y-1]–[VERIFY-A11Y-10] — see body of this doc