Core concepts
Every component follows the same handful of conventions. Learn them once and you know the whole library.
Controlled and uncontrolled​
Wheels, time, date and emoji state can be used either way:
// Controlled: you own the state
const [date, setDate] = useState(new Date());
<TimePicker value={date} onChange={setDate} />
// Uncontrolled: the component owns it; you only listen
<TimePicker defaultValue={new Date()} onChange={(d) => console.log(d)} />
| Component | Controlled | Uncontrolled |
|---|---|---|
WheelPicker | value | defaultValue |
TimePicker | value | defaultValue (default: now) |
DatePicker / Calendar | value | defaultValue |
DateWheelPicker | value | defaultValue (default: today) |
EmojiPicker skin tone | skinTone | defaultSkinTone |
EmojiPicker recents | recent | defaultRecent |
SelectPicker / SelectList | value | — (always controlled) |
HorizontalList | value | — (always controlled) |
SelectPicker and SelectList don't keep their own selection. Always pass value and update it in onChange.
Setting value from outside (for example, a "Reset" button) moves the component without firing onChange. onChange only reports changes the user made.
onChange vs onValueChanging​
Wheel-based pickers (WheelPicker, MultiWheelPicker columns, TimePicker, DateWheelPicker) fire two callbacks:
| Callback | When | Use it for |
|---|---|---|
onChange | Once, after the wheel settles on a new value | Updating state, saving |
onValueChanging | On every row crossing while scrolling | Haptics, live previews |
<WheelPicker
items={items}
value={value}
onChange={setValue}
onValueChanging={() => Haptics.selectionAsync()}
/>
Sheets and drafts​
DatePickerSheet, TimePickerSheet and WheelPickerSheet wrap a picker in a BottomSheet with Cancel and Done:
- The sheet copies
valueinto a draft each time it opens. - Scrolling or tapping changes only the draft.
- Done calls
onConfirm(draft), thenonClose(). - Cancel, a backdrop tap, a swipe down or Android back calls
onClose()only, so the draft is discarded.
<TimePickerSheet visible={open} onClose={() => setOpen(false)} value={time} onConfirm={setTime} />
EmojiPickerSheet has no Done button: picking an emoji calls onSelect and (by default) closes the sheet. SelectPicker opens its own sheet; you don't manage visible.
Fields​
In a form, the picker hides behind a field. SelectPicker is one, and every sheet has a field version that takes the same props without visible / onClose: DatePickerField, TimePickerField, DateTimePickerField, WheelPickerField, EmojiPickerField. The field owns the open state, shows the confirmed value formatted for locale, and looks the same as SelectPicker's field. By default it opens a bottom sheet on iOS and Android and a dropdown under the field on the web; presentation picks 'bottomSheet', 'modal' or 'dropdown'.
<TimePickerField label="Alarm" value={time} onConfirm={setTime} />
Designs​
Pickers look native wherever they run: the iOS design on iOS, Material Design on Android, a web design in the browser. The design sets shapes and layout (where Done sits, a radio or a ✓, wheels or a clock dial), never your colors. Use one look everywhere, or for one component, with design:
<PickersProvider design="ios">{/* the iOS look on Android and the web too */}</PickersProvider>
<TimePicker design="material" value={time} onChange={setTime} />
→ Designs guide
Theming​
Three layers, each merged on top of the previous one:
- Built-in
lightTheme/darkTheme, chosen from the OS appearance, with the design's shapes <PickersProvider theme={…} darkTheme={…}>for your whole app- The
themeprop on any single component
<PickersProvider theme={{ colors: { primary: '#0EA5E9' }, radius: { md: 6 } }}>
<DatePicker theme={{ colors: { primary: '#22C55E' } }} /> {/* green only here */}
</PickersProvider>
→ Theming guide
Slot styles​
Every visual part of a component has a named slot. Pass styles for any of them through styles:
<DatePicker
styles={{
selectedDay: { borderRadius: 8 },
headerTitle: { fontSize: 20 },
rangeBand: { backgroundColor: '#FFE4E6' },
}}
/>
Slot names are typed, so your editor autocompletes them. Each component page lists its slots. → Styling guide
Render props​
When styles aren't enough, replace a part entirely:
| Component | Render props |
|---|---|
WheelPicker | renderItem, renderIndicator |
DatePicker / Calendar | renderDay, renderArrow, formatTitle |
SelectPicker / SelectList | renderTrigger, renderOption, renderGroupHeader, renderLoading |
HorizontalList | renderItem |
EmojiPicker | renderEmoji, categoryIcons |
*PickerField / PickerField | renderTrigger, renderChevron |
Strings and i18n​
No component hard-codes a user-facing string you can't change. Done, Cancel, placeholders, search hints, empty states and category labels are all props. Month, weekday and AM/PM names come from Intl for whatever locale you pass. → i18n & RTL