About this animation
A habit calendar with stretching day selection and a staggered agenda, with configurable days, month label, accent colors, and fonts.
Why this one
Most week pickers slide a fixed capsule and swap the content below it. This version gives the selection physical intent: its leading edge arrives first, its trailing edge catches up, and the white date is revealed by the moving capsule instead of cross-fading. Week changes add a second depth cue with separate parallax for dates and completion dots while preserving the selected weekday.
How it works
- One floating-point
dayProgressshared value drives day selection, week dragging, and the release spring on the UI thread. Taps and arrow controls schedule the same worklets used by the gesture. - Capsule translation and stretch derive from that progress and its destination. Its width stays fixed: transforms avoid layout work on every animation frame.
- A counter-scaled row of white dates sits inside the clipped gradient capsule, keeping the numbers undistorted during the stretch and week movement.
- Dates, completion dots, and agenda parallax derive from the same progress. Interrupted snaps continue from their current position; the UI thread completes the week transition without waiting for React.
- React updates the agenda and
onDayChangeonly when the destination settles, skipping intermediate days. A short directional stagger and spring-driven Skia ring complete the handoff; the outgoing agenda unmounts when the handoff finishes.
Install
npx expo install @shopify/react-native-skia expo-image expo-linear-gradient react-native-gesture-handler react-native-reanimated react-native-workletsThe app root must be wrapped in GestureHandlerRootView, and Reanimated must be configured for the installed Expo SDK.
Icons: Lucide (ISC), rendered as inline SVG images through expo-image. Only the progress rings use Skia canvases (at most two during an agenda handoff), so changing days does not exhaust browser WebGL contexts. Week arrows keep a 56-point clear area at the top right for the catalog viewer controls.
Usage
import { FluidWeekStrip } from './fluid-week-strip';
export function HabitsScreen() {
return <FluidWeekStrip />;
}With brand and behavior knobs:
import { useRef } from 'react';
import {
FluidWeekStrip,
type FluidWeekStripRef,
} from './fluid-week-strip';
export function HabitsScreen() {
const ref = useRef<FluidWeekStripRef>(null);
return (
<FluidWeekStrip
ref={ref}
brandName="Tidewell"
monthLabel="October 2026"
accentColors={['#2DD4BF', '#0F766E']}
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
days | FluidWeekDay[] | DEFAULT_DAYS | Consecutive full weeks of agenda data. |
initialDayIndex | number | 1 | Initially selected absolute day index. |
monthLabel | string | 'October 2026' | Month heading. |
brandName | string | 'Wellness' | Brand wordmark. |
accentColors | readonly [string, string] | ['#2DD4BF', '#0F766E'] | Brand tile and selection gradient. |
fontFamily | string | undefined | Interface font family. |
mediumFontFamily | string | undefined | Medium interface font family. |
titleFontFamily | string | undefined | Editorial heading and wordmark font family. |
monoFontFamily | string | undefined | Numeric font family for changing values. |
onDayChange | (index: number, day: FluidWeekDay) => void | undefined | Called once the selected destination settles; interrupted intermediate days are skipped. |
onHabitPress | (habit: FluidWeekHabit, day: FluidWeekDay) => void | undefined | Called when a habit card is pressed. |
accessibilityLabel | string | 'Habit calendar' | Screen-reader label for the week control. |
style | StyleProp<ViewStyle> | undefined | Optional style for the outer container. |
testID | string | undefined | Test identifier for the outer container. |
The ref exposes goToDay(weekday), nextWeek(), and previousWeek().
Compatibility
- Expo SDK 58
- React Native 0.88
- Reanimated 4.7 and Worklets 0.13
- Skia 2.14
- iOS: supported
- Android: supported
- Web: supported
- Expo Go: supported because every native dependency is bundled with this Expo SDK
Accessibility
Every day, arrow, and habit has a role and descriptive label. The week strip also supports adjustable increment and decrement actions, while the arrows provide a touch alternative to swiping. Reduced Motion removes capsule stretch and agenda parallax, uses a short selection transition, and changes agenda movement to a fade.
Customization notes
Movement follows gestures, scroll position, or interruptible spring physics. A global duration is not exposed; reduced-motion fades retain their short fixed timing.




