[Android] Pagination dots show active color bleeding on inactive dots
Describe the bug When using Pagination.Basic with a carousel, inactive pagination dots display thin slivers of the active dot color on their edges. This visual artifact only occurs on Android devices and creates a broken appearance where inactive dots show traces of the active color instead of displaying purely in their designated background color.
To Reproduce Open the Expo Snack: https://snack.expo.dev/@grant_polar/react-native-reanimated-carousel-pagination-dot-bug Run the snack on an Android device or emulator Observe the pagination dots at the bottom Notice thin blue slivers on the edges of inactive dots (particularly visible on 2nd and 5th dots)
Expected behavior Inactive pagination dots should display only their designated background color (#e5e5e5) without any traces of the active dot color. Each dot should be clearly either active (blue) or inactive (gray) with no visual bleeding between states.
Screenshots
Versions (please complete the following information):
- react: v19.0.0
- react-native: v0.79.2
- react-native-reanimated: v3.17.4
- react-native-reanimated-carousel: v4.0.2
- react-native-gesture-handler: v2.24.0
Smartphone (please complete the following information):
- Device: Android on Expo Snack. Also occurs on Medium_Phone_API_35 Android Simulator
Additional context This issue only occurs on Android - iOS renders the pagination dots correctly without any color bleeding.
Hi @grantackerman! I'm Dosu and I’m helping the react-native-reanimated-carousel team.
There are currently no existing issues or discussions in the repository that directly address the pagination dots color bleeding on Android.
Based on the code, the pagination dots are rendered using the Pagination.Basic component, which passes dotStyle and activeDotStyle props to each dot. The PaginationItem component applies these styles and uses an animated view to handle transitions. Notably, the activeDotStyle includes overflow: hidden, and the animation logic uses translation and interpolation to move the active dot indicator. This setup, combined with platform-specific rendering differences on Android, may cause the thin slivers of the active color to appear on the edges of inactive dots, especially during transitions or due to subpixel rendering issues on Android devices [PaginationItem source].
Potential workarounds you can try:
- Explicitly set the
borderRadiusin bothdotStyleandactiveDotStyleto ensure the dots are perfectly round and avoid any overflow artifacts. - Remove or adjust the
overflow: hiddenproperty inactiveDotStyleto see if it affects the bleeding. - Slightly increase the gap between dots using the
containerStyle'sgapproperty to minimize overlap or visual blending. - Ensure that the
widthandheightvalues for both styles are identical and use whole numbers to avoid subpixel rendering issues.
If these adjustments do not resolve the issue, it may be necessary to further investigate the animation logic or consider using a custom pagination renderer that avoids animated overlays for the active dot on Android.
To reply, just mention my name @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
@dosu slightly increasing the gap worked for me! The solution feels a little bit suboptimal. If I have 5 dots with size of 10, gap of 10 has the issue, gap of 11 doesn't, and gap of 12 does. I feel like you shouldn't ever get the bleeding. Having gap of 11 is a little weird, but it's workable
You're right—having to use a gap of 11 to avoid color bleeding does feel odd and isn't ideal. This issue happens because of how Android handles view masking and anti-aliasing, especially when using overflow: 'hidden' and borderRadius together. The animation logic in the PaginationItem component moves the active dot using translation, and when the gap aligns just right (like 10 or 12), subpixel rendering or overlap can cause the active color to bleed onto inactive dots. A gap of 11 seems to sidestep this artifact, likely due to how the pixels align at that spacing, but it's not a robust fix and can vary with device or screen density source.
You might get better results by experimenting with these style tweaks: explicitly set borderRadius to half the dot size in both dotStyle and activeDotStyle, try removing or adjusting overflow: 'hidden', and ensure both width and height are whole numbers. If the issue persists, consider a custom pagination renderer that avoids masking and overlays for Android, since this is a known platform rendering quirk and not specific to this library source.
Let me know if you want help with a custom workaround or want to dig deeper into the animation logic!
To reply, just mention my name @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other