About this animation
A tactile statistics card with growing bars, an odometer-style total, and drag-to-explore monthly details.
Why this one
Most compact bar charts make every change feel like a page refresh. This one lets a finger scrub directly across the data, keeps a fine guide aligned with the active month, and rolls only the number characters that change. Bars also replay their staggered growth whenever the data prop changes, so live dashboards retain a polished entrance without extra orchestration.
How it works
- The component measures its own container and derives card, chart, bar, and type sizes from that space.
- A zero-distance pan gesture maps the finger's horizontal position to a bar, so a tap and a drag use the same interaction path.
- One shared selection value drives the active fills and glow, while a spring keeps the vertical guide fluid and interruptible.
- Each bar grows from its bottom edge with a 40 ms stagger and restarts when its normalized value changes.
- The selected value is formatted with
Intl.NumberFormat('en-US'); each character commits with its animation reset inuseLayoutEffectto avoid stale frames.
Install
npx expo install react-native-gesture-handler react-native-reanimated react-native-workletsUsage
import CountingBarChart from './CountingBarChart';
const data = [
{ label: 'January', value: 42480 },
{ label: 'February', value: 51620 },
{ label: 'March', value: 47890 },
{ label: 'April', value: 63840 },
{ label: 'May', value: 71240 },
{ label: 'June', value: 77318 },
];
export function AnalyticsScreen() {
return <CountingBarChart data={data} />;
}Add metrics, brand colors, fonts, and external control:
import { useRef } from 'react';
import CountingBarChart, { type CountingBarChartRef } from './CountingBarChart';
export function DetailedAnalytics() {
const chartRef = useRef<CountingBarChartRef>(null);
return (
<CountingBarChart
ref={chartRef}
data={monthlyData}
caption="Growth overview"
barColor="#376DE5"
mutedColor="#DFE7FA"
positiveColor="#188461"
negativeColor="#C94E4E"
fontFamily="YourSansFont"
numberFontFamily="YourNumberFont"
title={(datum) => `${datum.value.toLocaleString('en-US')} total`}
onSelect={(index) => trackMonth(index)}
/>
);
}The forwarded ref exposes select(index).
Props
| Prop | Type | Default | Description |
|---|---|---|---|
data | BarDatum[] | Required | Bar values and optional metrics for each period. |
initialIndex | number | 0 | Initially selected bar. |
title | (datum: BarDatum) => string | en-US grouped value | Formats the large title. |
caption | string | 'Last 6 months' | Supporting label below the title. |
barColor | string | '#2E9E7B' | Active bar, guide, and glow color. |
mutedColor | string | '#D7EEE6' | Inactive bar color. |
positiveColor | string | '#21845F' | Positive change color. |
negativeColor | string | '#D45B54' | Negative change color. |
fontFamily | string | System font | Labels and metric changes. |
numberFontFamily | string | System font | Large total and metric values. |
onSelect | (index: number) => void | — | Called after selection changes. |
style | StyleProp<ViewStyle> | — | Outer-container style. |
testID | string | — | 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, not yet verified
- Works in Expo Go because Gesture Handler, Reanimated, and Worklets are included in the SDK
Accessibility
The chart is exposed as an adjustable control. Screen readers hear the selected period and value, such as “June: 77,318 visitors,” and can move month by month with increment and decrement actions. Reduced Motion replaces rolling and spring movement with short fades and keeps bars fully visible.