Accessibility
BookWish follows WCAG 2.1 Level AA standards to ensure the app is accessible to all users, including those with visual, motor, auditory, and cognitive disabilities.
WCAG 2.1 Level AA Compliance
BookWish aims for WCAG 2.1 Level AA compliance across all features:
- Perceivable - Information and UI components must be presentable to users in ways they can perceive
- Operable - UI components and navigation must be operable
- Understandable - Information and operation of UI must be understandable
- Robust - Content must be robust enough to be interpreted by assistive technologies
Color Contrast
All text and interactive elements meet WCAG AA minimum contrast ratios.
Minimum Contrast Ratios
| Content Type | Minimum Ratio | WCAG Level |
|---|---|---|
| Normal text (< 18pt) | 4.5:1 | AA |
| Large text (≥ 18pt or 14pt bold) | 3:1 | AA |
| UI components and graphics | 3:1 | AA |
| Inactive/disabled elements | No requirement | - |
Color Palette Contrast
BookWish's primary color palette has been tested for sufficient contrast:
| Combination | Ratio | Usage | Passes |
|---|---|---|---|
| Ink Blue on Parchment | 13.2:1 | Body text, primary content | AAA |
| Ink Blue on White | 14.1:1 | Card content, buttons | AAA |
| Teal Edge on White | 3.4:1 | Interactive elements | AA |
| Amber Star on White | 1.7:1 | Backgrounds only (not text) | - |
| Coral Spine on White | 3.1:1 | Error states, warnings | AA |
| Success Green on White | 4.8:1 | Success messages | AA |
| Error Red on White | 5.4:1 | Error text | AA |
Testing Color Contrast
Tools for verification:
- WebAIM Contrast Checker
- Contrast Ratio by Lea Verou
- Browser DevTools accessibility inspector
How to test:
- Extract hex color values from theme.dart
- Input foreground and background colors into checker
- Verify ratio meets 4.5:1 for normal text, 3:1 for large text
- Document any exceptions or issues
Contrast Issues to Avoid
Don't:
- Use Amber Star (#FFC857) for text on white/light backgrounds
- Use low-opacity text without verifying contrast
- Rely solely on color to convey information
- Use light gray text (< 4.5:1 ratio) for essential content
Do:
- Use Ink Blue (#233548) for primary text
- Test all text with opacity applied
- Provide additional indicators beyond color (icons, labels)
- Use darker alternatives for emphasis
Touch Targets
All interactive elements meet minimum touch target sizes for easy tapping.
Minimum Sizes
| Element Type | Minimum Size | Recommended |
|---|---|---|
| Primary buttons | 48x48 dp | 48x48 dp |
| Icon buttons | 48x48 dp | 48x48 dp |
| Text buttons | 48dp height | 48dp height |
| List items | 48dp height | 56dp height |
| Form inputs | 48dp height | 52dp height |
| Switches/toggles | 48x48 dp | 48x48 dp |
Implementation
Buttons:
// ElevatedButton - Full width, 48dp height
ElevatedButton(
onPressed: () {},
child: Text('Primary Action'),
) // minimumSize: Size(double.infinity, 48)
// IconButton - Automatic 48dp tap target
IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
) // Default padding ensures 48x48
Custom Touch Targets:
// Wrap small elements to increase tap area
Material(
child: InkWell(
onTap: () {},
child: Padding(
padding: EdgeInsets.all(12), // Ensures 48dp minimum
child: Icon(Icons.favorite, size: 24),
),
),
)
Spacing Between Targets
- Minimum 8dp spacing between adjacent interactive elements
- Prefer 12-16dp spacing for better usability
- Use dividers or visual separation for dense lists
Screen Reader Support
BookWish uses Flutter's Semantics widget to provide screen reader support, though implementation is currently limited.
Current Implementation Status
Status: Basic screen reader support is in place through Flutter's built-in semantics, but explicit Semantics widgets are not widely implemented in the codebase.
What works:
- Text content is automatically readable by screen readers
- Button labels are announced
- Form field labels are associated correctly
- Navigation structure is available
What needs improvement:
- Custom widgets lack explicit semantic labels
- Image alternative text is missing
- Complex interactions need semantic descriptions
- Focus order needs verification
Semantics Widget Usage
While not extensively implemented yet, here's how Semantics should be used:
// Image with description
Semantics(
label: 'Book cover for ${book.title}',
image: true,
child: Image.network(book.coverUrl),
)
// Button with clear action
Semantics(
label: 'Add ${book.title} to wishlist',
button: true,
onTap: () => addToWishlist(book),
child: Icon(Icons.favorite_border),
)
// Decorative element to exclude
ExcludeSemantics(
child: Container(
decoration: BoxDecoration(
// Purely decorative visual element
),
),
)
Semantic Labels Reference
| Element Type | Label Format | Example |
|---|---|---|
| Book cover image | "Book cover for [Title]" | "Book cover for The Great Gatsby" |
| Add to wishlist | "Add [Title] to wishlist" | "Add 1984 to wishlist" |
| Remove from cart | "Remove [Title] from cart" | "Remove Dune from cart" |
| Like button | "Like [item type]" | "Like review by Sarah" |
| Follow button | "Follow [username]" | "Follow @bookworm" |
| Share button | "Share [item]" | "Share The Hobbit" |
| Close dialog | "Close [dialog name]" | "Close settings" |
Screen Reader Testing
iOS VoiceOver:
- Settings > Accessibility > VoiceOver
- Enable VoiceOver
- Swipe right/left to navigate
- Double-tap to activate
Android TalkBack:
- Settings > Accessibility > TalkBack
- Enable TalkBack
- Swipe right/left to navigate
- Double-tap to activate
What to test:
- All text is readable in context
- Interactive elements are clearly labeled
- Focus order is logical (top to bottom, left to right)
- Buttons describe their action
- Form errors are announced
- Dynamic content updates are announced
Keyboard Navigation (Web)
For BookWish web version, keyboard navigation is essential for accessibility.
Focus Management
Focus indicators:
- All interactive elements must show clear focus state
- Focus ring should be 2px solid, high contrast color
- Never remove outline without replacement indicator
- Focus should be visible against all backgrounds
Flutter Web Implementation:
// Theme configuration
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(
color: AppColors.inkBlue,
width: 2,
),
)
Skip Links
Web version should include skip navigation links:
// Skip to main content
Semantics(
label: 'Skip to main content',
button: true,
child: TextButton(
onPressed: () => _focusMainContent(),
child: Text('Skip to main content'),
),
)
Tab Order
Logical tab order:
- Header navigation
- Primary content
- Secondary navigation
- Footer content
Managing focus:
// Focus nodes for custom tab order
final FocusNode _firstFocusNode = FocusNode();
final FocusNode _secondFocusNode = FocusNode();
Focus(
focusNode: _firstFocusNode,
child: TextField(),
)
Keyboard Shortcuts
| Shortcut | Action | Scope |
|---|---|---|
| Tab | Move focus forward | Global |
| Shift + Tab | Move focus backward | Global |
| Enter / Space | Activate button | Buttons |
| Escape | Close dialog/modal | Dialogs |
| Arrow keys | Navigate lists | Lists |
Motion & Animation
Users with vestibular disorders may need reduced motion.
Reduced Motion Support
System preferences:
- iOS: Settings > Accessibility > Motion > Reduce Motion
- Android: Settings > Accessibility > Remove animations
- Web: prefers-reduced-motion media query
Implementation considerations:
// Check system reduced motion preference
final disableAnimations = MediaQuery.of(context).disableAnimations;
// Conditional animation duration
final duration = disableAnimations
? Duration.zero
: Duration(milliseconds: 300);
// Page transitions
PageRouteBuilder(
transitionDuration: duration,
pageBuilder: (context, animation, secondaryAnimation) => NextPage(),
)
Animation Guidelines
Safe animation practices:
- Keep animations under 500ms for most interactions
- Avoid rapid flashing (> 3 flashes per second)
- Provide static alternative for complex animations
- Allow users to pause or stop animations
- Browser the Cat can be disabled in settings
Parallax and scroll effects:
- Reduce or disable for vestibular concerns
- Test with reduced motion enabled
- Provide alternative non-animated experience
Text & Typography
Text must be readable and scalable for users with visual impairments.
Scalable Text
Flutter text scaling:
- Respect system text size settings
- Test at 200% text scale minimum
- Avoid fixed-size text containers
- Use responsive layouts
// Text automatically scales with system preferences
Text(
'Readable content',
style: AppTypography.body, // Scales with TextScaleFactor
)
// Avoid constraining text height
// Bad:
Container(height: 20, child: Text('Fixed'))
// Good:
Container(child: Text('Flexible'))
Line Length
- Optimal: 50-75 characters per line
- Maximum: 80 characters per line
- Use padding to constrain wide screens
- Break long content into paragraphs
Font Sizes
| Style | Size | Min Touch | Usage |
|---|---|---|---|
| Heading Large | 24pt | - | Page titles |
| Heading | 20pt | - | Section headers |
| Body | 16pt | - | Primary content |
| Body Small | 14pt | - | Secondary content |
| Caption | 12pt | - | Metadata (use sparingly) |
| Button Text | 16pt | 48dp height | Interactive labels |
Minimum readable sizes:
- Body text: 16pt (14pt minimum)
- Interactive labels: 16pt
- Captions: 12pt (use only for non-essential info)
Readability Best Practices
- Use high contrast for all text
- Avoid all-caps for long passages (okay for buttons)
- Provide adequate line-height (1.4-1.6 for body text)
- Use sentence case instead of title case
- Left-align text (avoid justified text)
Form Accessibility
Forms must be accessible and clearly communicate requirements and errors.
Form Labels
All inputs must have labels:
TextField(
decoration: InputDecoration(
labelText: 'Email Address', // Required
hintText: 'you@example.com', // Optional helper
prefixIcon: Icon(Icons.email),
),
)
Error Messages
Clear and actionable errors:
TextField(
decoration: InputDecoration(
labelText: 'Password',
errorText: _passwordError,
// Good: "Password must be at least 8 characters"
// Bad: "Invalid input"
),
)
Error message guidelines:
- Explain what went wrong
- Provide clear fix instructions
- Show errors near relevant fields
- Use error color with icon (not color alone)
- Announce errors to screen readers
Required Fields
Indicate required fields clearly:
TextField(
decoration: InputDecoration(
labelText: 'Email *', // Asterisk indicates required
helperText: '* Required field',
),
)
Best practices:
- Mark required fields with asterisk (*)
- Explain asterisk meaning at form start
- Consider making all fields required if most are
- Validate on submit, not on every keystroke
Form Validation
// Accessible validation flow
void _validateForm() {
setState(() {
if (_emailController.text.isEmpty) {
_emailError = 'Email is required';
} else if (!_isValidEmail(_emailController.text)) {
_emailError = 'Please enter a valid email address';
} else {
_emailError = null;
}
});
// Focus first error field
if (_emailError != null) {
_emailFocusNode.requestFocus();
}
}
Testing Checklist
Use this checklist to verify accessibility compliance:
Visual Accessibility
- All text meets 4.5:1 contrast ratio (normal text)
- Large text meets 3:1 contrast ratio
- UI components meet 3:1 contrast ratio
- Color is not the only indicator of state
- Focus indicators are visible on all interactive elements
- Text is readable at 200% zoom
Interactive Elements
- All buttons are at least 48x48 dp
- Touch targets have adequate spacing (8dp minimum)
- Tap areas are visually clear
- Interactive elements have clear hover/active states
- Disabled states are visually distinct
Screen Readers
- All images have semantic labels or are marked decorative
- Buttons describe their action
- Form fields have labels
- Error messages are announced
- Dynamic content updates are announced
- Navigation structure is logical
Keyboard Navigation (Web)
- All interactive elements are keyboard accessible
- Tab order is logical
- Focus indicators are visible
- Skip links are provided
- Dialogs trap focus appropriately
- Escape key closes modals
Content
- Text is left-aligned (not justified)
- Line length is under 80 characters
- Headings follow logical hierarchy
- Lists use proper list semantics
- Content scales with system text size
Forms
- All inputs have labels
- Required fields are marked
- Error messages are clear and actionable
- Errors appear near relevant fields
- Validation doesn't block all input
Motion
- Animations respect reduced motion preference
- No flashing content (> 3/second)
- Animations can be paused or disabled
- Critical features work without animation
Testing
- Tested with iOS VoiceOver
- Tested with Android TalkBack
- Tested with keyboard only (web)
- Tested at 200% text scale
- Tested with reduced motion enabled
- Tested with different color blindness filters
Resources
WCAG Guidelines
Testing Tools
Flutter Resources
Mobile Accessibility
Future Improvements
Areas for accessibility enhancement:
- Explicit Semantics - Add Semantics widgets throughout the codebase
- Image alt text - Implement semantic labels for all images
- Focus management - Improve focus order and trap for modals
- Reduced motion - Full support for prefers-reduced-motion
- High contrast mode - Alternative theme for low vision users
- Text-to-speech - Consider audio descriptions for complex content
- Dyslexia support - OpenDyslexic font option
- Screen reader testing - Regular testing with VoiceOver and TalkBack
- ARIA landmarks - For web version navigation
- Accessibility audit - Professional third-party evaluation