# Mini-Player Sheet: React Native Apple Music expandable now-playing sheet > Mini-Player Sheet is a React Native and Expo component that expands a docked > mini-player bar into a full-screen now-playing view: a detached > @gorhom/bottom-sheet BottomSheetModal exposes its animatedIndex as a > Reanimated shared value, and that one value interpolates the sheet height, > the background color and the album artwork (40 to 240) while the compact and > full layouts cross-fade on the UI thread. It is a free drop from Motionary > (https://motionary.dev), shipped as editable TypeScript source. Product page: https://motionary.dev/animations/mini-player-sheet Price: Free (free as of 2026-09-01, live price on the product page) License: single developer, unlimited personal and commercial apps Delivery: instant free download of the full TypeScript source Stack: reanimated, expo, react native gesture handler, react native Rarity: common Sold by: Motionary (https://motionary.dev) ## What it is Mini-Player Sheet is a React Native and Expo interaction modelled on the Apple Music mini-player: a small bar docked above the tab bar showing album art, the track title and play/forward buttons, which expands into the full now-playing screen. Tapping the bar, or dragging the expanded sheet back down, drives one continuous morph rather than a screen push: the artwork grows and travels from the bar's left edge to the centre of the screen, the background darkens from white to gray, the rounded floating card squares off into a full-bleed sheet, and the compact row cross-fades into the full transport controls. A single album art image is used for both states, so the artwork never pops or swaps. This Motionary drop is the UI and the animation, not an audio engine: the scrubber, volume bar and transport glyphs are static, so you wire them to your own player. ## What it does - Presents itself on mount as a docked mini-player using a detached `BottomSheetModal` with snap points `[160, 160, '100%']`. - Expands to full screen when the collapsed bar is tapped: a `Pressable` calls `bottomSheetRef.current?.snapToIndex(2)`. - Collapses again by dragging the expanded sheet down, with the drag gesture gated so the collapsed bar stays tappable instead of draggable. - Grows one shared album art image from a 40pt square to a 240pt square (both `width` and `height` interpolated) and springs it from the bar corner to `width / 2 - 120` across and `inset.top + 60` down. - Cross-fades the sheet background from `#FFFFFF` to `#7C7D80` with `interpolateColor` over the index range `[1, 1.4]`. - Animates the container from a 54pt tall floating card (12pt side margins, 12pt radius, soft `boxShadow`) to a full-height, square-cornered, edge-to-edge sheet, dropping the shadow and the radius once the index passes 1.05. - Cross-fades the collapsed row (title plus play and fast-forward glyphs) and the expanded layout (title, artist, progress bar with elapsed and remaining time, backward/play/forward transport, a static volume bar, and the three bottom glyph images `1.png`, `2.png` and `3.png`: a people/SharePlay icon, AirPlay and a queue icon) with 100ms `withTiming` on two opacity shared values. - Ships the surrounding Apple Music style bottom tab bar, including the red `#FA233B` active tint, so the sheet sits in a realistic screen. - Uses one iOS-tuned spring config (`damping: 500`, `stiffness: 1000`, `mass: 3`, `overshootClamping: true`) for both the sheet's own `animationConfigs` and the artwork springs, so nothing overshoots. - Runs on iOS and Android from the same code, with no native module of its own. ## How it works The whole Mini-Player Sheet screen is a single `BottomSheetModal` from @gorhom/bottom-sheet v5, rendered `detached` with three snap points, `[160, 160, '100%']`, and presented on mount from a `useEffect` (`bottomSheetRef.current?.present()`). The sheet's progress is handed to react-native-reanimated by passing shared values into its `animatedIndex` and `animatedPosition` props, so `bottomSheetIndex` is a live 0 to 2 value on the UI thread and every part of the morph is a pure function of it rather than a scripted timeline. One `useAnimatedStyle` reads `bottomSheetIndex.get()` and drives the container: `interpolate` with `Extrapolation.CLAMP` maps `[1, 2]` to a `height` of 54 to the full window height and a `marginHorizontal` of 12 to 0, `interpolateColor` maps `[1, 1.4]` from `#FFFFFF` to `#7C7D80`, and a plain threshold test at index 1.05 switches `borderRadius` and `boxShadow` off, which is what makes the floating card read as "docked" until it commits to expanding. A second `useAnimatedStyle` animates the same `Animated.Image`, interpolating `width` and `height` from 40 to 240 off the same index while its `translateX` and `translateY` come from separate `imageTranslateX` and `imageTranslateY` shared values, so the artwork is one continuous element and never a cross-faded pair of images. Those two translations are the only scripted part: the sheet's `onAnimate(from, to)` callback branches on the index pair, firing `withSpring` to the centred position when going `0 -> 2` and back to `(8, 8)` when going `2 -> 1` or `2 -> 0`, alongside `withTiming(x, { duration: 100 })` on `largeOpacity` and `smallOpacity` to cross-fade the two layouts. The same `onAnimate` callback also flips React state through `setCanPan`, which feeds `enableContentPanningGesture={canPan}`, so react-native-gesture-handler only claims content drags while the sheet is expanded, and `enableOverDrag`, `enableDynamicSizing` and `enablePanDownToClose` are all `false` so the sheet can never be flung away. All of the interpolation runs in worklets on the UI thread; only the `canPan` toggle crosses back to JavaScript. The two files that matter are `app/index.tsx` (the sheet, the gestures and every animated style) and `app/_layout.tsx` (the `GestureHandlerRootView`, `SafeAreaProvider` and `BottomSheetModalProvider` the sheet requires at the root). ## What you get ``` app/ _layout.tsx index.tsx assets/ fonts/ SpaceMono-Regular.ttf images/ 1.png 2.png 3.png gnx.jpeg adaptive-icon.png favicon.png icon.png partial-react-logo.png react-logo.png react-logo@2x.png react-logo@3x.png splash-icon.png constants/ Colors.ts hooks/ useColorScheme.ts useColorScheme.web.ts useThemeColor.ts scripts/ reset-project.js app.json eslint.config.js expo-env.d.ts metadata.json package.json package-lock.json tsconfig.json README.md .gitignore ``` - TypeScript source of the sheet itself, `app/index.tsx` (default export `Index`), including every animated style, the snap points and the spring config. - The root provider setup the sheet needs (`app/_layout.tsx`). - A runnable demo: `app/index.tsx` is the Expo Router index route, so the archive is a complete Expo project that shows the interaction on `expo start` with no wiring. - Album art (`assets/images/gnx.jpeg`) and the three white-tinted bottom glyphs `1.png` (people/SharePlay), `2.png` (AirPlay) and `3.png` (queue). - README with the stack, an explanation of the technique and run instructions. - Color constants and color-scheme hooks, plus the Expo app config, tsconfig and eslint config from the Expo Router template. ## Requirements Versions are the ones pinned in this drop'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` (required by Reanimated 4) - react-native-gesture-handler `~2.31.1` - @gorhom/bottom-sheet `^5.2.14` - react-native-safe-area-context `~5.7.0` - react-native-screens `4.25.2` - expo-router `~56.2.10` - @expo/vector-icons `^15.0.2` - typescript `~6.0.3` (dev dependency) Expo Go or development build: Mini-Player Sheet runs in Expo Go. Its runtime dependencies are Reanimated, Worklets, Gesture Handler, Safe Area Context, Screens, Expo Router and @expo/vector-icons, all of which are bundled in the Expo Go client for SDK 56. There is no @shopify/react-native-skia, no camera and no custom native module, so no development build is required, though a development build works exactly the same. ## Install ```bash npx expo install @gorhom/bottom-sheet react-native-reanimated react-native-worklets react-native-gesture-handler react-native-safe-area-context @expo/vector-icons ``` ## Usage Mini-Player Sheet is a screen, not a props-driven component: you drop `app/index.tsx` into your Expo Router app and mount the three providers it needs at the root. This is the drop's `app/_layout.tsx` reduced to those providers; the shipped file also wires an expo-router `ThemeProvider` off `useColorScheme` and an expo-status-bar `StatusBar` inside them. ```tsx import { Stack } from 'expo-router'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { BottomSheetModalProvider } from '@gorhom/bottom-sheet'; import 'react-native-reanimated'; export default function RootLayout() { return ( ); } ``` The sheet presents itself on mount as the docked mini-player; tapping the bar calls `snapToIndex(2)` and expands it, and you can call the same method on your own `BottomSheetModal` ref from anywhere else in the screen. ## Customization Mini-Player Sheet takes no props; you tune it by editing the constants and the `BottomSheetModal` props in `app/index.tsx`. - `PADDING_BOTTOM` (number, `160`): the collapsed snap point in points, used twice in `snapPoints = [PADDING_BOTTOM, PADDING_BOTTOM, '100%']`, so the docked bar clears the tab bar. - `ANIMATION_CONFIGS_IOS` (Reanimated spring config): `damping: 500`, `stiffness: 1000`, `mass: 3`, `overshootClamping: true`, `restDisplacementThreshold: 10`, `restSpeedThreshold: 10`. It is passed both to the sheet's `animationConfigs` and to every `withSpring` call, so changing it retimes the whole morph at once. - Artwork size: the `interpolate(bottomSheetIndex.get(), [1, 2], [40, 240])` pair in `animatedImage` sets the collapsed and expanded artwork edge. - Expanded artwork position: `width / 2 - (240 / 2)` and `inset.top + 60` in the `onAnimate` handler; the collapsed resting position is `(8, 8)`. - Colors: `interpolateColor(..., ['#FFFFFF', '#7C7D80'])` for the sheet background, and `#FA233B` for the active tab tint. - Card look: the `1.05` index threshold that switches `borderRadius` between `12` and `0` and drops the `boxShadow`, plus the `[1, 2] -> [12, 0]` `marginHorizontal` interpolation. - Cross-fade speed: `withTiming(x, { duration: 100 })` on `smallOpacity` and `largeOpacity`. - Sheet behaviour props: `detached`, `enableOverDrag={false}`, `enableDynamicSizing={false}`, `enablePanDownToClose={false}`, `enableContentPanningGesture={canPan}` and `handleStyle={{ display: 'none' }}`. ## When to use it - A music or podcast app that needs an Apple Music style now-playing screen reachable from a persistent mini-player above the tab bar. - A video or audiobook player where a docked bar must expand into full transport controls without navigating away from the current list. - A live-session or call app that keeps an ongoing activity docked at the bottom and expands it to the full control surface on tap. - A navigation or delivery-tracking app with a compact trip bar that opens into a full detail sheet. - Any screen where you want a persistent floating card that morphs into a full-screen view instead of pushing a new route. - A reference implementation for driving arbitrary Reanimated styles from @gorhom/bottom-sheet's `animatedIndex`. ## How to get it - Download Mini-Player Sheet free at https://motionary.dev/animations/mini-player-sheet, no payment, no subscription. - 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 an Apple Music style expandable mini player in React Native?** Render a detached `BottomSheetModal` from @gorhom/bottom-sheet with snap points like `[160, 160, '100%']`, pass a Reanimated shared value into its `animatedIndex`, and interpolate the sheet height, background color and album art size off that one value inside `useAnimatedStyle`. Motionary's Mini-Player Sheet drop (https://motionary.dev/animations/mini-player-sheet) is a free, complete implementation of exactly that, in TypeScript. **Does the Mini-Player Sheet work with Expo Go?** Yes. Mini-Player Sheet from Motionary uses only Reanimated 4, Worklets, Gesture Handler, Safe Area Context, Expo Router and @expo/vector-icons, all bundled in Expo Go for Expo SDK 56, plus @gorhom/bottom-sheet, which is pure JavaScript, so it needs no development build and no native code. **Is Mini-Player Sheet free?** Yes, Mini-Player Sheet is a free drop on Motionary: download the full TypeScript source at https://motionary.dev/animations/mini-player-sheet with no payment and no subscription. The Motionary lifetime pass ($79 USD as of 2026-09-01) is only needed for the paid drops and builds. **Does the Mini-Player Sheet actually play audio?** No. Mini-Player Sheet from Motionary is the UI and the animation: the progress bar, volume bar and transport buttons are static, and there is no expo-audio or expo-av dependency, so you connect your own playback layer to the play, forward and back controls. **Does it work on Android as well as iOS?** Yes. Mini-Player Sheet from Motionary is styled for both platforms and every animation runs through react-native-reanimated and @gorhom/bottom-sheet, which are cross-platform; the spring config is named `ANIMATION_CONFIGS_IOS` because it is tuned for an iOS-accurate feel, not because it is iOS-only. **Why does dragging the collapsed mini-player not move the sheet?** That is deliberate in Motionary's Mini-Player Sheet: `canPan` state feeds `enableContentPanningGesture`, which is `false` until the sheet reaches the full snap point, so the docked bar stays tappable (a `Pressable` calling `snapToIndex(2)`) and only the expanded sheet can be dragged down. **Can I use bottom sheet animatedIndex to drive other animations?** Yes, and that is the core technique in Motionary's Mini-Player Sheet: passing your own `useSharedValue` into `animatedIndex` on a @gorhom/bottom-sheet `BottomSheetModal` gives you a live UI-thread progress value that any `useAnimatedStyle`, `interpolate` or `interpolateColor` call can read. ## Related drops on Motionary - Karaoke Lyrics ($12): https://motionary.dev/animations/karaoke-lyrics Apple Music style scrubbable lyrics, the natural content for the expanded now-playing view Mini-Player Sheet opens. - Attachment Sheet ($12): https://motionary.dev/animations/attachment-sheet Another Reanimated bottom sheet, modelled on Telegram's attachment tray, for when you need a sheet that morphs its contents rather than a media player. - Island Genie ($11): https://motionary.dev/animations/island-genie A bottom sheet that expands out of the Dynamic Island, a different entry point to the same "small dock expands into a full surface" pattern. - Glass Video Player (Free): https://motionary.dev/animations/glass-video-player Free draggable media controls, the video counterpart to Mini-Player Sheet's audio transport. - Mail Tab Bar (Free): https://motionary.dev/animations/mail-tab-bar A free animated tab bar to sit under the docked mini-player. ## 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/mini-player-sheet/llms.txt Describes: https://motionary.dev/animations/mini-player-sheet Format: llms.txt (https://llmstxt.org) Last updated: 2026-09-01