docs: add ACCESSIBILITY_AUDIT, SECURITY_PRIVACY_AUDIT, PRODUCT_BRIEF with file-specific actions; a11y: add skip link, lang+hreflang, ARIA for menu/carousel/dialog/form; noValidate+honeypot

This commit is contained in:
2025-08-08 23:00:09 +02:00
parent 910253c8f4
commit 3c74d71e22
16 changed files with 501 additions and 43 deletions

148
ACCESSIBILITY_AUDIT.md Normal file
View File

@@ -0,0 +1,148 @@
### Omoluabi Accessibility Audit (WCAG 2.2 AA)
Scope: Current Astro site as of this commit. Focus on perceivable, operable, understandable, and robust criteria for Nigerians in NL and Dutch partners.
---
### Executive summary
- **Overall**: Solid semantic base with clear landmarks in `src/layouts/BaseLayout.astro`. Reduced-motion support exists in `src/styles/global.css`. Key gaps: language/i18n markup, skip links, menu ARIA, carousel semantics, form error/ARIA, dialog semantics, and strict CSP readiness.
- **Risk**: Medium → High for navigation/keyboard and language; Low → Medium for contrast on image/gradient backgrounds.
---
### High-priority issues (fix now)
1) Missing skip link + focus target
- File: `src/layouts/BaseLayout.astro`
- Snippet (add just inside `<body>` and set `id` on `<main>`):
```astro
<a href="#main" class="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-50 focus:bg-white focus:text-black focus:px-3 focus:py-2 focus:rounded">
Skip to main content
</a>
...
<main id="main" tabindex="-1">
<slot />
</main>
```
- Rationale: Allows keyboard users to bypass repeated navigation (WCAG 2.4.1, 2.4.3).
2) Page language + `hreflang`
- File: `src/layouts/BaseLayout.astro`
- Snippet (top `<html>` and `<head>`):
```astro
<html lang={Astro.props.lang ?? 'en'}>
<head>
...
<link rel="alternate" hrefLang="en" href="/en/" />
<link rel="alternate" hrefLang="nl" href="/nl/" />
<link rel="alternate" hrefLang="x-default" href="/" />
```
- Rationale: Correct language for screen readers and localized SEO (WCAG 3.1.1, 3.1.2).
3) Mobile menu button lacks ARIA state wiring
- File: `src/layouts/BaseLayout.astro`
- Snippet (button + script):
```astro
<button class="md:hidden" id="mobile-menu-button" aria-controls="mobile-menu" aria-expanded="false">
...
</button>
```
```html
<script>
document.addEventListener('DOMContentLoaded', () => {
const mobileMenuButton = document.getElementById('mobile-menu-button');
const mobileMenu = document.getElementById('mobile-menu');
if (mobileMenuButton && mobileMenu) {
mobileMenuButton.addEventListener('click', () => {
const isHidden = mobileMenu.classList.toggle('hidden');
mobileMenuButton.setAttribute('aria-expanded', String(!isHidden));
});
}
});
</script>
```
- Rationale: Communicates expanded/collapsed state for assistive tech (WCAG 4.1.2).
4) Carousel semantics and controls
- File: `src/components/HeroCarousel.jsx`
- Snippet:
```jsx
<div
className="relative w-full max-w-6xl mx-auto rounded-2xl overflow-hidden shadow-2xl bg-white"
role="region"
aria-roledescription="carousel"
aria-label="Featured content"
>
{slides.map((slide, index) => (
<div
key={index}
role="group"
aria-roledescription="slide"
aria-label={`${index + 1} of ${slides.length}`}
className={...}
>
...
</div>
))}
<button aria-pressed={!isPlaying} aria-label={isPlaying ? 'Pause slideshow' : 'Play slideshow'}>...</button>
</div>
```
- Rationale: Conveys carousel/slide semantics; exposes play/pause state (WCAG 1.4.2, 2.2.2, 4.1.2).
5) Contact form ARIA and honeypot
- File: `src/components/ContactForm.jsx`
- Snippet:
```jsx
const [hp, setHp] = useState('');
...
<form onSubmit={handleSubmit} noValidate ...>
<div className="hidden" aria-hidden="true">
<label htmlFor="website">Website</label>
<input id="website" name="website" value={hp} onChange={(e)=>setHp(e.target.value)} tabIndex={-1} autoComplete="off" />
</div>
<input aria-invalid={Boolean(errors.name)} aria-describedby={errors.name ? 'name-error' : undefined} ... />
{errors.name && <p id="name-error" className="...">{errors.name}</p>}
```
- Rationale: Accessible errors association and simple anti-bot (WCAG 3.3.1/3.3.3).
6) Lightbox dialog semantics
- File: `src/components/Lightbox.jsx`
- Snippet:
```jsx
<div
className="fixed inset-0 ..."
role="dialog"
aria-modal="true"
aria-label="Image viewer"
onClick={onClose}
>
```
- Rationale: Dialog semantics and modality (WCAG 1.3.1, 2.4.3, 4.1.2).
7) Inline styles and data-URI backgrounds limit strict CSP
- Files: `src/pages/about.astro`, `src/pages/donate.astro`, `src/pages/orphanage.astro`
- Fix: Extract inline `style="background: ..."` to named CSS classes in `src/styles/main.css` (e.g., `.bg-flag-nl-ng`, `.pattern-overlay`) and reference via `class` only. Move inline `<script>` in layout to a module file and import with `type="module"` and `nonce`.
- Rationale: Enables strict non-`unsafe-inline` CSP (security and robustness).
---
### Medium-priority issues
- **Contrast over imagery/gradients**: Ensure minimum overlay like `bg-black/60` behind text; avoid fully transparent gradient text for body copy. Files: `src/pages/index.astro`, `src/pages/about.astro`, `HeroCarousel.jsx`.
- **Focus visibility baseline**: Add base `:focus-visible` style in `src/styles/global.css` to ensure consistent visible focus.
- **Image dimensions**: Add `width`/`height` on `<img>` where known to prevent CLS.
---
### Suggested global utilities
- File: `src/styles/global.css`
```css
:focus-visible { outline: 2px solid var(--nigerian-green); outline-offset: 2px; }
[role="button"], a, button, input, select, textarea { -webkit-tap-highlight-color: transparent; }
```
---
### Verification plan (to add in CI)
- Playwright + `@axe-core/playwright`: nav landmarks, skip link focus, keyboard-only menu, form labels/errors, dialog semantics.
- Lighthouse budgets: LCP < 2.0s, CLS < 0.05, TTI < 3.5s; run on PR and fail if exceeded.
- Spot-check color contrast with axe; prohibit text over images without overlay.