Skip to content
Native Animation
Synced Side Tabs animation preview

Synced Side Tabs

A virtualized section list with compact vertical tabs that track scrolling through one continuous, tactile indicator.

$9 for this animation

Checkout opens soon. Leave your email and we will tell you the moment you can buy it.

One email when checkout opens. No spam.

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.

npx nativeanimation add synced-side-tabs

About this animation

A virtualized section list with compact vertical tabs that track scrolling through one continuous, tactile indicator.

How it works

  1. The component flattens section headers and items into one Animated.FlatList, with deterministic row geometry for stable rendering.
  2. Every section header reports its actual content position with onLayout; tab presses wait for and use that measured offset rather than an estimate.
  3. One continuous shared value drives the indicator position, its elastic in-between stretch, and each tab label's color, opacity, and emphasis.
  4. Pressing a tab selects it immediately, moves the indicator with a spring, and locks scroll synchronization until the programmatic scroll ends. Reaching the bottom explicitly selects the final section even when it cannot align to the top.
  5. Viewability tracking records each item after its first reveal, so virtualized cells only receive their staggered entrance once.

Why this one

Typical side tabs snap their highlight after a section boundary or let scroll events override the tab a person just pressed. This version targets measured section positions, holds the requested selection throughout programmatic motion, treats the bottom edge correctly, gives every tab at least a 44-point hit area, and keeps its spring indicator continuously connected to manual scrolling.

Install

npx expo install react-native-reanimated react-native-worklets

Usage

import SyncedSideTabs from './SyncedSideTabs';

export function Library() {
  return (
    <SyncedSideTabs
      sections={genres}
      itemHeight={72}
      keyExtractor={(track) => track.id}
      renderItem={(track) => <TrackRow track={track} />}
    />
  );
}

Customize the rail and control it externally:

import { useRef } from 'react';
import SyncedSideTabs, { type SyncedSideTabsRef } from './SyncedSideTabs';

export function Library() {
  const tabs = useRef<SyncedSideTabsRef>(null);
  return (
    <SyncedSideTabs
      ref={tabs}
      sections={genres}
      itemHeight={88}
      tabWidth={50}
      accentColor="#6E56FF"
      mutedColor="#9C9DAE"
      fontFamily="Geist_500Medium"
      keyExtractor={(track) => track.id}
      renderItem={(track, index, section) => <TrackRow track={track} />}
      onSectionChange={(index) => analytics.sectionViewed(index)}
    />
  );
}

The forwarded ref exposes scrollToSection(index).

Props

PropTypeDefaultDescription
sectionsSideTabSection<T>[]RequiredOrdered section titles and item arrays.
renderItem(item, index, section) => ReactNodeRequiredRenders an item with local and section indices.
keyExtractor(item) => stringRequiredReturns a stable item key.
itemHeightnumberRequiredFixed item height in points, excluding the built-in 14-point gap.
headerReactNode—Optional content above tabs and list.
tabWidthnumber44Vertical tab rail width in points.
accentColorstring'#111111'Active labels and indicator.
mutedColorstring'#B9BCC4'Inactive labels.
fontFamilystringSystem fontSection and tab label font.
onSectionChange(index) => void—Called at a visible-section boundary.
styleStyleProp<ViewStyle>—Outer-container style.
testIDstring—Test identifier for the outer container.

Compatibility

  • Expo SDK 57
  • React Native 0.86
  • Reanimated 4.5 and Worklets 0.10
  • iOS: supported, not yet verified on a device
  • Android: supported, not yet verified on a device
  • Web: supported, verified by the project export
  • Works in Expo Go because Reanimated and Worklets are bundled native modules

Accessibility

The rail exposes a tablist, and each labeled tab announces its selected state and remains a touch alternative to scrolling. Reduced Motion preserves direct section changes, replaces item displacement with a short fade, and removes indicator stretch and animated list scrolling.