Skip to content

feat: introduce rudimentary RTL support to the RN SDK#3522

Merged
isekovanic merged 16 commits intodevelopfrom
fix/rtl-fixes
Mar 31, 2026
Merged

feat: introduce rudimentary RTL support to the RN SDK#3522
isekovanic merged 16 commits intodevelopfrom
fix/rtl-fixes

Conversation

@isekovanic
Copy link
Copy Markdown
Contributor

@isekovanic isekovanic commented Mar 30, 2026

🎯 Goal

This PR addresses a bunch of RTL issues our SDK has had over the last couple of majors and makes sure we have first class support for it.

Summary

This PR fixes a set of RTL specific UI regressions that were affecting several surfaces in the SDK.

The issues were not all caused by the same thing. Some were caused by relying on implicit platform mirroring, some by scroll/list containers using the wrong coordinate space in RTL, and some by spacing being applied on the wrong level of a scrollable surface. The result was a mix of broken alignment, unstable animations, blank gallery content, and inconsistent behavior between iOS and Android.

This PR makes those behaviors explicit and stabilizes the affected components without changing their public API.

Problems fixed

  • Channel swipable actions not working properly with RTL
  • Swipe to reply not working properly with RTL
  • Audio playing not working properly with RTL
  • Context menu positioning being broken with RTL
  • Attachment upload preview strip behaved incorrectly in RTL, especially on Android.
  • Poll creation and poll result modals had broken spacing in RTL, with content appearing pinned to one side.
  • Poll text alignment was inconsistent between Android and iOS.
  • Poll inputs inside poll screens/modals were not following the same RTL text alignment behavior as the main composer input.
  • The image gallery could open in RTL, but images would not render even though gestures still worked.
  • The thread list item channel name at the top of each thread card was aligned incorrectly in RTL.

What changed

Gesture driven bugs in RTL

For a lot of the features in the SDK, such as channel swipeables, STR and similar we rely on gestures directly in order to be able to do an action. Gestures in RN are never RTL compliant and so we need to handle these cases explicitly. Most of the time, simple inversions work just fine however in certain instances (like our SwipeableWrapper) a small adapter layer is needed to be able to distinguish between left and right.

RTL attachment preview stability

The attachment preview strip now uses a stable coordinate-space approach in RTL so it no longer fights the list/pager/layout system.

  • stabilized the horizontal preview list so RTL no longer depends on fragile scroll compensation behavior
  • removed the RTL specific scroll race that was causing inconsistent Android behavior
  • kept the visual RTL ordering/alignment behavior while avoiding the previous listwide bounce/shift issues
  • disabled problematic overscroll behavior where needed so native scroll physics stop interfering with the preview strip

Unfortunately, I could not at all get RTL to work natively here due to the fact that reanimated's layout animations were completely messing with the underlying ScrollView's width calculations when horizontal. After a couple of hours of debugging I settled with disabling RTL directly and handling all edge cases manually.

This was the area with the most platform sensitivity, especially on Android.

Poll modal spacing and layout fixes

The poll modal issue turned out not to be an individual-screen bug. The main problem was that outer padding was being applied on scroll/list wrappers, which behaves badly in Android RTL and can look like horizontal padding is only applied from one side.

The fix was to move spacing from outer scroll containers into their content containers for the shared poll modal surfaces.

This fixes layout issues in:

  • create poll
  • poll results
  • poll answers list
  • full option results views
  • other shared poll modal surfaces using the same pattern

As part of that cleanup, earlier safe-area wrapper changes were reverted because they were not the actual fix.

Poll text alignment on iOS RTL

Poll titles, labels, cards, and result text were relying too much on implicit platform behavior. Android happened to look mostly correct, while iOS did not.

This PR makes the relevant non-input poll text alignment explicit where needed so the iOS rendering matches the intended RTL layout.

It also replaces a few LTR-only spacing rules with logical start-aware spacing where the text/result rows needed it.

Poll input alignment consistency

Poll inputs now follow the same RTL alignment rule as the existing autocomplete composer input instead of using a separate behavior.

That means poll inputs now use:

  • textAlign: I18nManager.isRTL ? 'right' : 'left'

This was applied to the poll-specific input surfaces, including modal inputs, so editable text behaves consistently across the SDK.

Image gallery rendering in RTL

The gallery rendering issue was caused by a coordinate-space mismatch.

The pager/slide math in the image gallery is LTR-based, but the gallery container was still inheriting RTL layout direction. In practice, that meant the gallery could open and still respond to swipe/
close gestures, while the image slides themselves were effectively laid out off-screen.

The fix was to force only the gallery pager strip into LTR coordinates while leaving the rest of the experience intact.

This restores image rendering in RTL without changing the gallery gesture model.

Thread list item alignment

The channel name at the top of thread list items now uses explicit alignment so it renders correctly in RTL.

This is a narrow change scoped only to that text style.

Why this approach

The common theme across these issues is that implicit RTL handling was not reliable enough for these components.

The fixes in this PR intentionally avoid broad “flip everything” logic. Instead, each surface now expresses the behavior it actually needs:

  • content containers own spacing in scrollable modal surfaces
  • text components that need explicit alignment get explicit alignment
  • input components use the same RTL rule as the main composer
  • animation/pager surfaces that depend on physical coordinates are pinned to an LTR coordinate space when that is what their math assumes

That keeps the fixes targeted and avoids introducing new regressions in already. correct layouts.

I'm sure there are other edge cases with RTL that I did not manage to notice, however as a first step this should do just fine, especially considering we've never had any RTL support before.

🛠 Implementation details

🎨 UI Changes

iOS
Before After
Android
Before After

🧪 Testing

☑️ Checklist

  • I have signed the Stream CLA (required)
  • PR targets the develop branch
  • Documentation is updated
  • New code is tested in main example apps, including all possible scenarios
    • SampleApp iOS and Android
    • Expo iOS and Android

@isekovanic isekovanic requested a review from oliverlaz March 30, 2026 22:02
@Stream-SDK-Bot
Copy link
Copy Markdown
Contributor

Stream-SDK-Bot commented Mar 30, 2026

SDK Size

title develop branch diff status
js_bundle_size 346 KB 346 KB 0 B 🟢

@isekovanic isekovanic merged commit 13944da into develop Mar 31, 2026
5 checks passed
@isekovanic isekovanic deleted the fix/rtl-fixes branch March 31, 2026 07:20
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