4 · Accessibility & cookie compliance
Objective — run the WCAG 2.1 AA checks (automated scanners + manual verification) and build the GDPR/CCPA cookie inventory with consent gating that actually blocks non-essential cookies — the audits that keep the app usable for everyone and keep it legal.
Steps at a glance:
- Audit accessibility (WCAG 2.1 AA) — Run automated scanners (WAVE, axe DevTools) on key pages, then verify keyboard/focus/contrast with browser automation when available and human review for judgment-heavy calls.
- Verify cookie compliance (GDPR / CCPA) — Inventory every cookie, then verify consent actually gates the non-essential ones.
Background
Section titled “Background”The audits that keep the app usable for everyone — and keep it legal. Automated tools catch deterministic failures, and browser automation can drive tab order, focus, and consent-banner behavior. Human review remains for judgment-heavy accessibility and legal adequacy calls.
Use Zaj-PROJECT.md to record the current accessibility/cookie posture: scanner/date, consent mechanism, cookie-policy URL, legal-review owner, and unresolved issues. Detailed evidence belongs in the Phase 10 report; unresolved blockers go to Zaj-BACKLOG.md.
1. Audit accessibility (WCAG 2.1 AA)
Section titled “1. Audit accessibility (WCAG 2.1 AA)”Run automated scanners (WAVE, axe DevTools) on key pages, then verify keyboard/focus behavior with browser automation where available.
| Check | Requirement |
|---|---|
| Keyboard nav | Logical tab order, visible focus, no traps |
| Focus indicator | Never removed globally; :focus { outline: 2px solid … } |
| Images | Descriptive alt; alt="" for decorative |
| Form inputs | Every visible input has a <label for> or aria-label |
| Color contrast | 4.5:1 normal text · 3:1 large text & UI |
| Icon buttons | aria-label or .sr-only text |
| Structure | Semantic landmarks, skip link, sequential headings |
Agent-runnable greps for the deterministic failures (the scanners catch the rest):
# Images missing alt textgrep -rn "<img" resources/views/ --include="*.blade.php" | grep -v "alt=" || echo "OK all <img> have alt"
# Inputs that may lack a label (excluding hidden)grep -rn "<input" resources/views/ --include="*.blade.php" | grep -v 'type="hidden"' | head -20
# Icon-only elements missing an accessible namegrep -rn 'class=".*fa-' resources/views/ --include="*.blade.php" | grep -v "aria-" || echo "OK icons labelled"
# Landmark / semantic elements present in layoutsgrep -rn "<main\|<nav\|<header\|<footer" resources/views/layouts/ --include="*.blade.php"Then add visible focus styles (never remove the outline globally) and a skip link to #main-content.
-
Scan key pages, then verify the interactive checks against every row of the table above.
- ✅ Automated scanners are clean and the interactive checks (tab order, focus, contrast) all pass with evidence.
2. Verify cookie compliance (GDPR / CCPA)
Section titled “2. Verify cookie compliance (GDPR / CCPA)”Inventory every cookie, then verify consent actually gates the non-essential ones.
-
Inventory all cookies (name, expiry,
HttpOnly,Secure,SameSite) and classify them Essential / Functional / Analytics / Marketing.- ✅ Every cookie is listed with its attributes and a category.
-
Verify session security — session cookies need
HttpOnly=true,Secure=true,SameSite=lax.Terminal window php artisan tinker --execute="echo json_encode(\Illuminate\Support\Arr::only(config('session'), ['secure','http_only','same_site']));"# Expected: {"secure":true,"http_only":true,"same_site":"lax"} (production/staging APP_URL=https://…)- ✅ Runtime session config reports
secure,http_only, andsame_site=lax(not justenv()lines inconfig/session.php).
- ✅ Runtime session config reports
-
Scan for custom cookies in app code and enforce the lifetime ceilings. Every custom cookie must carry the same security attributes as the session cookie.
Terminal window grep -rnE "Cookie::make|cookie\(|setcookie|->cookie\(" app/ routes/ --include="*.php"# Expected: each hit uses HttpOnly + Secure + SameSite; none sets a raw long-lived plaintext cookieLifetime ceilings:
Cookie Max lifetime Session 120 min Remember-me 30 days Consent 1 year - ✅ Every custom cookie sets
HttpOnly+Secure+SameSite, and no cookie exceeds its lifetime ceiling (session ≤ 120 min, remember-me ≤ 30 days, consent ≤ 1 year).
- ✅ Every custom cookie sets
-
Test the banner in incognito: only essential cookies pre-consent; “Accept All” loads analytics/marketing; “Reject” keeps them blocked; preferences are changeable later.
- ✅ Pre-consent loads only essential cookies; Accept/Reject behave correctly; preferences are editable later.
-
Use one consent mechanism only — if you use a third-party manager, disable the built-in one. Confirm the cookie policy page matches reality.
- ✅ A single consent mechanism is active and the cookie policy page matches the real inventory.
Checklist
Section titled “Checklist”Do not mark this step done until every box below is checked.
- 🔀 WCAG 2.1 AA passes — scanners clean; keyboard, focus, contrast, and structure verified by browser automation where available plus human review for judgment-heavy calls.
- 🤖 Cookies inventoried — every cookie listed with attributes and a category.
- 🤖 Session cookies secure — runtime
config('session')hassecure,http_only,same_site=lax. - 🔀 Banner gates non-essential — incognito browser test confirms Accept/Reject and editable preferences; legal adequacy reviewed by the human.
- 🔀 One consent mechanism — built-in disabled if a third-party manager is used; policy page matches.
- 🤖 Project state updated —
Zaj-PROJECT.mdrecords scanner/date, consent mechanism, cookie-policy URL, legal-review owner, and anyZaj-BACKLOG.mdaccessibility/cookie blocker rows.