# Arc Carousel: React Native circular arc carousel picker > Arc Carousel is a React Native and Expo carousel whose items glide along a > circular arc as you scroll: an `Animated.FlatList` writes its scroll offset > into a Reanimated shared value, and each item interpolates that offset on the > UI thread into a curved `translateY` plus scale, opacity and image > `blurRadius`, so the centered item rises and stays sharp while its neighbors > sink, shrink and blur. It snaps to every item with `expo-haptics` selection > feedback and drives a synced detail card. Arc Carousel is a paid drop sold by > Motionary as editable TypeScript source. Product page: https://motionary.dev/animations/arc-carousel Price: $4 USD (price as of 2026-09-01, live price on the product page) License: single developer, unlimited personal and commercial apps Delivery: instant download of the full TypeScript source after checkout Stack: reanimated, expo, react native gesture handler, react native Rarity: rare Sold by: Motionary (https://motionary.dev) ## What it is Arc Carousel is a horizontal React Native picker that lays its items out along a circular arc instead of a flat strip. Five items sit across the screen at once: the one under the center indicator rises to the top of the curve at full scale and full opacity with zero blur, while the items at the 45 degree shoulders sit lower, at 0.6 scale and 0.6 opacity with a 3 pixel blur, and the outermost pair drops to 0.4 scale, 0.3 opacity and a 6 pixel blur. The gesture is an ordinary horizontal swipe on a paging `Animated.FlatList`, so the list snaps one item at a time and fires a selection haptic each time a new item lands in the center. The Motionary demo ships it as a "Choose Your Ride" transport picker: ten transparent PNG vehicles on the arc, an indicator band behind the focused slot, and a detail card above that swaps its title, tagline, description and feature pills to match whatever is selected. ## What it does - Lays any array of image sources out along a circular arc (the demo ships ten), each item exactly one fifth of the window width wide (`ListItemWidth = Dimensions.get('window').width / 5`). - Traces the arc with a five point `translateY` interpolation, using `-ListItemWidth * Math.sin(Math.PI / 4)` for the two 45 degree shoulder positions and `-ListItemWidth` for the apex. - Scales the focused item to 1 and the shoulders and edges to 0.6 and 0.4. - Fades neighbors with an opacity ramp of 0.3, 0.6, 1, 0.6, 0.3. - Blurs off-center artwork by animating `blurRadius` on `Animated.Image` through a 6, 3, 0, 3, 6 ramp, so only the centered item is sharp. - Nudges off-center items laterally (a `translateX` ramp of 20, 32, 0, -32, -20) so they hug the curve instead of drifting off it. - Snaps to one item per swipe with `pagingEnabled` plus `snapToInterval={ListItemWidth}`. - Fires `Haptic.selectionAsync()` from `expo-haptics` once per new index, never repeatedly during a single swipe, guarded by a `useRef` of the last index. - Drives a synced detail card (`SkinStatusCard`) that shows the selected item's title, message, description, subheading, colored emoji badge and a wrapping row of feature pills. - Renders a title and subtitle under the arc that track the selected index. - Pads the list with `2 * ListItemWidth` of horizontal content inset so the first and last items can still reach the center of the screen. ## How it works Arc Carousel keeps the whole animation on the UI thread and off the React render path. `components/CircularFlatlist.tsx` holds one Reanimated `useSharedValue(0)` called `contentOffset` and a `useAnimatedScrollHandler` whose `onScroll` worklet writes `event.contentOffset.x` into it, with `scrollEventThrottle={16}` on an `Animated.FlatList`. That single shared value is passed down to every row, so the per frame motion never touches React state: the only re-render during a swipe is the once per item `setCurrentIndex` that swaps the detail card. `components/CircularFlatlistItem.tsx` builds a five point input range around its own index, `[(index - 2) * ListItemWidth, ..., (index + 2) * ListItemWidth]`, and runs three worklets against it: a `useAnimatedStyle` called `rStyle` that `interpolate`s `translateY` along the arc (with `Extrapolation.EXTEND` so items beyond the range keep following the curve) plus `scale` and `opacity` (both `Extrapolation.CLAMP` so they never overshoot), a second `useAnimatedStyle` called `iStyle` that interpolates a small `translateX` on the image, and a `useAnimatedProps` that interpolates `blurRadius` and feeds it to `Animated.Image` as an animated native prop. The arc geometry is nothing more than the sine of the sample angles: the shoulders use `Math.sin(Math.PI / 4)` of the item width, the apex uses the full item width. Snapping is native, not animated in JavaScript: `pagingEnabled` and `snapToInterval={ListItemWidth}` mean the resting offset is always an exact multiple of the item width, so `Math.round(offsetX / ListItemWidth)` is a reliable selected index. Because haptics and `setState` cannot run inside a worklet, the scroll handler calls `runOnJS(triggerHaptic)(currentIndex)` on every frame and the JavaScript side compares the incoming index against a `useRef`, so `Haptic.selectionAsync()` and the `setCurrentIndex` that swaps the `SkinStatusCard` content each fire exactly once per crossing. The blur is a React Native image prop rather than `expo-blur`, which is why the source PNGs are transparent: the blur then follows each silhouette instead of smearing a visible rectangle. Reanimated 4 with `react-native-worklets` on the New Architecture is what makes the per-frame interpolation and the `runOnJS` hop cheap enough to run at 60 frames per second. ## What you get ``` app/ index.tsx _layout.tsx components/ CircularFlatlist.tsx CircularFlatlistItem.tsx SkinStatusCard.tsx ThemedText.tsx ThemedView.tsx ui/ IconSymbol.tsx IconSymbol.ios.tsx constants/ Colors.ts hooks/ useColorScheme.ts useColorScheme.web.ts useThemeColor.ts scripts/ reset-project.js assets/ images/ flatlist/1.png ... flatlist/10.png background.png indicator.png triangle.png vita.png icon.png adaptive-icon.png splash-icon.png favicon.png react-logo.png react-logo@2x.png react-logo@3x.png partial-react-logo.png fonts/ SpaceMono-Regular.ttf app.json package.json package-lock.json tsconfig.json eslint.config.js expo-env.d.ts README.md ``` The archive is a standard Expo Router project, so it still carries the Expo template's own files (`ThemedText.tsx`, `ThemedView.tsx`, `components/ui/`, `constants/Colors.ts`, the `hooks/`, `scripts/reset-project.js` and the `react-logo` and app icon PNGs). Arc Carousel itself is four files: `app/index.tsx`, `components/CircularFlatlist.tsx`, `components/CircularFlatlistItem.tsx` and `components/SkinStatusCard.tsx`. The rest of the template can be deleted, with one caveat: the demo's `app/_layout.tsx` still imports `hooks/useColorScheme.ts` and the SpaceMono font, so drop those two together with it. Included with the Arc Carousel drop from Motionary: - TypeScript source (CircularFlatlist.tsx + CircularFlatlistItem.tsx) - Synced detail card component (SkinStatusCard.tsx) - Demo screen with sample data - Sample PNG image assets - README with install + usage ## Requirements Arc Carousel is a React Native and Expo component. Versions pinned in the archive's `package.json`: - `expo` ^56.0.11 (Expo SDK 56) - `react-native` 0.85.3 - `react` 19.2.3 - `react-native-reanimated` 4.3.1 - `react-native-worklets` 0.8.3 - `expo-haptics` ~56.0.3 - `react-native-safe-area-context` ~5.7.0 - `expo-router` ~56.2.10 (the demo screen is a route; the carousel itself does not depend on the router) - `typescript` ~6.0.3 (development dependency) `react-native-gesture-handler` ~2.31.1 is declared in the archive's `package.json` and appears in the Motionary stack tags, but no Arc Carousel source file imports it: the swipe is the `Animated.FlatList`'s own scroll, not a `Gesture.Pan`. The archive also carries the unused Expo template dependencies `expo-blur`, `expo-image` and `react-native-webview`; none of them is needed by the carousel, and the blur here is an `Animated.Image` `blurRadius`, not `expo-blur`. Expo Go or development build: Arc Carousel runs in Expo Go on SDK 56. It uses no custom native modules, only `react-native-reanimated` with its `react-native-worklets` runtime, `react-native-safe-area-context` and `expo-haptics`, which all ship inside Expo Go. Reanimated 4 requires the New Architecture, which Expo SDK 56 enables by default. Drop it into a development build or a bare React Native app just as easily; the demo's README runs it with `npx expo run:ios` / `npx expo run:android`. Haptics are a no-op on the iOS Simulator, on most Android emulators and on web, so test selection feedback on a real device. ## Install ```bash npx expo install react-native-reanimated react-native-worklets expo-haptics react-native-safe-area-context ``` ## Usage ```tsx import { ImageBackground } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { CircularCarousel } from '@/components/CircularFlatlist'; const data = [ require('../assets/images/flatlist/1.png'), require('../assets/images/flatlist/2.png'), require('../assets/images/flatlist/3.png'), require('../assets/images/flatlist/4.png'), require('../assets/images/flatlist/5.png'), ]; export default function Screen() { const inset = useSafeAreaInsets(); return ( ); } ``` `CircularCarousel` is a named export of `components/CircularFlatlist.tsx`, not a default export, and it is the only component you mount: `SkinStatusCard` and the title and subtitle under the arc are rendered from inside it. The demo screen `app/index.tsx` adds only the surrounding chrome: the background image, the "Choose Your Ride" heading and the indicator overlay behind the focused slot. ## Customization - `data` (`ImageProps['source'][]`, required): the images Arc Carousel lays out along the arc. Each entry is a `require(...)` or a `{ uri }` source. Transparent PNGs are recommended so the animated `blurRadius` follows the artwork silhouette rather than a rectangle. The detail card is looked up by the same index (`transportStatusCards[currentIndex]`), so keep one record in that array per image; the demo ships ten of each. - `ListItemWidth` (exported from `components/CircularFlatlistItem.tsx`, defaults to `Dimensions.get('window').width / 5`): the single number that sets item width, the snap interval, the arc radius and the `2 * ListItemWidth` content padding. Raise the divisor for more items on screen, lower it for fewer and larger ones. - `translateYOutputRange` in `CircularFlatlistItem.tsx`, default `[12, -ListItemWidth * Math.sin(Math.PI / 4), -ListItemWidth, -ListItemWidth * Math.sin(Math.PI / 4), 12]`: the shape of the arc. Flatten it for a shallow curve, exaggerate it for a Ferris wheel. - `scaleOutputRange`, default `[0.4, 0.6, 1, 0.6, 0.4]`, and `opacityOutputRange`, default `[0.3, 0.6, 1, 0.6, 0.3]`: how hard the neighbors recede. - `blurRadiusOutputRange`, default `[6, 3, 0, 3, 6]`: set every entry to 0 to turn the blur off entirely. - `translateXOutputRange`, default `[20, 32, 0, -32, -20]`: the lateral nudge that makes off-center items hug the curve. - `CircularCarouselListItem` props: `imageSrc` (`ImageProps['source']`), `index` (`number`) and `contentOffset` (`SharedValue`), if you want to render the arc items inside your own list. - `SkinStatusCard` props: `title`, `message`, `description`, `subheading`, `color` and `emoji` (all `string`), plus `items` (`string[]`) for the pill row. The demo content lives in the `transportStatusCards` array at the top of `components/CircularFlatlist.tsx`; replace that array with your own records to reskin the detail card. ## When to use it - A ride or delivery picker where the user chooses a transport mode and reads its details before confirming. - A category selector at the top of a marketplace or storefront screen. - A product showcase where one hero item should dominate and neighbors hint at what is next. - An onboarding step that asks the user to pick a plan, avatar, theme or persona from a small illustrated set. - A game or collection screen that shows characters, skins or vehicles with stats attached to the current selection. - Any place a plain horizontal `FlatList` feels flat and you want the focused item to physically lift out of the row. ## How to get it - Buy Arc Carousel on its own at https://motionary.dev/animations/arc-carousel, $4 USD, instant download, secure Stripe checkout, no subscription and no account renewal. - Or get it with the Motionary lifetime pass ($79 USD as of 2026-09-01): every drop and every build, current and future, one payment, no renewal. https://motionary.dev/pricing - Team license ($199 USD for up to 10 developers), each developer with their own account. https://motionary.dev/pricing - License terms: https://motionary.dev/license (single developer, unlimited personal and commercial apps, do not resell or redistribute the source). - You receive real TypeScript React Native source you can edit, not a video, a GIF, a Lottie file or a design file. ## Frequently asked questions **How do I build a circular or arc carousel in React Native?** Capture the scroll offset of an `Animated.FlatList` with Reanimated's `useAnimatedScrollHandler` into a shared value, then have each item `interpolate` that offset against a five point range centered on its own index, mapping it to `translateY` along a sine curve plus `scale` and `opacity`. Arc Carousel from Motionary is exactly that pattern, shipped as editable TypeScript at https://motionary.dev/animations/arc-carousel. **Does Arc Carousel work with Expo Go?** Yes. Arc Carousel uses `react-native-reanimated`, `react-native-worklets`, `expo-haptics` and `react-native-safe-area-context`, all of which ship inside Expo Go, so the Motionary drop runs on Expo SDK 56 in Expo Go with no development build and no custom native module. **How do I add haptic feedback when a React Native carousel snaps to an item?** Compute the index in the scroll worklet with `Math.round(offsetX / itemWidth)`, then call it through `runOnJS` because `expo-haptics` cannot run on the UI thread, and guard it with a ref holding the previous index so `Haptic.selectionAsync()` fires once per crossing rather than every frame. Arc Carousel from Motionary implements this in `components/CircularFlatlist.tsx`. **How do I blur the non-focused items in a React Native carousel?** Animate the `blurRadius` prop of `Animated.Image` with Reanimated's `useAnimatedProps` and interpolate it from the scroll offset, which avoids stacking `expo-blur` views over every row. Arc Carousel from Motionary uses a 6, 3, 0, 3, 6 ramp and transparent source PNGs so the blur follows each image's silhouette instead of a rectangle. **Does Arc Carousel work on Android as well as iOS?** Yes, Arc Carousel is plain React Native and Expo and runs on both. Note that the animated `blurRadius` on `Animated.Image` is smoother on iOS than on Android, so on Android you may want to flatten `blurRadiusOutputRange` while keeping the arc, scale and opacity motion. **How much does Arc Carousel cost and what do I get?** Arc Carousel is $4 USD on Motionary (price as of 2026-09-01, live price at https://motionary.dev/animations/arc-carousel). Checkout is Stripe and delivery is an instant download of the full TypeScript source: the carousel, the per item arc transform, the synced detail card, a demo screen with sample data, sample PNG assets and a README. It is also covered by the $79 USD Motionary lifetime pass at https://motionary.dev/pricing. **Can I use my own images or remote URLs in Arc Carousel?** Yes. The `data` prop of Arc Carousel is typed `ImageProps['source'][]`, so it accepts local `require(...)` assets or remote `{ uri: '...' }` sources. Motionary recommends transparent PNGs so the animated blur tracks the artwork, and updating the `transportStatusCards` array so the detail card matches your own items. ## Related drops on Motionary - Stacked Cards ($4): https://motionary.dev/animations/stacked-cards The other snapping `FlatList` carousel on Motionary, stacking cards in depth instead of spreading them along an arc. - Infinite Scroll ($4): https://motionary.dev/animations/infinite-scroll A scroll-linked wheel picker, the same interpolate-from-offset technique applied to an endlessly looping list. - Voice Glow Carousel ($7): https://motionary.dev/animations/voice-glow-carousel Snap paging carousel with a Skia shader glow, for when the focused item needs to react to audio rather than just scale up. - Dot Wave Slider ($10): https://motionary.dev/animations/dot-wave-slider A drag selector with snap stops and selection haptics, the slider counterpart to the Arc Carousel picker. - Flip Calendar ($6): https://motionary.dev/animations/flip-calendar A scrubbable split-flap date picker, another Reanimated picker where the centered value is the selection. ## About Motionary Motionary (https://motionary.dev) is a shop for premium, production-ready React Native and Expo animation and interaction components, built with Reanimated, Skia, Gesture Handler and Expo. Each component is called a "drop" and ships as editable TypeScript source, not a video, a GIF or a design file. Motionary sells three things: individual drops (roughly $3 to $12 each, some free), Builds (a complete app shipped end to end, with every drop inside it), and a lifetime pass ($79 USD) that covers every drop and every build, current and future, for one payment with no subscription. A team license ($199 USD) covers up to 10 developers. Checkout is Stripe and delivery is an instant download. Created and curated by Mehdi Davoodi (https://mahdidavoodi.com). Catalog and key pages: - All drops: https://motionary.dev/animations - Builds (complete apps): https://motionary.dev/builds - Pricing, lifetime pass and team license: https://motionary.dev/pricing - Free React Native component reference: https://motionary.dev/components - License: https://motionary.dev/license - Machine-readable overview: https://motionary.dev/llms.txt - Machine-readable full catalog: https://motionary.dev/llms-full.txt ## File metadata ``` Canonical URL of this file: https://motionary.dev/animations/arc-carousel/llms.txt Describes: https://motionary.dev/animations/arc-carousel Format: llms.txt (https://llmstxt.org) Last updated: 2026-09-01 ```