Audit date: 2026-02-26 Standard: WCAG 2.1 AA (where practical) Scope: All core flows — navigation, bonding, trust score
| Element | Before | After | Ratio |
|---|---|---|---|
Link text on #f8fafc |
#0ea5e9 (3.0:1 ✗) |
#0284c7 (4.56:1 ✓) |
AA pass |
| Button background | #0ea5e9 |
#0284c7 |
AA pass |
Subtitle text #64748b on #f8fafc |
4.4:1 (borderline) | Kept — passes AA for normal text | AA pass |
- Before: No custom
:focus-visiblestyles; browser defaults could be invisible on some elements. - After: Global
:focus-visibleoutline (2px solid #0284c7,2pxoffset) on all interactive elements.
- Before: No skip-to-content link.
- After: Added
.skip-linkthat appears on Tab, linking to#main-content.
- Before:
<label>elements had nohtmlFor;<input>elements had noid. - After:
- Bond page:
htmlFor="bond-amount"→id="bond-amount" - Trust Score page:
htmlFor="wallet-address"→id="wallet-address"
- Bond page:
- Before: Descriptive paragraphs existed but were not programmatically linked to inputs.
- After: Added
aria-describedbyon each input pointing to the page description<p>(id="bond-desc",id="trust-desc").
- Before:
<nav>had no accessible label. - After: Added
aria-label="Main navigation"to<nav>.
- Before:
<Link>elements styled as buttons had noroleattribute. - After: Added
role="button"to call-to-action links on the Home page.
- Added
<meta name="description">toindex.html.
Use this checklist when building new components or pages.
- Text contrast ratio ≥ 4.5:1 for normal text, ≥ 3:1 for large text (18px+ bold or 24px+)
- Do not rely on color alone to convey information (use icons, text, patterns)
- Interactive elements have visible hover and active states
- All interactive elements are reachable via Tab key
- Focus order follows visual reading order
-
:focus-visibleoutline is visible on all focusable elements - Custom components support Enter/Space activation
- Modal dialogs trap focus and return focus on close (see Focus Management Spec)
- Overlays set initial focus to the correct element per focus-patterns.md §4
- Background is inert (
inertattribute) while a focus-trapping overlay is active
- Every
<input>has a<label>with matchinghtmlFor/id - Helper text is linked via
aria-describedby - Required fields use
aria-required="true"and visible indicator - Error messages use
aria-live="polite"orrole="alert"and appear next to the field - Error language is clear, specific, and non-blaming (e.g. "Enter an amount greater than 0" not "Invalid input")
- One
<h1>per page; heading levels never skip (h1 → h2 → h3) - Use
<nav>,<main>,<header>,<footer>landmarks - Add
aria-labelto distinguish multiple<nav>regions - Use
<button>for actions,<a>for navigation; addrole="button"if a link looks like a button - Lists use
<ul>/<ol>, not styled<div>s
- Decorative images use
alt=""; meaningful images have descriptivealt - Icon-only buttons have
aria-label - Dynamic content updates use
aria-liveregions - Use
.sr-onlyclass for visually hidden but screen-reader-accessible text
- Minimum touch/click target size: 44×44px
- Skip-to-content link present on every page layout
- Page has
<title>and<meta name="description"> - Language attribute set on
<html lang="en">
- Every input must have a visible
<label>element linked viahtmlForandid. - Do not use placeholder text as a substitute for labels — placeholders disappear on focus.
- Labels should clearly describe the expected input (e.g. "Bond Amount" not "Amount").
- Use helper text below the input to clarify format or requirements (e.g. "Enter amount in USD").
- Link hint text to the input using
aria-describedbyso screen readers announce it.
- Errors must never rely on color alone — always include an icon (⚠) and descriptive text.
- Add
aria-invalid="true"to the input when an error is present. - Use
role="alert"on the error message so screen readers announce it immediately. - Error messages should be specific (e.g. "Amount must be greater than 0" not "Invalid input").
- Enable VoiceOver (iOS: Settings → Accessibility → VoiceOver).
- Navigate to the bond form using swipe gestures.
- Confirm each label is announced when focusing its input.
- Confirm hint text is read after the label.
- Trigger a validation error and confirm the error message is announced automatically.
// Bad — label not associated
<label>Amount</label>
<input type="number" />
// Good — programmatic association
<label htmlFor="amount">Amount</label>
<input id="amount" type="number" />// Bad — generic, blaming
<span style={{ color: 'red' }}>Invalid input!</span>
// Good — specific, accessible
<span id="amount-error" role="alert" style={{ color: '#dc2626' }}>
Enter a USDC amount greater than 0.
</span>
<input aria-describedby="amount-error" aria-invalid="true" />/* Bad — removes focus indicator */
*:focus {
outline: none;
}
/* Good — visible, custom focus */
:focus-visible {
outline: 2px solid #0284c7;
outline-offset: 2px;
}// Bad — link looks like button, no role
<Link to="/bond" style={{ ... }}>Create bond</Link>
// Good — role communicates purpose to assistive tech
<Link to="/bond" role="button" style={{ ... }}>Create bond</Link>
// Best — use <button> + navigation for true actions