Picker sheets
Every React Native picker in this library is also available inside a BottomSheet with Cancel / Done: in the header beside the title on iOS, under the content with OK in Material, per the design. They all share the same "chrome" props and the same draft behaviour.
You don't need your own button and visible state. Picker fields (DatePickerField, TimePickerField, DateTimePickerField, DurationPickerField, MonthPickerField, ColorPickerField, WheelPickerField, EmojiPickerField) take the same props without visible/onClose, render a field that opens the sheet, and show the confirmed value.
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.
Each sheet can also open from a field that shows the picked value.
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' · 'OK' Material | 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. |
design | 'ios' | 'material' | 'web' | from PickersProvider | The look to follow, over the provider's: iOS, Material or web. |
DatePickerSheet​
A calendar (single, range, multiple) or date wheel in a sheet.
<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, no half-picked range in ranges mode. Multiple and ranges modes can confirm 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' | 'ranges' | 'single' | Selection mode. |
value | Date | null · DateRange · Date[] · DateRange[] | — | Current committed value; copied into the draft on open. |
onConfirm | (date: Date) · (range: DateRange) · (dates: Date[]) · (ranges: DateRange[]) => void | required | Called with the draft on Done. |
minRangeDays / maxRangeDays | number | — | Range and ranges modes. |
maxSelections | number | — | Multiple mode. |
maxRanges | number | — | Ranges 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​
<> <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​
<> <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}.
<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.
<> <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.