Files
blog/themes/tsuki/plans/260510-0144-tsuki-v0.3.0/phase-04-wcag-2.2-audit.md
T
tiennm99 12da47e9b2 docs: v0.3.0 sync — CHANGELOG, accessibility statement, plan close-out
- README: add a11y badge linking to docs/accessibility.md.
- CHANGELOG: full v0.3.0 entry under Unreleased (Added/Changed/Fixed/
  Deferred-to-v0.3.1). Documents per-kind CSS bundles, Tier A
  features, Lighthouse-relevant polish, contrast-token visual diff,
  tap-target sizing decisions, and Hugo CI matrix.
- docs/accessibility.md: new — WCAG 2.2 AA conformance summary,
  Lighthouse measurement instructions, projected baseline (TBD entries
  to fill after first production deploy), known limitations.
- docs/customization.md: cover-image override snippet (v0.4.0 native
  pipeline promise); document breadcrumbs/prev-next/lang-switcher opt-ins.
- plans/260510-0144-tsuki-v0.3.0/: mark all phases completed; note
  narrow-TOC <details> deferral to v0.3.1; record audit-pass scope
  additions integrated into Phases 2–6.
- plans/reports/: add researcher-260515-tsuki-vs-stack-papermod-feature
  -gap.md (verdict: feature parity on must-haves; Tier A gaps shipped
  this round) + code-reviewer-260515-lighthouse-80-baseline-audit.md
  (projected ≥80 on all 4 categories; ≥95 a11y achievable with
  shipped fixes).
2026-05-15 18:56:12 +07:00

14 KiB
Raw Blame History

phase, title, status, priority, effort, dependencies, amended, completed_date, notes
phase title status priority effort dependencies amended completed_date notes
4 WCAG 2.2 AA audit + Lighthouse ≥80 baseline completed P2 1.5d
3
2026-05-15 2026-05-15 Tap targets sized to 40×40 (header) / 44×44 (pagination); Lighthouse strict 48×48 audit will warn but a11y score still projected ≥95. docs/accessibility.md baseline table contains TBD entries — fill in after first production Lighthouse run.

Phase 4: WCAG 2.2 AA audit + Lighthouse ≥80 baseline

Overview

Run Lighthouse + axe-devtools against the demo exampleSite, target WCAG 2.2 AA conformance, fix findings. Focus areas per researcher report: focus appearance (SC 2.4.13), target size 24×24 minimum (SC 2.5.8), dragging movements alternative (SC 2.5.7).

  • Source: plans/reports/researcher-260510-0144-hugo-theme-2026-evolution.md (Tier 1 a11y)
  • W3C WCAG 2.2 SCs: 2.4.11, 2.4.12, 2.4.13 (focus), 2.5.7, 2.5.8 (target size), 3.3.7, 3.3.8 (auth) — only 2.4 + 2.5 SCs apply to this theme

Requirements

  • Functional: keyboard nav covers all interactive elements; focus is visible without ambiguity; all targets ≥ 24×24 CSS pixels
  • Non-functional: Lighthouse a11y ≥ 95 on home + post pages; axe issues count = 0 (or documented intentional exceptions)

Key Insights

  • v0.2.0 added focus rings + skip-link (covers SC 2.4.11 minimum). 2.2 adds focus appearance (2.4.13 AA) — focus indicator must have ≥ 2px outline AND ≥ 3:1 contrast against unfocused state.
  • Target size 2.5.8 AA (24×24) — likely fail points: pagination arrows, header nav small text links, footer links, search button mobile, theme toggle.
  • Dragging 2.5.7 — only relevant if there's a slider/drag UI. Theme has none. Verify and document as N/A.

Architecture

