Skip to content
Native Animation
Brutalist Music Player animation preview

This preview uses fictional demo content. See the props below for the customization options available in this component.

npx nativeanimation add brutalist-music-player

Brutalist Music Player

A music browser with cover-to-player transitions and a draggable waveform, with configurable tracks, labels, colors, and fonts.

$9 for this animation

Add to your project

Copy the component and its assets into your app with the Native Animation CLI, or paste the agent prompt into Claude Code, Codex or Cursor once the MCP server is connected.

About this animation

A music browser with cover-to-player transitions and a draggable waveform, with configurable tracks, labels, colors, and fonts.

Why this one

The shadow moves with the cover between discovery and playback. Every button sinks toward its shadow instead of shrinking, so the visual language carries through the entire interaction.

How it works

  1. The component measures its container with onLayout, limits the content width on larger screens, and sizes the cover against both available axes.
  2. A shared screen spring interpolates the selected thumbnail's position, size, corner radius, and shadow displacement into the player cover. Back reverses that spring.
  3. Independent pressure values translate buttons toward fixed shadows. The cover's opening press is also available through openTrack.
  4. One normalized progress value drives clipped waveform fills and timestamps. A UI-thread clock advances it while playing; a pan gesture writes directly to the same value.
  5. Track changes send the cover sideways, replace its content, then spring the new cover into place. Waveform heights stagger into the new shape.
  6. Reduced motion replaces the screen transition with a fade and disables cover flights and waveform bounce.

Install

npx expo install expo-image react-native-gesture-handler react-native-reanimated react-native-worklets

Wrap the host application in GestureHandlerRootView. Configure Reanimated and Worklets for your Expo SDK. No Skia or web canvas setup is required. Icons: Lucide (ISC); paths are included locally in icons.ts.

Usage

import { BrutalistMusicPlayer } from './brutalist-music-player';

<BrutalistMusicPlayer
  tracks={[
    {
      id: 'first',
      title: 'First Light',
      artist: 'Your Artist',
      cover: require('./cover.webp'),
      duration: 280,
    },
  ]}
  artists={[{ id: 'artist', name: 'Your Artist', photo: require('./artist.webp') }]}
  onPlay={(playing) => audioEngine.setPlaying(playing)}
/>

Supply fontFamily, titleFontFamily, and monoFontFamily after loading your fonts. The demo uses Geist and Geist Mono, loaded by the catalog host.

<BrutalistMusicPlayer
  ref={playerRef}
  tracks={tracks}
  colors={{ paper: '#1A1A1A', ink: '#FFFFFF', muted: '#555555' }}
  shadowOffset={{ x: 4, y: 5 }}
  labels={{ discover: 'Explore', artistsTitle: 'Featured Artists' }}
  onSearch={openSearch}
  onTabChange={navigateToTab}
/>
// playerRef.current?.openTrack('first');
// playerRef.current?.seek(0.7);

Props

PropTypeDefaultPurpose
tracksMusicTrack[]RequiredIDs, titles, artists, local covers, durations in seconds, optional 46 normalized waveform heights
artistsMusicArtist[][]Featured portraits; the first three appear on Discover
labelsPartial<MusicLabels>English UI labelsVisible copy and accessibility labels
colors{ ink?, paper?, muted? }#1A1A1A, #FFFFFF, #CFCFCFFull palette
shadowOffset{ x, y }{ x: 5, y: 6 }Small-card hard shadow; player cover grows to 9/11
fontFamilystringSystemBody typeface
titleFontFamilystringSystemBold typeface
monoFontFamilystringSystemChanging timestamps
onPlay(playing: boolean) => void—Audio-engine integration
onTrackChange(track: MusicTrack) => void—Selected-track notification
onSearch() => void—Host search navigation
onTabChange(index: number) => void—Host navigation; tab selection is local
onInteraction() => void—Stop host autoplay on touch
styleStyleProp<ViewStyle>—Container styling
testIDstringbrutalist-music-playerControl ID prefix

Ref

openTrack(id), back(), togglePlay(), next(), previous(), and seek(p) provide the same controls used by touch. Seeking clamps to 0–1. Unknown track IDs are ignored. Artists open the track at the corresponding list position.

Demo

Monotone is an invented music app with five invented artists and local generated portraits. Autoplay opens the second row at 1.2 seconds, advances at 3.4 seconds, seeks at 5 seconds, pauses at 5.5 seconds, and returns at 7 seconds in a 9-second loop. Touch stops autoplay.

Compatibility

Targets Expo SDK 58, React Native 0.88.0, and Reanimated 4.7.1 on iOS, Android, and web. Install the SDK-compatible dependencies above for Expo Go. Audio playback requires a host-provided engine.

Accessibility

Buttons expose roles, labels, and accessibility activation. The waveform supports direct touch and screen-reader increment/decrement actions. Reduced motion removes spatial screen and track travel, press displacement, and waveform playback motion while retaining short fades. The host owns safe-area padding. The component is intended for a bounded screen container and supports compact layouts with a width cap in landscape.

Limitations

This is a visual player and playback clock; it does not load or play audio. Connect an audio engine through callbacks. Search and destination tabs belong to the host. Shuffle and repeat are local playback modes. No artwork or fonts are bundled in the installable component. The demo assets stay in demo/assets.

The supplied demo portraits are 600 × 600 pixels. The cover can reach 330 points, so these demo assets do not meet the catalog’s 3× image-density target. Supply artwork of at least 990 × 990 pixels for that maximum size; resizing the current portraits would not add detail.