Skip to content

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 -l494 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 -l263 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 :src binding 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):

  1. Add vuejs-accessibility/alt-text ESLint rule.
  2. Sweep all <img> and add either alt="..." or alt="" (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):

  1. Add a mounted hook in the default layout that shifts focus to the page H1 on route change (or use a live region announcement).
  2. Add a "Skip to content" link as the first focusable element.
  3. 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-accessibility with recommended ruleset; wire to a lint script (which doesn't exist yet — see architecture-overview.md Finding WC-ARCH-3).
  • Add an ESLint rule blocking vue/no-v-html (already a Vue ESLint rule in some configs).
  • Run axe-core browser 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-ci in 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