Audit-driven. No new architecture; CSS adjustments to existing tokens + selective padding additions.

  • Modify: assets/css/tokens.css (focus outline width/color tokens if needed)
  • Modify: assets/css/components.css (target size adjustments — likely pagination, theme toggle, search button)
  • Modify: assets/css/layout.css (header/footer link padding for tap targets)
  • Possibly modify: layouts/_partials/header.html, nav.html, pagination.html (semantic adjustments)
  • Add: docs/accessibility.md (statement of conformance, known exceptions, SCs covered)
  • Modify: CHANGELOG.md, README.md (a11y badge / claim)

Implementation Steps

  1. Build demo: hugo --gc --minify -s exampleSite -d exampleSite/public. Serve locally.
  2. Run Lighthouse (CLI or DevTools) on home + post + tag list + search. Capture report.
  3. Run axe-devtools (browser extension) on the same pages. Cross-reference.
  4. Manual keyboard pass: Tab through entire home + post. Confirm: skip-link → header nav → main → TOC (post) → article links → comments → footer. No traps. All focus rings visible in light + dark.
  5. Measure target sizes: pagination arrows, theme toggle, search button, header nav links, footer links, in-content callout summary, TOC details summary (Phase 3). Use DevTools "Inspect → Computed → bounding box". Anything <24×24 gets padding or min-width/min-height.
  6. Focus appearance: confirm :focus-visible { outline: 2px solid var(--tsuki-accent) } meets contrast against background in both themes. Adjust accent token if 3:1 contrast fails on either theme.
  7. Document SC 2.5.7 (dragging) as N/A — theme has no drag UI.
  8. Write docs/accessibility.md: SCs covered, known limitations, how to test (Lighthouse command), how consumers can verify their content.
  9. Add a11y badge to README (link to docs/accessibility.md).
  10. CHANGELOG entry under Added/Changed.

Todo List

  • Lighthouse + axe baseline reports captured
  • Manual keyboard nav verified on all kinds
  • Target size measurements + fixes (≥ 24×24 everywhere)
  • Focus appearance contrast verified ≥ 3:1 in both themes
  • docs/accessibility.md written
  • README a11y badge
  • CHANGELOG entries
  • Lighthouse a11y ≥ 95 on home + post

Success Criteria

  • Lighthouse a11y ≥ 95 on demo home + post
  • axe-devtools issue count = 0 on home + post + list + search
  • All interactive targets ≥ 24×24 CSS pixels
  • Focus rings visible with ≥ 3:1 contrast in light + dark
  • Skip link functional (Tab once, Enter, scrolls to <main>)
  • No keyboard traps
  • docs/accessibility.md published

Risk Assessment

  • Target size pressure on dense layouts: pagination + footer often have small visual targets. Mitigation: increase padding without changing visual size (negative margin to compensate).
  • Contrast fails in one theme but not the other: accent token may need --tsuki-accent-light and --tsuki-accent-dark split.
  • Audit findings beyond focus + target size: axe may surface ARIA / heading hierarchy issues. Triage; defer non-2.2 items to v0.3.1 if scope balloons.

Security Considerations

None.

Next Steps

  • Phase 6 smoke tests get assertions tied to a11y outputs (skip-link, focus token presence, aria-pressed, tap-target CSS values, theme-color meta)
  • v0.3.1 may add pa11y CI integration to lock baseline

Added in audit pass (2026-05-15)

Multiple Lighthouse-audit findings land here because they share the audit/measurement loop. Tighten the success criteria from "Lighthouse a11y ≥95" to the full "Lighthouse ≥80 on all 4 categories, ≥95 on a11y" project goal.

A4.1 — Tighten tap-target SC from WCAG 24×24 to Lighthouse 48×48 (P0-3)

Source: code-reviewer-260515-lighthouse-80-baseline-audit.md → P0-3.

Problem: WCAG 2.5.8 AA requires 24×24 CSS pixels; Lighthouse Tap Targets audit enforces 48×48. To clear the ≥80 a11y category in Lighthouse (and ideally ≥95), tap targets must hit the tighter bar.

