Skip to content

Conversation

@JasperVercammen
Copy link

♿ Add VoiceOver and TalkBack Accessibility Support

Summary

Implements comprehensive screen reader accessibility for the timer picker component, enabling VoiceOver (iOS) and TalkBack (Android) users to interact with all picker columns using native swipe gestures.

What's Changed

New Files

  • src/utils/useScreenReaderEnabled.ts - Hook to detect screen reader state with automatic change detection

Modified Files

  • src/components/DurationScroll/types.ts - Added accessibility-related props
  • src/components/DurationScroll/DurationScroll.tsx - Implemented accessible picker with adjustable role
  • src/components/TimerPicker/types.ts - Added accessibility label props
  • src/components/TimerPicker/TimerPicker.tsx - Integrated screen reader detection and passed accessibility props
  • src/index.ts - Exported useScreenReaderEnabled hook
  • README.md - Documented new accessibility features and props

Features

Native Screen Reader Support

  • Pickers become "adjustable" elements when screen readers are enabled
  • Users can swipe up/down to increment/decrement values
  • Values wrap around at boundaries (e.g., 00 → 23 → 00 for hours)

Immediate Audio Feedback

  • New values announced instantly after each gesture
  • Respects formatting preferences (padWithZero, 12-hour format, etc.)

Conditional Behavior

  • Accessibility only active when screen reader is enabled
  • Normal scroll behavior preserved for sighted users
  • Prevents iOS parent-blocking-child accessibility issues

Internationalization Ready

<TimerPicker
    accessibilityLabels={{
        hours: "Heures",
        minutes: "Minutes",
        seconds: "Secondes",
        hint: "Balayez vers le haut ou le bas pour ajuster"
    }}
/>

New Props

TimerPicker / TimerPickerModal

  • accessibilityLabel?: string - Label for entire picker
  • accessibilityLabels?: { days, hours, minutes, seconds, picker, hint } - Per-column labels

Technical Implementation

  • Uses AccessibilityInfo API to detect VoiceOver/TalkBack state
  • Wraps FlatList in accessible View with accessibilityRole="adjustable"
  • Hides child elements from screen readers using importantForAccessibility="no-hide-descendants"
  • Labels explicitly hidden with accessibilityElementsHidden (iOS) and importantForAccessibility (Android)
  • Format functions passed to respect existing display preferences

Breaking Changes

None - fully backward compatible. Default behavior unchanged when screen readers are disabled.

Testing

Manual Testing Required:

  • iOS: Enable VoiceOver (Settings → Accessibility → VoiceOver)
  • Android: Enable TalkBack (Settings → Accessibility → TalkBack)
  • Verify each picker column is focusable
  • Verify swipe up/down adjusts values
  • Verify values are announced immediately
  • Verify wrap-around behavior at min/max values
  • Test with different configurations (12-hour format, custom intervals, limits)

Automated Tests:

All existing tests pass. Accessibility features don't interfere with test queries (default isScreenReaderEnabled={false}).

Documentation

Updated README with:

  • New props in TimerPicker props table
  • Dedicated "Accessibility" section with usage examples
  • Internationalization examples
  • How to use useScreenReaderEnabled hook in custom components

@troberts-28
Copy link
Owner

Hey @JasperVercammen, thank you for putting this PR together. Will review ASAP 🙌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants