Skip to main content
Version: v0.5.x

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)} />
ComponentControlledUncontrolled
WheelPickervaluedefaultValue
TimePickervaluedefaultValue (default: now)
DatePicker / CalendarvaluedefaultValue
DateWheelPickervaluedefaultValue (default: today)
EmojiPicker skin toneskinTonedefaultSkinTone
EmojiPicker recentsrecentdefaultRecent
SelectPicker / SelectListvalue— (always controlled)
Select is controlled-only

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:

CallbackWhenUse it for
onChangeOnce, after the wheel settles on a new valueUpdating state, saving
onValueChangingOn every row crossing while scrollingHaptics, 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 value into a draft each time it opens.
  • Scrolling or tapping changes only the draft.
  • Done calls onConfirm(draft), then onClose().
  • 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.

Theming​

Three layers, each merged on top of the previous one:

  1. Built-in lightTheme / darkTheme, chosen from the OS appearance
  2. <PickersProvider theme={…} darkTheme={…}> for your whole app
  3. The theme prop 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:

ComponentRender props
WheelPickerrenderItem, renderIndicator
DatePicker / CalendarrenderDay, renderArrow, formatTitle
SelectPicker / SelectListrenderTrigger, renderOption, renderGroupHeader
EmojiPickerrenderEmoji, categoryIcons

→ Render props guide

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