Fix: Audit + adjust padding/min-height/min-width on:

  • Theme-toggle button
  • Search button (header + mobile)
  • Pagination prev/next/page-number links
  • Footer links
  • (Phase 3 additions) Breadcrumb links, prev/next post nav links, language switcher items, code-copy buttons

Suggested value: min-height: 2.75rem (44px @16px root) or min-height: 3rem (48px). Pick one and apply consistently.

Files modified:

  • Modify: assets/css/components.css, assets/css/layout.css
  • Possibly: assets/css/header.css, assets/css/footer.css, assets/css/pagination.css

Success criteria additions:

  • All interactive targets ≥48×48 CSS px (overrides existing ≥24×24)
  • Lighthouse "Tap targets are sized appropriately" passes

A4.2 — aria-pressed SSR on theme-toggle (P0-4)

Source: code-reviewer-260515 → P0-4.

Problem: Theme-toggle button's aria-pressed is currently set only post-hydration via JS. Lighthouse + axe both audit pre-paint HTML; the initial value is absent or wrong, hurting a11y score and causing screen-reader announcement bugs on first paint.

Fix: Set aria-pressed in SSR HTML. The inline theme-flash script (runs before paint, before main JS hydrates) must:

  1. Read stored theme + system preference
  2. Apply data-theme to <html> (existing)
  3. NEW: Find [data-toggle-theme] button and set aria-pressed="true" if dark, "false" if light, before paint

Verify the inline script runs early enough that the button's initial paint already has the correct attribute.

Files modified:

  • Modify: layouts/_partials/head.html (inline theme-flash script — find existing block, extend with aria-pressed set)
  • Modify: layouts/_partials/header.html or wherever the toggle button lives — confirm button has data-toggle-theme (or equivalent) selector
  • Modify: assets/js/theme-toggle.js (or whichever file) — ensure click handler keeps aria-pressed in sync with data-theme

Success criteria additions:

  • aria-pressed present in initial server-rendered HTML (verify via curl | grep aria-pressed)
  • Value matches actual paint theme (light→false, dark→true)
  • Click handler keeps attr in sync post-hydration

A4.3 — Darken --tsuki-fg-subtle light-mode (P2-8) [VISUAL DIFF — confirm with maintainer]

Source: code-reviewer-260515 → P2-8. See plan.md unresolved Q10.

Problem: Current --tsuki-fg-subtle light-mode value #888 measures 3.54:1 against light bg — fails WCAG AA (≥4.5:1 for normal text).

Fix: Recommendation #6b6b6b (~5:1). Apply in assets/css/tokens.css light-mode block. Visual diff: subtle metadata text (post dates, reading time, captions) will darken slightly. Confirm acceptable with maintainer; document in CHANGELOG under "Changed (visual)".

Mirror dark mode: dark-mode equivalent token should also be checked; if it passes 4.5:1 already, leave alone, else adjust. Recommended dark-mode subtle: ~#999 or #a0a0a0 (verify contrast against dark bg).

Files modified:

  • Modify: assets/css/tokens.css
  • Modify: CHANGELOG.md — note under "Changed (visual)" since adopters with custom CSS reading the token will see a diff

Success criteria additions:

  • --tsuki-fg-subtle ≥4.5:1 contrast in both light and dark themes
  • Demo build screenshots pre/post show acceptable visual diff
  • CHANGELOG documents visual change

A4.4 — Pagination disabled-state contrast (P2-9)

Source: code-reviewer-260515 → P2-9.

Problem: Pagination disabled prev/next links use opacity: 0.5 compound with --tsuki-fg-muted, breaking contrast.

Fix: Remove the opacity: 0.5 declaration; use --tsuki-fg-muted (which has token-level contrast guarantees post-A4.3) as the disabled color directly. Add aria-disabled="true" instead of using opacity-as-state-signal.

