Lightweight, spinner-style picker for React Native and Expo. Supports date and date-time modes, custom formatting, min/max ranges, configurable column order, gradients, and custom separators.
Full Javascript, no linking required
Based on (and grateful to) react-native-timer-picker.
- 📱 Demos
- 🛠️ Installation
- 🚀 Quick start (date)
- 🕑 Date-time mode (date + hour + minute) and gradient overlay
- 🔄 Month/day/year swap with formatted labels
- 🎯 Example with selected line and non-infinite scrolling
- 🧪 Extended example (datetime with spacing, separator, gradient mask)
- 📑 Props
- 🎨 Styling
- 🧭 Behavior notes
- 🤝 Contributing / scripts
- ⚖️ License
React Native >= 0.72, React >= 18.2.
yarn add react-native-date-time-spinner
# or
npm install react-native-date-time-spinnerimport React, { useState } from "react";
import { View, Text } from "react-native";
import { DateTimeSpinner } from "react-native-date-time-spinner";
export default function Example() {
const [value, setValue] = useState(new Date(2025, 5, 15));
return (
<View style={{ padding: 16 }}>
<Text>{value.toDateString()}</Text>
<DateTimeSpinner
initialValue={value}
minDate={new Date(2020, 0, 1)}
maxDate={new Date(2030, 11, 31)}
onDateChange={({ date }) => setValue(date)}
padDayWithZero
padMonthWithZero
/>
</View>
);
}import React, { useState } from "react";
import { View, Text } from "react-native";
import { DateTimeSpinner } from "react-native-date-time-spinner";
import { format } from "date-fns";
export default function Example() {
const [value, setValue] = useState(new Date(2025, 5, 15));
return (
<View style={{ padding: 16 }}>
<Text>{value.toDateString()}</Text>
<DateTimeSpinner
mode="datetime"
dateTimeOrder={["date", "hour", "minute"]}
dateTimeSpacing={16}
formatDateLabel={(date) => format(date, "eee, MMM d")}
initialValue={new Date(2024, 7, 19, 7, 40)}
minDate={new Date(2020, 0, 1)}
maxDate={new Date(2030, 11, 31)}
padHourWithZero
padMinuteWithZero
LinearGradient={LinearGradient}
pickerGradientOverlayProps={{ locations: [0, 0.5, 0.5, 1] }}
timeSeparator="·"
onDateChange={({ date }) => setValue(date)}
/>
</View>
);
}import { format } from "date-fns";
import { LinearGradient } from "expo-linear-gradient";
<DateTimeSpinner
columnOrder={["year", "month", "day"]}
formatDateToParts={(date) => ({
day: format(date, "do"),
month: format(date, "MMM"),
year: format(date, "''yy"),
})}
initialValue={new Date(2024, 7, 19)}
onDateChange={({ date }) => setValue(date)}
/>;import React, { useState } from "react";
import { format } from "date-fns";
import { LinearGradient } from "expo-linear-gradient";
import { View, Text } from "react-native";
import { DateTimeSpinner } from "react-native-date-time-spinner";
export default function Example() {
const [value, setValue] = useState(new Date(2025, 5, 15));
return (
<View style={{ padding: 20 }}>
<Text style={{ marginBottom: 8 }}>{format(value, "PPpp")}</Text>
<View
style={{
alignItems: "center",
justifyContent: "center",
}}>
<DateTimeSpinner
disableInfiniteScroll={true}
formatDateToParts={(date) => ({
day: format(date, "dd"),
month: format(date, "MMMM"),
year: format(date, "yyyy"),
})}
initialValue={value}
LinearGradient={LinearGradient}
onDateChange={({ date }) => setValue(date)}
pickerGradientOverlayProps={{
locations: [0, 0.5, 0.5, 1],
}}
repeatNumbersNTimes={1}
/>
<View
accessible={false}
style={{
borderColor: "#1F5E73",
borderRadius: 12,
borderWidth: 2,
height: 40,
pointerEvents: "none",
position: "absolute",
width: 300,
}}></View>
</View>
</View>
);
}import React, { useState } from "react";
import { View, Text } from "react-native";
import { DateTimeSpinner } from "react-native-date-time-spinner";
import { format } from "date-fns";
import { LinearGradient } from "expo-linear-gradient";
export default function Example() {
const [value, setValue] = useState(new Date(2025, 5, 15));
return (
<View style={{ padding: 20 }}>
<Text style={{ marginBottom: 8 }}>{format(value, "PPpp")}</Text>
<DateTimeSpinner
mode="datetime"
dateTimeOrder={["date", "hour", "minute"]}
dateTimeSpacing={16}
timeSeparator="·"
padHourWithZero
padMinuteWithZero
LinearGradient={LinearGradient}
pickerGradientOverlayProps={{
colors: [
"#232323",
"rgba(255,255,255,0)",
"rgba(255,255,255,0)",
"#232323",
],
locations: [0, 0.4, 0.6, 1],
}}
formatDateLabel={(date) => format(date, "eee, MMM d")}
minDate={new Date(2020, 0, 1)}
maxDate={new Date(2030, 11, 31)}
initialValue={value}
onDateChange={({ date }) => setValue(date)}
styles={{
theme: "dark",
pickerItem: { fontSize: 20 },
timeSeparatorText: { fontWeight: "700" },
dateTimeSpacer: { width: 20 },
}}
/>
</View>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
mode |
"date" | "datetime" |
"date" |
Select date-only or date + time columns. |
initialValue |
Date or partial { day, month, year, hour, minute } |
today (clamped) | Starting value. Clamped to minDate/maxDate. |
minDate |
Date |
current year - 50 Jan 1 | Lower bound (inclusive). |
maxDate |
Date |
current year + 50 Dec 31 | Upper bound (inclusive). |
onDateChange |
(value) => void |
undefined |
Called with { date, day, month, year, hour, minute } when selection changes. |
columnOrder |
Array<"day"|"month"|"year"> |
["day","month","year"] |
Column order in date mode. |
dateTimeOrder |
Array<"date"| "hour"| "minute"> |
["date","hour","minute"] |
Column order in datetime mode. |
dateTimeSpacing |
number |
12 |
Horizontal space between date and time columns (px) in datetime mode. |
timeSeparator |
string |
":" |
Separator text between hour and minute when both are present. |
formatDateToParts |
(date) => { day?: string; month?: string; year?: string } |
undefined |
Per-column formatter for date parts. |
formatDateLabel |
(date) => string |
undefined |
Formatter for the date column label in datetime mode. |
padDayWithZero |
boolean |
true |
Left-pad day values to 2 chars. |
padMonthWithZero |
boolean |
true |
Left-pad month values to 2 chars. |
padHourWithZero |
boolean |
true |
Left-pad hour values to 2 chars. |
padMinuteWithZero |
boolean |
true |
Left-pad minute values to 2 chars. |
padWithNItems |
number |
1 (clamped 0–10) |
Buffer items above/below the selected row. |
disableInfiniteScroll |
boolean |
false |
If true, render finite lists (no looping). |
repeatNumbersNTimes |
number |
3 (min 2 in infinite) |
How many times to repeat values to allow recentring in infinite mode. |
decelerationRate |
number | "normal" | "fast" |
0.88 |
Scroll deceleration rate passed to FlatList. |
allowFontScaling |
boolean |
false |
Pass through to Text for accessibility scaling. |
pickerFeedback |
() => Promise<void> |
undefined |
Called on scroll end (hook for haptics/sounds). |
pickerGradientOverlayProps |
Partial<LinearGradientProps> |
undefined |
Props forwarded to the gradient overlay. |
LinearGradient |
any |
undefined |
Gradient component (e.g., from expo-linear-gradient). |
MaskedView |
any |
undefined |
Mask component to apply gradient as a mask. |
styles |
CustomDateTimeSpinnerStyles |
undefined |
Style overrides (see below). |
pickerContainerProps |
View props |
undefined |
Extra props for outer container. |
accessibilityLabel |
string |
Auto-generated | Accessibility label for screen readers. Auto-generated based on mode. |
accessibilityLabels |
object |
English defaults | Custom labels for each column (see Accessibility section below). |
The date spinner is fully accessible and works with VoiceOver (iOS) and TalkBack (Android):
- Each column (day, month, year, hour, minute) is announced with clear labels
- Users can adjust values using the "Adjust Value" gesture (swipe up/down with VoiceOver)
- The container is announced as "Date picker" or "Date and time picker" based on mode
- You can provide a custom
accessibilityLabelprop to override the default announcement
Example with custom accessibility label:
<DateTimeSpinner
accessibilityLabel="Select your birthday"
initialValue={new Date(1990, 0, 1)}
onDateChange={({ date }) => console.log(date)}
/>Use accessibilityLabels to provide column labels in any language:
<DateTimeSpinner
mode="datetime"
accessibilityLabels={{
picker: "Sélecteur de date et heure",
date: "Date",
hour: "Heure",
minute: "Minute",
day: "Jour",
month: "Mois",
year: "Année",
hint: "Balayez vers le haut ou vers le bas pour ajuster",
}}
initialValue={new Date(2025, 5, 15, 14, 30)}
onDateChange={({ date }) => console.log(date)}
/>Available label keys:
| Key | Default (English) | Used in Mode |
|---|---|---|
picker |
"Date picker" or "Date and time picker" | All modes |
day |
"Day" | date |
month |
"Month" | date |
year |
"Year" | date |
date |
"Date" | datetime |
hour |
"Hour" | datetime |
minute |
"Minute" | datetime |
hint |
"Swipe up or down to adjust" | All modes (each column) |
minute |
"Minute" | datetime |
Provide styles to override typography, sizing, colors, and spacing. Defaults are derived from src/components/TimerPicker/styles.ts.
| Style key | Type | Default | Notes |
|---|---|---|---|
theme |
"light" | "dark" |
light |
Switches base colors. |
backgroundColor |
string |
#FFFFFF (light) / #232323 (dark) |
Picker container background. |
pickerContainer |
ViewStyle |
{ flexDirection: "row", justifyContent: "center" } |
Outer container for all columns. |
pickerItemContainer |
ViewStyle & { height?: number } |
height: 50, paddingHorizontal: 12, centered |
Row wrapper for each item. |
pickerItem |
TextStyle |
fontSize: 25, themed color |
Text style for each item. |
pickerLabelContainer |
ViewStyle |
Positioned right, min width auto | Holds optional label content. |
pickerLabel |
TextStyle |
fontSize: 18, bold, themed color |
Label text style. |
durationScrollFlatList |
ViewStyle |
minWidth: 1 |
FlatList style override. |
durationScrollFlatListContainer |
ViewStyle |
overflow: "visible" |
Wrapper around FlatList. |
durationScrollFlatListContentContainer |
ViewStyle |
{} |
Content container override. |
timeSeparatorContainer |
ViewStyle |
centered | Wrapper for the time separator. |
timeSeparatorText |
TextStyle |
fontSize: 24, themed color |
Separator text between hour and minute. |
dateTimeSpacer |
ViewStyle |
width: 12 |
Gap between date and time columns. |
pickerGradientOverlay |
ViewStyle |
full overlay | Used with LinearGradient/MaskedView. |
disabledPickerContainer |
ViewStyle |
opacity: 0.4 |
Applied when picker disabled. |
disabledPickerItem |
TextStyle |
opacity: 0.2 |
Applied to disabled items. |
text |
TextStyle |
n/a | Base text override applied to labels/items when provided. |
- Values are clamped to
minDate/maxDate; days respect month length. - In
datetimemode, the date column lists all days in range; hour/minute remain separate columns. - Inside a
ScrollView, enablenestedScrollEnabledon the parent to quiet nested list warnings.
yarn test– run Jestyarn lint– lintyarn build– build with bob
MIT




