feat(a11y): improve accessibility across dialogs, forms, menus, media, and focus flows#3146
Merged
MartinCupela merged 37 commits intomasterfrom Apr 28, 2026
Merged
feat(a11y): improve accessibility across dialogs, forms, menus, media, and focus flows#3146MartinCupela merged 37 commits intomasterfrom
MartinCupela merged 37 commits intomasterfrom
Conversation
β¦n-reader-only text add VisuallyHidden component using inline sr-only styles (clip, 1px box, absolute positioning) preserve content in the accessibility tree while hiding it visually export utility from src/components/VisuallyHidden/index.ts for reuse in upcoming WCAG tasks
β¦LiveRegion Add a shared AriaLiveRegion provider and useAriaLiveAnnouncer hook so components can announce dynamic updates from one consistent place. This improves WCAG 2.1 AA compliance with polite/assertive live regions and reliable re-announcement of repeated messages via clear-then-set microtask logic. Include tests for both regions and repeated announcement behavior.
Add semantic button behavior to MessageUIβs inner wrapper for bounced messages: - switch keyboard handling to onKeyDown (Enter/Space) - add role="button", tabIndex=0, and i18n aria-label - keep non-interactive/failed states without button semantics to avoid nested-interactive violations
Add button semantics to interactive QuotedMessagePreviewUI only:
- set role="button", tabIndex={0}, and aria-label via t('aria/Jump to quoted message')
- handle Enter/Space on keydown to trigger jump behavior
- keep non-interactive preview usages unchanged
β¦Anchor - set role="dialog" and aria-modal="true" when trapFocus is enabled - forward aria-labelledby/aria-describedby and use aria-label as fallback - add DialogPortal regression + jest-axe coverage - mark WCAG task 11 complete in specs/wcag-compliance
β¦llery close behavior
β¦estored switch styling`
β¦ewer primitives
β¦/Viewer call sites
Codecov Reportβ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #3146 +/- ##
==========================================
+ Coverage 82.79% 83.53% +0.73%
==========================================
Files 419 434 +15
Lines 12270 12918 +648
Branches 3951 4157 +206
==========================================
+ Hits 10159 10791 +632
- Misses 2111 2127 +16 β View full report in Codecov by Sentry. π New features to boost your workflow:
|
|
Size Change: +54.1 kB (+8.75%) π Total Size: 672 kB π¦ View Changed
βΉοΈ View Unchanged
|
github-actions Bot
pushed a commit
that referenced
this pull request
May 4, 2026
## [14.1.0](v14.0.1...v14.1.0) (2026-05-04) ### Bug Fixes * add ScrollToLatestMessageButton to ComponentContext ([#3159](#3159)) ([952c125](952c125)) * allow user blocking only in DM-type channels ([#3139](#3139)) ([deda536](deda536)) * decouple msg bubble width from reaction list width ([#3142](#3142)) ([980c233](980c233)) * export AttachmentSelectorContext from the SDK ([#3158](#3158)) ([68efeb5](68efeb5)) * font & box shadow fixes ([#3135](#3135)) ([6d04cdf](6d04cdf)), closes [#3134](#3134) * limit reactions host width (segmented/bottom) ([#3154](#3154)) ([be50105](be50105)) * make search results scrollable ([#3152](#3152)) ([ead6cb5](ead6cb5)) * **MessageList:** prevent message pagination too early on mount ([#3143](#3143)) ([12e282f](12e282f)) * prevent cutting off button outlines in ContextMenu components ([#3151](#3151)) ([b3469f0](b3469f0)) * remove scrollbar gutters from VML ([#3148](#3148)) ([4a6a8ae](4a6a8ae)) ### Features * **a11y:** improve accessibility across dialogs, forms, menus, media, and focus flows ([#3146](#3146)) ([917b7f5](917b7f5)) * change textarea default placeholder text ([#3150](#3150)) ([45b1836](45b1836)) * introduce `MessageUI` to `ComponentContext` ([#3140](#3140)) ([16af18d](16af18d)) ### Refactors * message styling ([#3136](#3136)) ([cd3a9c0](cd3a9c0))
|
π This PR is included in version 14.1.0 π The release is available on: Your semantic-release bot π¦π |
7 tasks
isekovanic
added a commit
to GetStream/stream-chat-react-native
that referenced
this pull request
May 6, 2026
## Summary - Lands an **opt-in** accessibility layer for VoiceOver (iOS) and TalkBack (Android), aligned with primitive shapes from [`stream-chat-react#3146`](GetStream/stream-chat-react#3146). - A11y is **OFF by default** β pass `<Chat accessibility={{ enabled: true }}>` to enable. Existing integrators see no change. When disabled, no announcer mounts, no listeners attach, and `useA11yLabel` short-circuits the `t()` call so hot list paths stay free. - Foundation is in place; gesture-alternative rewrites for `AudioRecorder` and `ImageGallery`, plus the lint plugin and Reassure benchmark, are explicitly deferred (called out in commits and docs). ## What's in this PR 7 conventional commits, each independently reviewable: 1. **`feat(a11y): add opt-in accessibility announcer, context, and hooks`** β `AccessibilityContext` (flat config with `'auto' | 'always' | 'never'` enums), `AccessibilityAnnouncer` + `useAccessibilityAnnouncer` (mirrors `useAriaLiveAnnouncer` from React), `NotificationAnnouncer`, `useIncomingMessageAnnouncements` (verbatim port β same throttle/batch/bounded-id semantics), utility hooks under `package/src/a11y/` (`useScreenReaderEnabled`, `useReducedMotionPreference`, `useResolvedModalAccessibilityProps`, `useAnnounceOnStateChange`, `useA11yLabel`). `<Chat>` wires the provider stack. `aria/*` keys added to all 12 locales (English values; translations are a follow-up β `validate-translations` passes). 16 new unit tests. 2. **`feat(a11y): wire accessibility into base UI primitives`** β Avatar (accepts `name` + auto-composes `aria/Avatar of {{name}}`), Button (role + disabled/selected state), Input (label/hint/state + assertive live region for validation errors), Indicators (live-region progressbar, hidden decorative dots, alert role on error), ProgressControl (`progressbar` / `adjustable` + `accessibilityValue`), BottomSheetModal (uses `useResolvedModalAccessibilityProps` β `accessibilityViewIsModal` on iOS, `importantForAccessibility='yes'` on Android). 3. **`feat(a11y): add accessible message actions, reactions, and reply preview`** β MessageActionList (`role='menu'`), MessageActionListItem (`role='menuitem'`, action title as label), MessageReactionPicker (`role='menu'`), ReactionButton (composed label via `useA11yLabel`), Reply (role+title). 4. **`feat(a11y): announce incoming messages and label scroll-to-latest button`** β MessageList wires `useIncomingMessageAnnouncements({ channel, ownUserId, activeThreadId, threadList })`. ScrollToBottomButton announces unread count. 5. **`feat(a11y): announce AI typing, label channel preview status, mount NotificationAnnouncer`** β AITypingIndicatorView wraps in polite live region + debounced announcer; ChannelMessagePreviewDeliveryStatus composes a Sending/Sent/Delivered/Read label; Channel mounts `<NotificationAnnouncer />` so connection-state transitions reach SR users. 6. **`feat(a11y): add radio/checkbox semantics to poll vote button`** β PollOption VoteButton: `role='radio'` for single-vote polls, `'checkbox'` for multi-vote polls, `accessibilityState={{ checked, selected }}`, option text as label. 7. **`chore(a11y): add maintenance skill and integrator docs`** β `.claude/skills/accessibility/SKILL.md` (RN-adapted port of React's `.cursor/skills/accessibility/SKILL.md`) and `ai-docs/accessibility.md` covering the opt-in default, config schema, localization, public hooks/components, cross-SDK parity, and platform notes. ## Cross-SDK alignment | React (web) | This PR (RN) | |---|---| | `useAriaLiveAnnouncer` | `useAccessibilityAnnouncer` (identical call shape) | | `useIncomingMessageAnnouncements` | identical params + throttle/batch logic | | `aria/*` i18n namespace | shared verbatim | | `AriaLiveRegion` provider | `AccessibilityAnnouncer` (uses `AccessibilityInfo.announceForAccessibility`) | | `NotificationAnnouncer` | same component name; RN announces connection state (no shared notifications queue exists yet) | | `useResolvedModalAriaProps` | `useResolvedModalAccessibilityProps` (`accessibilityViewIsModal` iOS / `importantForAccessibility` Android) | Mobile-only deviation: `<Chat accessibility={...}>` config object β RN needs gesture-alternative toggles (`audioRecorderTapMode`, `imageGalleryScreenReaderMode`, `messageActionsTrigger`) that don't exist on web. ## Out of scope (explicitly deferred) - AudioRecordingButton tap-mode swap (gated on `audioRecorderTapMode`). - ImageGallery screen-reader-mode UI swap (gated on `imageGalleryScreenReaderMode`). - Composed Message label + rotor `accessibilityActions` menu (touches the 1180-line `Message.tsx`). - AttachmentPicker modal trap, AudioAttachment scrub `accessibilityValue`, AutoCompleteInput suggestion-list semantics. - ESLint `eslint-plugin-react-native-a11y` rules β adds a runtime dep, flagged for separate review. - Reassure perf benchmark for `MessageList` with a11y on/off β adds a runtime dep, flagged for separate review. - Translations of `aria/*` keys to non-English locales β English values ship in all 12 so `validate-translations` passes; translators can fill in later. ## Native handler note The plan included an `AccessibilityInfo` handler in `native.ts` (mirroring how `Audio`/`Sound`/etc. are abstracted). Skipped: `AccessibilityInfo` from `react-native` is identical on bare RN and Expo, so a wrapper handler would be unnecessary indirection. ## Test plan - [x] `cd package && yarn tsc --noEmit` β no new errors (only pre-existing ones unrelated to this branch: `AttachmentPickerContent`, `useIsCommandDisabled`, `usePendingAttachmentUpload`). - [x] `cd package && TZ=UTC npx jest src/a11y src/components/Accessibility src/contexts/accessibilityContext` β 16/16 passing. - [x] `cd package && yarn lint` on touched dirs β clean (snapshot files have pre-existing `no-undef` errors, untouched). - [ ] Manual SampleApp smoke on iOS w/ VoiceOver β verify incoming-message announcement, modal focus trap, AITypingIndicatorView announcement. - [ ] Manual SampleApp smoke on Android w/ TalkBack β verify same flows, plus `accessibilityLiveRegion` triggers. - [ ] Render with `accessibility={{ enabled: false }}` and confirm zero behavioral change vs current main. - [ ] Render with `accessibility={{ enabled: true, forceScreenReaderMode: true }}` and confirm accessible variants render where applicable. Plan checked in at `.claude/plans/2026-05-04-accessibility.md`. --------- Co-authored-by: Ivan Sekovanikj <ivan.sekovanikj@getstream.io>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
π― Goal
Improve accessibility across
stream-chat-reactto move key UX paths closer to WCAG 2.1 AA by fixing keyboard navigation gaps, dialog/menu semantics, live announcements, and focus management.π Implementation details
This PR consolidates and expands accessibility work across dialogs, forms, menus, message interactions, media controls, and notifications.
1) Screen reader foundations
VisuallyHiddenutility for SR-only content.AriaLiveRegion+useAriaLiveAnnouncerinfrastructure.NotificationAnnouncercomponent and new-message announcement support.2) Keyboard interaction + semantic correctness
ContextMenu.Dropdown(keys Up, Down, Escape)3) Dialog, modal, and prompt semantics
role="dialog",aria-modal, label/description wiring with fallback behavior) across portal/modal primitives.4) Forms, controls, and media
SwitchFieldto native switch semantics with preserved styling.5) Visual/accessibility polish
focus-visiblestyles.prefers-reduced-motionsupport.BaseImage/Avatar.6) AI Skill to promote future maintenance
β Validation
jest-axecoverage).π₯ Breaking changes
None expected.
Changes are behaviorally additive and focused on semantics, keyboard support, and assistive technology compatibility.
π¨ UI Changes
Mostly non-visual.
Any visual changes are limited to focus indicators and reduced-motion behavior.