Accessibility
Y3NKO is designed to meet WCAG 2.1 AA standards and provide a great experience for all users.
Core Requirements
Color Contrast
All text meets minimum contrast ratios:
| Text Type | Minimum Ratio | Our Implementation |
|---|---|---|
| Normal text | 4.5:1 | Night on Paper: 15.8:1 |
| Large text (18pt+) | 3:1 | Ghana Red on Paper: 5.2:1 |
| UI components | 3:1 | Gold on Coffee: 6.3:1 |
Focus Indicators
All interactive elements have visible focus states:
*:focus-visible {
outline: 2px solid var(--ghana-gold);
outline-offset: 2px;
}The Ghana Gold (#D4A853) provides high visibility on both light and dark backgrounds.
Touch Targets
Minimum touch target size is 44x44 pixels:
{/* Button with adequate size */}
<Button className="min-h-[44px] min-w-[44px]">
{/* Content */}
</Button>
{/* Icon button */}
<button className="p-3">
<Icon className="w-5 h-5" />
</button>Keyboard Navigation
Tab Order
Ensure logical tab order follows visual layout:
{/* Good: natural reading order */}
<nav>
<Link href="/">Home</Link>
<Link href="/accommodations">Accommodations</Link>
<Link href="/about">About</Link>
</nav>
{/* Avoid: tabindex manipulation */}
<nav>
<Link href="/" tabIndex={3}>Home</Link> {/* Don't do this */}
</nav>Keyboard Shortcuts
Interactive elements respond to expected keys:
| Element | Keys |
|---|---|
| Links, buttons | Enter, Space |
| Checkboxes | Space |
| Dropdowns | Arrow keys, Enter, Escape |
| Modals | Escape to close |
| Carousels | Arrow keys |
Skip Links
Allow users to skip to main content:
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-white focus:text-[#CE1126]"
>
Skip to main content
</a>
<main id="main-content">
{/* Page content */}
</main>Screen Readers
Semantic HTML
Use appropriate HTML elements:
{/* Good: semantic structure */}
<header>
<nav aria-label="Main navigation">
<ul>
<li><Link href="/">Home</Link></li>
</ul>
</nav>
</header>
<main>
<article>
<h1>Page Title</h1>
<section aria-labelledby="section-heading">
<h2 id="section-heading">Section</h2>
</section>
</article>
</main>
<footer>
{/* Footer content */}
</footer>ARIA Labels
Add labels to icon-only buttons:
{/* Icon button with label */}
<button aria-label="Add to favorites">
<Heart className="w-5 h-5" />
</button>
{/* Close button */}
<button aria-label="Close dialog">
<X className="w-5 h-5" />
</button>
{/* Search button */}
<button aria-label="Search accommodations">
<Search className="w-5 h-5" />
</button>Decorative Icons
Hide decorative icons from screen readers:
{/* Decorative icon next to text */}
<span className="flex items-center gap-2">
<MapPin className="w-4 h-4" aria-hidden="true" />
Accra, Ghana
</span>
{/* Informational icon */}
<span className="flex items-center gap-2">
<Star className="w-4 h-4 fill-yellow-400 text-yellow-400" aria-hidden="true" />
<span>4.8 rating</span>
</span>Live Regions
Announce dynamic content changes:
{/* Toast notifications */}
<div role="alert" aria-live="polite">
Booking confirmed!
</div>
{/* Error messages */}
<div role="alert" aria-live="assertive">
Payment failed. Please try again.
</div>
{/* Loading status */}
<div aria-live="polite" aria-busy={isLoading}>
{isLoading ? 'Loading...' : 'Content loaded'}
</div>Images
Alt Text
Provide meaningful alt text:
{/* Informative image */}
<Image
src="/villa.jpg"
alt="Luxury beach villa with infinity pool overlooking the ocean"
/>
{/* Decorative image */}
<Image
src="/pattern.svg"
alt=""
role="presentation"
/>
{/* User avatar */}
<Image
src={user.avatar}
alt={`${user.name}'s profile photo`}
/>Complex Images
For complex images like charts, provide detailed descriptions:
<figure>
<Image src="/booking-chart.png" alt="Monthly bookings chart" />
<figcaption>
Chart showing booking trends from January to December.
Bookings peaked in July with 150 reservations.
</figcaption>
</figure>Forms
Labels
Every input needs an associated label:
{/* Explicit label */}
<div>
<Label htmlFor="email">Email Address</Label>
<Input id="email" type="email" />
</div>
{/* Implicit label */}
<label>
Email Address
<Input type="email" />
</label>Error Messages
Associate errors with inputs:
<div>
<Label htmlFor="email">Email</Label>
<Input
id="email"
aria-invalid={!!errors.email}
aria-describedby={errors.email ? "email-error" : undefined}
/>
{errors.email && (
<p id="email-error" role="alert" className="text-red-600 text-sm mt-1">
{errors.email.message}
</p>
)}
</div>Required Fields
Indicate required fields clearly:
<Label htmlFor="name">
Full Name <span className="text-red-500" aria-hidden="true">*</span>
<span className="sr-only">(required)</span>
</Label>Reduced Motion
Respect user preferences:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}Or use Tailwind:
<div className="motion-safe:animate-fade-in motion-reduce:opacity-100">
{/* Animates only if reduced motion not set */}
</div>Testing Checklist
- Navigate entire page using only keyboard
- Test with screen reader (VoiceOver, NVDA)
- Check color contrast with browser dev tools
- Verify focus indicators are visible
- Test with 200% zoom
- Enable
prefers-reduced-motionand verify - Test with prefers-color-scheme if dark mode supported
- Validate HTML structure
- Check heading hierarchy (h1 → h2 → h3)
Tools
- axe DevTools: Browser extension for accessibility testing
- WAVE: Web accessibility evaluation tool
- Lighthouse: Chrome DevTools audit
- VoiceOver: macOS built-in screen reader
- NVDA: Free Windows screen reader