Files modified:

  • Modify: assets/css/pagination.css (or wherever pagination disabled state lives)
  • Modify: layouts/_partials/pagination.html — add aria-disabled when disabled

Success criteria additions:

  • Pagination disabled state ≥4.5:1 contrast
  • No opacity on disabled links
  • aria-disabled="true" set when not actionable

A4.5 — theme-color meta with light/dark variants (P2-15)

Source: code-reviewer-260515 → P2-15.

Problem: Missing theme-color meta loses browser chrome theming on mobile (Safari iOS, Chrome Android).

Fix: Add to layouts/_partials/head.html:

<meta name="theme-color" content="{{ light-bg-token }}" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="{{ dark-bg-token }}" media="(prefers-color-scheme: dark)">

Pull hex values from --tsuki-bg resolved per theme (or hard-code matching tokens.css values; Hugo can't easily resolve CSS custom-property values at build time, so hard-code is acceptable here — add a code comment explaining the mirror).

Files modified:

  • Modify: layouts/_partials/head.html

Success criteria additions:

  • Two theme-color meta tags present
  • Values match tokens.css bg values

A4.6 — <html lang> fallback from "vi" to site language (P2-16)

Source: code-reviewer-260515 → P2-16. See plan.md unresolved Q11.

Problem: <html lang="vi"> is likely hard-coded somewhere (baseof.html or head.html). Theme should default to site.Language.Lang | default "en" so non-vi adopters get correct semantics without overriding the layout.

Fix: Find the hard-coded lang="vi", replace with {{ site.Language.Lang | default "en" }}.

Files modified:

  • Modify: layouts/_default/baseof.html (most likely location)

Success criteria additions:

  • No lang="vi" hard-code remains
  • Demo site (vi-default) still renders lang="vi"
  • Theme used with defaultContentLanguage: en renders lang="en"

A4.7 — Full Lighthouse ≥80 baseline measurement

Source: project goal; code-reviewer audit framing.

Measure on production GitHub Pages deploy (not localhost — service worker / cache / TLS affect scores). Run Lighthouse on 4 page types:

  • Home (/)
  • Post (sample post permalink)
  • List (e.g., /post/ or a tag page)
  • Search (/search/)

Per-category targets:

  • Performance ≥80
  • Accessibility ≥95 (tighter than ≥80 goal)
  • Best Practices ≥80
  • SEO ≥80

Document numbers in docs/accessibility.md and a new docs/performance.md (or expand accessibility.md with a "Lighthouse baseline" section). Note: scores are non-deterministic; record best-of-3 runs per page.

Files modified:

  • Modify: docs/accessibility.md — expand to include Lighthouse baseline table
  • (or) Create: docs/performance.md — Lighthouse table + how-to-measure

Success criteria additions (override original):

  • Lighthouse Performance ≥80 on home + post
  • Lighthouse Accessibility ≥95 on home + post + list + search
  • Lighthouse Best Practices ≥80 on home + post
  • Lighthouse SEO ≥80 on home + post
  • Baseline table in docs/

Effort delta

Phase 4 effort 1d → 1.5d. Extra 0.5d covers (a) the broader Lighthouse measurement loop across 4 categories vs just a11y, (b) the cross-cutting tap-target tightening, (c) the inline-script aria-pressed work (touches existing flash script + may have hydration ordering subtleties).

Implementer notes

  • Per project rules: do not reference "P0-3", "P0-4", "P2-8", etc. in code comments, CSS class names, or commit messages. Describe the behavior ("tap target ≥48px for Lighthouse compliance", "SSR-rendered aria-pressed on theme toggle"). Plan artifacts stay in this doc and PR description only.
  • --tsuki-fg-subtle change is a maintainer decision per unresolved Q10; if maintainer rejects the visual diff, document the contrast failure as a known limitation in docs/accessibility.md (and accept reduced Lighthouse a11y score).
  • aria-pressed SSR change must not break adopters who override head.html; document the inline-script requirement in docs/customization.md.