Skip to content

ADR-018: Contact Page Accessibility Enhancements

Accepted

Note: This ADR documents the implementation of patterns defined in ADR 019: Accessibility Patterns & Standards. Refer to ADR 019 for comprehensive accessibility guidelines.

The contact.astro page contained several accessibility issues that could impact screen reader users:

  1. Decorative emojis were not hidden from assistive technology, causing redundant announcements
  2. Badge components used role="status" for all instances, even when decorative or informational
  3. Focus order in grid layouts needed validation documentation

These issues violated WCAG 2.1 Level AA guidelines for perceivable and operable content.

Amendment (2026-08-02): the contact page no longer contains these emojis or any role="status" badge — the emoji icons were replaced by the Icon atom, whose decorative prop applies aria-hidden internally (ADR-055), and the remaining badges all use role="presentation". The patterns below are preserved as the decision record.

We will implement the following accessibility enhancements:

1. Decorative Emojis with aria-hidden="true"

Section titled “1. Decorative Emojis with aria-hidden="true"”

All decorative emojis that serve purely visual purposes will be hidden from screen readers:

<!-- Before -->
<span class="text-2xl">📧</span>
<!-- After -->
<span class="text-2xl" aria-hidden="true">📧</span>

Affected emojis (9 total):

  • 📧 Email icon
  • 💬 Chat icon
  • 📞 Phone icon
  • 📍 Location icon
  • 🕒 Time icon
  • 🌍 Globe icon
  • ⚡ Lightning icon
  • 🎯 Target icon
  • 🤝 Handshake icon

Rationale: Adjacent text already conveys the meaning (e.g., “Email” heading next to 📧), making emoji announcement redundant and potentially confusing.

Badge components now use appropriate ARIA roles based on their purpose:

<!-- Decorative/Informational badges -->
<Badge role="presentation">Available for new projects</Badge>
<Badge role="presentation">Remote-friendly</Badge>
<!-- Status badges (live updates) -->
<Badge role="status">Online now</Badge>

Role Guidelines:

  • role="presentation" - Decorative or informational badges that don’t announce status changes
  • role="status" - Live status updates that should be announced (e.g., “Online now”)
  • No role - Default for general informational content

Added HTML comments documenting that grid layouts follow natural DOM order for keyboard navigation:

<!-- Focus order: Grid flows naturally left-to-right, top-to-bottom for keyboard navigation -->
<section class="py-16 bg-background-surface">
<div class="grid md:grid-cols-3 gap-8">
<!-- Content flows naturally -->
</div>
</section>
  • Screen Reader Experience: Eliminates redundant emoji announcements, reducing cognitive load
  • Semantic Correctness: Badge roles accurately reflect their purpose
  • WCAG AA Compliance: Meets WCAG 2.1 Level AA criteria for:
    • 1.1.1 Non-text Content (Level A)
    • 1.3.1 Info and Relationships (Level A)
    • 2.4.3 Focus Order (Level A)
  • Developer Clarity: Comments document accessibility considerations for future maintainers
  • Testing Ready: Changes enable proper automated accessibility testing
  • Minimal: No negative consequences - these are pure accessibility improvements
  • Visual Appearance: No visual changes - only affects assistive technology
  • Performance: No performance impact
<!-- Decorative emoji (visual enhancement only) -->
<span aria-hidden="true">📧</span>
<h3>Email</h3> <!-- Text conveys meaning -->
<!-- Meaningful emoji (rare - would need alt text via title or aria-label) -->
<span role="img" aria-label="Warning">⚠️</span>
Is the badge announcing a live status change?
├─ YES → role="status" (e.g., "Online now", "Processing")
└─ NO → Is it purely decorative?
├─ YES → role="presentation"
└─ NO → No role (default semantic span)
  1. Screen Reader Testing:

    • NVDA (Windows)
    • JAWS (Windows)
    • VoiceOver (macOS/iOS)
  2. Keyboard Navigation:

    • Tab through all interactive elements
    • Verify focus order is logical (left-to-right, top-to-bottom)
    • Ensure no keyboard traps
  3. Automated Testing:

    Terminal window
    pnpm run test:a11y # Playwright with axe-core
  • Testable consequences:
    • TC-1: the contact page passes the automated axe sweep in the E2E suite (e2e/a11y-axe.spec.ts, wired 2026-08-13 — the claim predated the integration).
  • Checks:
    • TC-1 → @a11y Playwright suite in CI (status: block, pre-existing gate)
  • Not machine-checkable: whether a given emoji is decorative or meaningful is a content judgment.
  • Graduation log: (empty at creation; entries added when a check changes status)

Date: 2025-10-01 (footer backfilled 2026-07-05 from git history; this record predates the footer convention)
Participants: Template maintainers
Outcome: Accepted