Skip to main content

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 TypeMinimum RatioWCAG Level
Normal text (< 18pt)4.5:1AA
Large text (≥ 18pt or 14pt bold)3:1AA
UI components and graphics3:1AA
Inactive/disabled elementsNo requirement-

Color Palette Contrast

BookWish's primary color palette has been tested for sufficient contrast:

CombinationRatioUsagePasses
Ink Blue on Parchment13.2:1Body text, primary contentAAA
Ink Blue on White14.1:1Card content, buttonsAAA
Teal Edge on White3.4:1Interactive elementsAA
Amber Star on White1.7:1Backgrounds only (not text)-
Coral Spine on White3.1:1Error states, warningsAA
Success Green on White4.8:1Success messagesAA
Error Red on White5.4:1Error textAA

Testing Color Contrast

Tools for verification:

How to test:

  1. Extract hex color values from theme.dart
  2. Input foreground and background colors into checker
  3. Verify ratio meets 4.5:1 for normal text, 3:1 for large text
  4. 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 TypeMinimum SizeRecommended
Primary buttons48x48 dp48x48 dp
Icon buttons48x48 dp48x48 dp
Text buttons48dp height48dp height
List items48dp height56dp height
Form inputs48dp height52dp height
Switches/toggles48x48 dp48x48 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 TypeLabel FormatExample
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:

  1. Settings > Accessibility > VoiceOver
  2. Enable VoiceOver
  3. Swipe right/left to navigate
  4. Double-tap to activate

Android TalkBack:

  1. Settings > Accessibility > TalkBack
  2. Enable TalkBack
  3. Swipe right/left to navigate
  4. 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,
),
)

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:

  1. Header navigation
  2. Primary content
  3. Secondary navigation
  4. 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

ShortcutActionScope
TabMove focus forwardGlobal
Shift + TabMove focus backwardGlobal
Enter / SpaceActivate buttonButtons
EscapeClose dialog/modalDialogs
Arrow keysNavigate listsLists

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

StyleSizeMin TouchUsage
Heading Large24pt-Page titles
Heading20pt-Section headers
Body16pt-Primary content
Body Small14pt-Secondary content
Caption12pt-Metadata (use sparingly)
Button Text16pt48dp heightInteractive 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:

  1. Explicit Semantics - Add Semantics widgets throughout the codebase
  2. Image alt text - Implement semantic labels for all images
  3. Focus management - Improve focus order and trap for modals
  4. Reduced motion - Full support for prefers-reduced-motion
  5. High contrast mode - Alternative theme for low vision users
  6. Text-to-speech - Consider audio descriptions for complex content
  7. Dyslexia support - OpenDyslexic font option
  8. Screen reader testing - Regular testing with VoiceOver and TalkBack
  9. ARIA landmarks - For web version navigation
  10. Accessibility audit - Professional third-party evaluation