Picker sheets
Every picker is also available inside a BottomSheet with a Cancel / Done header. They all share the same "chrome" props and the same draft behaviour.
Inline, bottom sheet or modal
Every picker can be shown three ways:
- Inline: the plain component (
WheelPicker,TimePicker,DatePicker,SelectList,EmojiPicker) renders in place. - Bottom sheet: slides up from the bottom and can be dragged down to dismiss.
- Modal: a centered dialog.
The sheets take presentation: 'auto' (default), 'bottomSheet' or 'modal'. auto is a modal on web windows wider than the sheet, where a bottom sheet would look cut off, and a bottom sheet on phones and native.
<TimePickerSheet presentation="modal" visible={open} onClose={close} value={time} onConfirm={setTime} />
SelectPicker also supports 'dropdown', which opens under the field. It's SelectPicker's default on the web.
import { DatePickerSheet, TimePickerSheet, WheelPickerSheet, EmojiPickerSheet, PickerSheet, useDraft } from '@elik56a/react-native-pickers';
Try the props
presentationmodedisplay<DatePickerSheet
visible={open}
onClose={() => setOpen(false)}
value={value}
onConfirm={setValue}
/>
How drafts work
- When the sheet opens, it copies
valueinto a draft. - The picker inside edits only the draft.
- Done calls
onConfirm(draft)and thenonClose(). - Cancel, a backdrop tap, a swipe down or Android back calls only
onClose(), and the draft is thrown away.
Your state changes only when the user confirms, which is how native iOS / Android pickers behave.
Shared props
All sheets accept these (SheetChromeProps):
| Prop | Type | Default | Description |
|---|---|---|---|
visible | boolean | required | Show / hide. |
onClose | () => void | required | Set visible to false here. |
title | ReactNode | — | Header title. |
confirmLabel | string | 'Done' | Confirm button text. |
cancelLabel | string | 'Cancel' | Cancel button text. |
showCancel | boolean | true (false for emoji) | Show the Cancel button. |
presentation | 'auto' | 'bottomSheet' | 'modal' | 'auto' | Bottom sheet or centered modal. auto: modal on wide web windows, bottom sheet elsewhere. |
sheetProps | Partial<BottomSheetProps> | — | Passed to the BottomSheet (height, bottomInset, styles, enableSwipeToClose…). |
theme | DeepPartial<PickerTheme> | — | Theme for the sheet and the picker. |
DatePickerSheet
A calendar (single, range, multiple) or date wheel in a sheet.
import { useState } from 'react'; import { Pressable, Text, View } from 'react-native'; import { DatePickerSheet, formatDate } from '@elik56a/react-native-pickers'; import type { DateRange } from '@elik56a/react-native-pickers'; function Field({ label, value, onPress }: { label: string; value: string; onPress: () => void }) { return ( <Pressable onPress={onPress} style={{ borderWidth: 1, borderColor: '#E8E4DC', borderRadius: 12, padding: 14 }}> <Text style={{ fontSize: 12, color: '#6E685E' }}>{label}</Text> <Text style={{ fontSize: 16, marginTop: 2 }}>{value}</Text> </Pressable> ); } export default function Example() { const [sheet, setSheet] = useState<'day' | 'range' | 'birthday' | null>(null); const [day, setDay] = useState<Date | null>(null); const [range, setRange] = useState<DateRange>({ start: null, end: null }); const [birthday, setBirthday] = useState(new Date(1995, 6, 12)); const close = () => setSheet(null); return ( <View style={{ gap: 10 }}> <Field label="Delivery date" value={day ? formatDate(day) : 'Choose…'} onPress={() => setSheet('day')} /> <Field label="Trip" value={range.start && range.end ? `${formatDate(range.start)} → ${formatDate(range.end)}` : 'Choose…'} onPress={() => setSheet('range')} /> <Field label="Birthday" value={formatDate(birthday)} onPress={() => setSheet('birthday')} /> <DatePickerSheet visible={sheet === 'day'} onClose={close} title="Delivery" value={day} onConfirm={setDay} minDate={new Date()} confirmOnSelect /> <DatePickerSheet visible={sheet === 'range'} onClose={close} title="Trip dates" mode="range" value={range} onConfirm={setRange} minDate={new Date()} /> <DatePickerSheet visible={sheet === 'birthday'} onClose={close} title="Birthday" display="wheel" value={birthday} onConfirm={setBirthday} maxDate={new Date()} /> </View> ); }
Behaviour
- Done is disabled until the selection is complete: a date in single mode, both
startandendin range mode. Multiple mode is always confirmable (including an empty array). confirmOnSelect(single + calendar only) hides Done and confirms as soon as a day is tapped.display="wheel"shows a DateWheelPicker (single mode). The draft starts atvalueor today.locale,minDateandmaxDateare forwarded; pass other wheel props viawheelProps.
Props
Shared props, plus every calendar prop (minDate, maxDate, isDateDisabled, markedDates, locale, firstDayOfWeek, styles, renderDay…), plus:
| Prop | Type | Default | Description |
|---|---|---|---|
mode | 'single' | 'range' | 'multiple' | 'single' | Selection mode. |
value | Date | null · DateRange · Date[] | — | Current committed value; copied into the draft on open. |
onConfirm | (date: Date) · (range: DateRange) · (dates: Date[]) => void | required | Called with the draft on Done. |
minRangeDays / maxRangeDays | number | — | Range mode. |
maxSelections | number | — | Multiple mode. |
display | 'calendar' | 'wheel' | 'calendar' | Wheel is single mode only. |
wheelProps | Partial<DateWheelPickerProps> | — | Extra props for the wheel. |
confirmOnSelect | boolean | false | Single calendar: confirm on tap, no Done button. |
TimePickerSheet
import { useState } from 'react'; import { Pressable, Text } from 'react-native'; import { TimePickerSheet } from '@elik56a/react-native-pickers'; export default function Example() { const [open, setOpen] = useState(false); const [time, setTime] = useState(new Date(2026, 0, 1, 7, 30)); return ( <> <Pressable onPress={() => setOpen(true)} style={{ alignItems: 'center', padding: 12 }}> <Text style={{ fontSize: 48, fontWeight: '200' }}>{time.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' })}</Text> <Text style={{ color: '#C8460D' }}>Change</Text> </Pressable> <TimePickerSheet visible={open} onClose={() => setOpen(false)} title="Wake up" value={time} onConfirm={setTime} minuteInterval={5} /> </> ); }
Props: shared props, every TimePicker prop except value/defaultValue/onChange, plus:
| Prop | Type | Default | Description |
|---|---|---|---|
value | Date | now | Copied into the draft on open. |
onConfirm | (date: Date) => void | required | Called with the draft on Done. |
WheelPickerSheet
import { useState } from 'react'; import { Pressable, Text } from 'react-native'; import { WheelPickerSheet } from '@elik56a/react-native-pickers'; const guests = Array.from({ length: 12 }, (_, i) => ({ label: `${i + 1} guest${i ? 's' : ''}`, value: i + 1 })); export default function Example() { const [open, setOpen] = useState(false); const [count, setCount] = useState(2); return ( <> <Pressable onPress={() => setOpen(true)} style={{ borderWidth: 1, borderColor: '#E8E4DC', borderRadius: 12, padding: 14 }}> <Text>👥 {guests[count - 1].label}</Text> </Pressable> <WheelPickerSheet visible={open} onClose={() => setOpen(false)} title="Guests" items={guests} value={count} onConfirm={setCount} /> </> ); }
Props: shared props, every WheelPicker prop except value/defaultValue/onChange, plus:
| Prop | Type | Default | Description |
|---|---|---|---|
value | T | first item | Copied into the draft on open. |
onConfirm | (value: T) => void | required | Called with the draft on Done. |
EmojiPickerSheet
No Done button: each pick calls onSelect, and the sheet closes unless closeOnSelect={false}.
import { useState } from 'react'; import { Pressable, Text, View } from 'react-native'; import { EmojiPickerSheet } from '@elik56a/react-native-pickers'; export default function Example() { const [open, setOpen] = useState(false); const [reactions, setReactions] = useState(['👍', '🎉']); return ( <View style={{ flexDirection: 'row', gap: 8, flexWrap: 'wrap' }}> {reactions.map((r, i) => ( <Text key={i} style={{ fontSize: 22, backgroundColor: '#F4F2EE', borderRadius: 999, paddingHorizontal: 10, paddingVertical: 4 }}>{r}</Text> ))} <Pressable onPress={() => setOpen(true)} style={{ borderRadius: 999, borderWidth: 1, borderColor: '#E8E4DC', paddingHorizontal: 12, justifyContent: 'center' }}> <Text>+</Text> </Pressable> <EmojiPickerSheet visible={open} onClose={() => setOpen(false)} title="React" onSelect={(e) => setReactions((r) => [...r, e])} /> </View> ); }
Props: shared props, every EmojiPicker prop, plus:
| Prop | Type | Default | Description |
|---|---|---|---|
closeOnSelect | boolean | true | Close after a pick. |
height | number | 420 | Picker height inside the sheet. |
showCancel | boolean | false | Show a Cancel button. |
PickerSheet
The building block: a BottomSheet with Cancel on the left and (when you pass onConfirm) Done on the right. Use it to build sheets for your own pickers.
import { useState } from 'react'; import { Pressable, Text, Switch, View } from 'react-native'; import { PickerSheet, useDraft } from '@elik56a/react-native-pickers'; type Prefs = { push: boolean; email: boolean }; export default function Example() { const [open, setOpen] = useState(false); const [prefs, setPrefs] = useState<Prefs>({ push: true, email: false }); const [draft, setDraft] = useDraft(prefs, open); return ( <> <Pressable onPress={() => setOpen(true)} style={{ padding: 14, borderRadius: 12, backgroundColor: '#F4F2EE' }}> <Text>Notifications: {[prefs.push && 'push', prefs.email && 'email'].filter(Boolean).join(' + ') || 'off'}</Text> </Pressable> <PickerSheet visible={open} onClose={() => setOpen(false)} title="Notifications" confirmLabel="Save" onConfirm={() => { setPrefs(draft); setOpen(false); }} > <View style={{ padding: 16, gap: 16 }}> {(['push', 'email'] as const).map((k) => ( <View key={k} style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}> <Text style={{ fontSize: 16, textTransform: 'capitalize' }}>{k}</Text> <Switch value={draft[k]} onValueChange={(v) => setDraft({ ...draft, [k]: v })} /> </View> ))} </View> </PickerSheet> </> ); }
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | required | Sheet content. |
onConfirm | () => void | — | Shows the confirm button. You close the sheet (call onClose / set visible). |
confirmDisabled | boolean | false | Disable the confirm button. |
| …shared props | visible, onClose, title, confirmLabel, cancelLabel, showCancel, sheetProps, theme. |
useDraft
function useDraft<V>(value: V, visible: boolean): [draft: V, setDraft: (v: V) => void];
Returns a draft copy of value that resets to value each time visible turns true. It's the hook the built-in sheets use; use it for your own PickerSheets as shown above.