About this animation
This React Native date picker selects a booking range across supplied month grids. It is a custom calendar with a Skia ribbon, draggable endpoints, date-tap alternatives, nightly prices, and a rolling total. Supply consecutive month metadata, rates, and the first selectable day index; onRangeChange reports the confirmed selection, while onReserve reports the complete range and total. Dates are zero-based indices across your months, with departure excluded from the nightly charge. Your app maps indices to actual dates and handles timezones, availability, and booking persistence. This is a focused range interaction rather than the operating system date or time selector.
When to use it
- Select check-in and check-out dates.
- Show nightly rates while choosing a stay.
- Connect a confirmed range to a booking flow.
A travel date-range picker with elastic selection and configurable months, nightly prices, accent color, and fonts.
Why this one
Most calendars fill a set of cells immediately. This one stretches a single Skia ribbon across week boundaries, snaps its draggable endpoints to dates, and rolls the booking total alongside the range. Nightly rates reveal lower-priced dates before a guest commits.
How it works
- Measure the container with
onLayout; seven equal columns have 44-point hit targets at a 320-point width. - Lay out consecutive month grids inside a vertical scroll view. Each row contributes one capsule to a single canvas behind the day numbers.
- A shared range object drives both endpoints, the ribbon geometry, night count, and rolling total on the UI thread.
- Compose endpoint pans and date taps with
Gesture.Exclusive. Dragging favors the nearest date; release springs to it with inherited velocity. Starting a new selection replaces the old range. - Reveal row segments with 30 ms staggering. Nearly critical springs (damping 27, stiffness 240, mass 1) clamp overshoot on the ribbon, endpoints, and press feedback; custom damping is bounded to a ratio of at least 0.85. Press and drag feedback compresses to 0.97 and returns to 1. Digit columns roll only at their carry boundary and fade away from their resting position.
- Report confirmed selections through
onRangeChange. Autoplay belongs to the demo and uses the imperative ref.
Install
npx expo install @shopify/react-native-skia react-native-gesture-handler react-native-reanimated react-native-workletsPlace the picker under GestureHandlerRootView. Configure Reanimated/worklets for your host project. On web, load CanvasKit before importing this component, using Skia's WithSkiaWeb or an equivalent lazy boundary. The demo additionally uses expo-image and expo-linear-gradient and assumes the host has loaded its Geist and Instrument Serif fonts.
Usage
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { DateRangePicker } from './date-range-picker';
export function Booking() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<DateRangePicker onReserve={(range, total) => saveBooking(range, total)} />
</GestureHandlerRootView>
);
}<DateRangePicker
months={[
{ label: 'November 2026', name: 'November', days: 30, firstWeekday: 0 },
{ label: 'December 2026', name: 'December', days: 31, firstWeekday: 2 },
]}
prices={nightlyRates}
minimumDay={9}
accentColor="#1E3A8A"
bandColor="rgba(14,165,233,0.14)"
springDamping={27}
springStiffness={240}
colorScheme="light"
/>Indices are zero-based across all supplied months. November 12 is index 11. The departure day is excluded from the nightly total. prices defaults to $420 per night for missing entries; provide your own rates for production. Set minimumDay from the booking system's current date. No timezone conversion occurs inside the component.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| months | CalendarMonth[] | Nov–Dec 2026 | Consecutive month labels and grid metadata. |
| prices | number[] | [] | Nightly rates, with $420 fallback. |
| minimumDay | number | 0 | First selectable index. |
| title | string | Select dates | Sheet heading. |
| reserveLabel | string | Reserve | Primary action text. |
| accentColor | string | #1E3A8A | Endpoint and button color. |
| bandColor | string | Sky blue at 14% | Ribbon fill. |
| springDamping | number | 27 | Damping, bounded to a ratio of at least 0.85. |
| springStiffness | number | 240 | Elastic stiffness. |
| fontFamily | string | System | Interface font. |
| titleFontFamily | string | System | Header font. |
| monoFontFamily | string | System | Rates and total font. |
| colorScheme | light | dark | auto | light | Surface theme. |
| onRangeChange | (DateRange | null) => void | — | Selection committed or cleared. |
| onReserve | (DateRange, number) => void | — | Complete booking range and total. |
| style | StyleProp<ViewStyle> | — | Root container style. |
| testID | string | — | Root test identifier. |
Ref methods: selectDay(index), setRange(start, end), and clear(). Four digits are displayed for the booking total; use rates and stays totaling less than $10,000. The component is designed for containers at least 320 points wide.
Compatibility
Built for Expo SDK 58, React Native 0.88.0, Reanimated 4.7.1, worklets 0.13.0, Gesture Handler 3.3, and Skia 2.14.0. Uses the installed Expo Go-compatible stack. Supports iOS, Android, and web. No additional native dependency is required.
Accessibility
Each date announces its month, day, nightly rate, disabled status, and selected endpoint. Every gesture has a date-tap alternative and an accessibility activation action. Past dates are disabled. Clear and Reserve expose button roles; Reserve requires a complete range. A polite summary announces confirmed nights and total. Reduced motion replaces ribbon growth with a 180 ms fade, updates digits directly, and removes endpoint and button bounce.
Icons: Lucide (ISC), copied from the repository kit. The Northwind demo uses the kit's compass tile and gradient. The photo is bundled locally.
FAQ
- Is this a native date picker or a date range calendar?
- It is a custom date range calendar built with Skia, Gesture Handler, and Reanimated. It does not open a native date or time dialog. Choose a system picker when your task is a single date or time input.
- How do dates, prices, and booking totals work?
- Supply consecutive months and zero-based nightly prices. minimumDay sets the earliest selectable index; missing prices default to $420 per night, so replace them for production. Departure is excluded from the total. No timezone conversion is included. The total displays four digits; keep stays below $10,000.
- Can people select a range without dragging?
- Yes. Date taps and accessibility activation offer alternatives to endpoint pans. Dates announce their month, day, rate, and selection. Reserve requires a complete range. Reduced Motion uses a short fade and direct digit updates. Use a container at least 320 points wide.




