# React Native Pickers > @elik56a/react-native-pickers: pure-JS wheel, time, date (calendar, range, multiple, wheel), select and emoji pickers plus bottom sheets for React Native, Expo and React Native Web. This file contains all documentation content in a single document following the llmstxt.org standard. ## Introduction ## React Native Pickers **One dependency-free package for every picker a React Native app needs** — wheel, time, date (calendar, range, multiple, wheel), select / dropdown, emoji, the bottom sheets to put them in, and the form fields that open them. **npm** ```bash npm install @elik56a/react-native-pickers ``` **yarn** ```bash yarn add @elik56a/react-native-pickers ``` **pnpm** ```bash pnpm add @elik56a/react-native-pickers ``` **Expo** ```bash npx expo install @elik56a/react-native-pickers ``` It is **100% JavaScript with zero native code**, so it runs unchanged in **Expo Go**, **bare React Native** and **React Native Web**. There's no `pod install`, no rebuild and no config plugin. ```tsx live import { useState } from 'react'; import { DatePicker } from '@elik56a/react-native-pickers'; export default function Example() { const [date, setDate] = useState(new Date()); return ; } ``` :::tip Every example on this site is live Edit the code and the result updates instantly. Each example is a complete file you can copy into your app as-is, imports included. ::: ### What's included | Component | What it is | | --- | --- | | [`WheelPicker`](./components/wheel-picker) | iOS-style 3D wheel with infinite loop, disabled items, tap-to-type, custom rows | | [`MultiWheelPicker`](./components/multi-wheel-picker) | Several wheels sharing one selection band (durations, units, anything multi-column) | | [`TimePicker`](./components/time-picker) | 12h / 24h (auto from locale), minute & second steps, min / max time | | [`DatePicker`](./components/date-picker) | Calendar with **single**, **range** and **multiple** selection, or a day/month/year **wheel** | | [`DateTimePicker`](./components/date-time-picker) | Day and time together: an iOS-style wheel row, or a calendar with time wheels | | [`SelectPicker`](./components/select-picker) | Form field that opens a searchable sheet; single or multi-select with chips, groups, icons | | [`SelectList`](./components/select-list) | The same list, inline | | [`EmojiPicker`](./components/emoji-picker) | 1,906 emojis, categories, search, skin tones, recents, version filtering | | [`BottomSheet`](./components/bottom-sheet) | Animated sheet with drag-to-dismiss, for your own content | | [`DatePickerSheet`, `TimePickerSheet`, `DateTimePickerSheet`, `WheelPickerSheet`, `EmojiPickerSheet`](./components/picker-sheets) | Ready-made pickers in a sheet with Cancel / Done and draft state | | [`DatePickerField`, `TimePickerField`, `DateTimePickerField`, `WheelPickerField`, `EmojiPickerField`, `PickerField`](./components/picker-fields) | Form fields that open a picker sheet and show the picked value, styled like `SelectPicker` | ### Why this library - **No native code.** Works in Expo Go and on the web with nothing to link. The UI and behaviour are identical on iOS, Android and web, so your design isn't at the mercy of each OS's picker. - **One consistent API.** Every picker takes `value` + `onChange` (controlled) or `defaultValue` (uncontrolled), a `theme`, a `styles` object for each visual part, and render props for full control. - **Themeable to the pixel.** Light and dark themes are built in and follow the OS automatically. Override any token globally with `PickersProvider`, or per component. - **International.** Month, weekday and AM/PM names, the 12/24-hour clock, the first day of the week and the date-wheel order all come from `Intl` for any locale. Layouts flip for RTL. - **Accessible.** Wheels are `adjustable` (VoiceOver / TalkBack swipe up and down). Days announce their full date, and selected / disabled states are exposed. - **Fast.** Animations use the native driver. Wheels and the emoji grid are virtualized with fixed row layouts, and rows are memoized. - **Typed.** Written in TypeScript, with generics for value types and discriminated unions for selection modes, so `mode="range"` gives you a typed `{ start, end }`. ### Requirements | | Version | | --- | --- | | `react` | `>= 18` | | `react-native` | `>= 0.72` | | Expo | Any SDK (works in Expo Go) | | `react-native-web` | Supported | No other peer dependencies: no Reanimated, no Gesture Handler, no SVG. ### Where to next - [Installation](./getting-started/installation): install and optional setup - [Quick start](./getting-started/quick-start): every picker in five minutes - [Core concepts](./getting-started/core-concepts): controlled vs uncontrolled, sheets and drafts, theming, slots - [API reference](./api/reference): every export in one page --- ## For AI assistants This documentation is published in machine-readable form, following the [llms.txt](https://llmstxt.org) convention: | File | Contents | | --- | --- | | [`/llms.txt`](pathname:///llms.txt) | Index of every page with a one-line summary | | [`/llms-full.txt`](pathname:///llms-full.txt) | **The entire documentation in one Markdown file**: every component, prop table and example | | `/docs/**/*.md` | Each page as plain Markdown | Paste `llms-full.txt` into your assistant's context, or point Cursor / Claude / Copilot at the URL. `llms-full.txt` also ships inside the npm package, so a coding agent can read it offline from `node_modules/@elik56a/react-native-pickers/llms-full.txt`, always matching the installed version. ### Cheat sheet ```text Package: @elik56a/react-native-pickers (npm i @elik56a/react-native-pickers) Pure JS, zero native code, no peer deps besides react >=18 and react-native >=0.72. Works in Expo Go, bare RN, React Native Web. All exports are named exports from the package root. Native apps: prefer subpaths so Metro skips unused pickers (root import bundles ~130 KB of emoji data): /wheel /time /date /calendar /datetime /select /emoji /sheet /core e.g. '@elik56a/react-native-pickers/date' PickersProvider and theme/date utils live in /core. Subpaths need RN 0.79+ (Metro package exports). Controlled: value + onChange. Uncontrolled: defaultValue. SelectPicker/SelectList are controlled-only. onChange fires on user changes only (not when value is set programmatically). Wheels fire onChange when they settle; onValueChanging fires on every row crossing (use for haptics). WheelPicker items: (T | {label, value, disabled?})[] value onChange(value, index) loop itemHeight=40 visibleItems=5 variant '3d'|'flat' keyboardInput=true isEqual renderItem({item,index,selected,theme}) styles MultiWheelPicker columns: [{ key, items, value, onChange, flex?, width?, loop? }] separator: node | (leftIndex)=>node TimePicker value: Date onChange(date) format '12h'|'24h'|'auto' minuteInterval showSeconds minTime/maxTime locale DatePicker display 'calendar'(default)|'wheel' mode 'single' value: Date|null onChange(date) mode 'range' value: {start,end} onChange(range) minRangeDays maxRangeDays allowSingleDayRange mode 'multiple' value: Date[] onChange(dates) maxSelections mode 'ranges' value: DateRange[] onChange(ranges) maxRanges minRangeDays maxRangeDays allowSingleDayRange minDate maxDate isDateDisabled(date) markedDates{'YYYY-MM-DD':{dots?,label?,textColor?,disabled?}} locale firstDayOfWeek(0=Sun) initialMonth onMonthChange renderDay(state,theme) dayHeight=44 DateWheelPicker value: Date onChange order ['day','month','year'] monthFormat 'long'|'short'|'numeric' min/maxDate SelectPicker options: {label,value,description?,icon?,group?,disabled?,keywords?}[] value onChange(value, option) multiple → value T[], onChange(values, options), maxSelected, chips label placeholder searchable(dropdown: type in field, default on; sheet: >8 auto) clearable+onClear (single: reset value in onClear) renderTrigger({open,selected}) renderChevron({open,theme}) SelectList same list inline: options value onChange multiple listHeight EmojiPicker onSelect(emoji, data) columns=8 height=380 categories exclude maxEmojiVersion skinTone recent onRecentChange BottomSheet visible onClose title headerLeft headerRight footer height(<=1 fraction | px) enableSwipeToClose draggable(header drags too; native:true web:false; handle always drags) expandable expandedHeight Sheets (draft; commit on Done): DatePickerSheet visible onClose value onConfirm + all calendar props + mode + display + confirmOnSelect TimePickerSheet visible onClose value onConfirm + TimePicker props WheelPickerSheet visible onClose items value onConfirm EmojiPickerSheet visible onClose onSelect closeOnSelect=true DateTimePickerSheet visible onClose value onConfirm + DateTimePicker props PickerSheet visible onClose onConfirm children + useDraft(value, visible) Fields (a SelectPicker-style field that opens the sheet and shows the confirmed value; field owns open state): DatePickerField / TimePickerField / DateTimePickerField / WheelPickerField / EmojiPickerField = the matching *Sheet props WITHOUT visible (value, onConfirm | onSelect, mode, locale, title...) + label placeholder='Select…' displayValue(default: value formatted for locale) clearable+onClear(reset value there) disabled renderTrigger({open,displayValue,disabled}) renderChevron({open,theme}) onOpen onClose style testID styles = field slots (label trigger triggerText placeholder chevron clear) + the picker's slots EmojiPickerField also takes value: string (the emoji shown). Sheet title defaults to label. PickerField same field props, children: ({visible, onClose}) => Theme: ; per-component `theme` prop. tokens: colors{background,surface,sheet,text,textSecondary,textDisabled,primary,onPrimary,primaryMuted,border,backdrop,indicator,handle} radius{sm,md,lg,full,sheet} spacing{xs..xl} typography{fontFamily,fontFamilyMedium,sizes,weights} animation{duration} Slots: every component takes `styles={{ slotName: style }}`. Utils: toDateKey(d) 'YYYY-MM-DD' local; addDays addMonths isSameDay compareDay isBetweenDays formatDate(d, locale, opts) searchEmojis findEmoji withSkinTone getAllEmojis; defaultFilter for select search. ``` ### Canonical examples ```tsx import { useState } from 'react'; import { DatePicker, DatePickerField, DatePickerSheet, SelectPicker, TimePicker, TimePickerField, WheelPicker } from '@elik56a/react-native-pickers'; import type { DateRange } from '@elik56a/react-native-pickers'; function Examples() { const [size, setSize] = useState('M'); const [time, setTime] = useState(new Date()); const [date, setDate] = useState(null); const [range, setRange] = useState({ start: null, end: null }); const [country, setCountry] = useState(null); const [open, setOpen] = useState(false); return ( <> setOpen(false)} mode="range" value={range} onConfirm={setRange} /> {/* In forms: a field that opens the sheet and shows the value, no open state needed */} ); } ``` --- ## Install React Native Pickers ## Installation React Native Pickers is pure JavaScript: one package, with no native linking, pods or rebuilds. **npm** ```bash npm install @elik56a/react-native-pickers ``` **yarn** ```bash yarn add @elik56a/react-native-pickers ``` **pnpm** ```bash pnpm add @elik56a/react-native-pickers ``` **Expo** ```bash npx expo install @elik56a/react-native-pickers ``` That's it. The package is pure JavaScript, so there is: - no `pod install` - no native rebuild — it works in **Expo Go** - no Babel plugin, Metro config or config plugin - no other dependency (no Reanimated, Gesture Handler or SVG) Peer dependencies are `react >= 18` and `react-native >= 0.72`, which your app already has. ### Optional: add the theme provider Every component works with sensible light and dark defaults that follow the OS appearance. To use your brand colours everywhere, wrap your app once: ```tsx title="App.tsx" import { PickersProvider } from '@elik56a/react-native-pickers'; export default function App() { return ( ); } ``` See [Theming](../guides/theming) for every token. ### React Native Web Nothing extra is needed beyond a standard `react-native-web` setup (Expo web already has one). If you bundle manually, alias `react-native` to `react-native-web`: ```js title="webpack.config.js" module.exports = { resolve: { alias: { 'react-native$': 'react-native-web' } }, }; ``` This documentation site is itself built this way: every live example is the real library running on React Native Web. ### Importing Everything is a named export from the package root: ```tsx import { WheelPicker, MultiWheelPicker, TimePicker, DatePicker, Calendar, DateWheelPicker, SelectPicker, SelectList, EmojiPicker, BottomSheet, PickerSheet, DatePickerSheet, TimePickerSheet, WheelPickerSheet, EmojiPickerSheet, PickersProvider, } from '@elik56a/react-native-pickers'; ``` The package is marked `sideEffects: false`, so bundlers that tree-shake (web) drop what you don't import. On native, Metro resolves the TypeScript source through the `react-native` export condition. #### Smaller native bundles Metro doesn't tree-shake, so a root import bundles every picker on iOS and Android, including about 130 KB of emoji data. Import from the feature subpaths instead and Metro only bundles what you use: ```tsx import { DatePicker, DatePickerSheet } from '@elik56a/react-native-pickers/date'; import { SelectPicker } from '@elik56a/react-native-pickers/select'; import { PickersProvider } from '@elik56a/react-native-pickers/core'; ``` | Subpath | Exports | | --- | --- | | `/wheel` | `WheelPicker`, `MultiWheelPicker`, `WheelPickerSheet` | | `/time` | `TimePicker`, `TimePickerSheet` | | `/date` | `DatePicker`, `DateWheelPicker`, `DatePickerSheet` | | `/calendar` | `Calendar` | | `/datetime` | `DateTimePicker`, `DateTimePickerSheet` | | `/select` | `SelectPicker`, `SelectList` | | `/emoji` | `EmojiPicker`, `EmojiPickerSheet`, emoji utils | | `/sheet` | `BottomSheet`, `PickerSheet`, `useDraft` | | `/core` | `PickersProvider`, themes, date utils | Each subpath also exports its types. Subpaths need React Native 0.79+ (Expo SDK 53+), where Metro reads package `exports` by default. On older versions, set `resolver.unstable_enablePackageExports = true` in `metro.config.js` or keep the root import. --- ## Quick start Each block below is a complete, runnable component. Edit it, and copy it straight into your app when you're done. ### Wheel Pass primitives or `{ label, value }` objects. ```tsx live import { useState } from 'react'; import { WheelPicker } from '@elik56a/react-native-pickers'; export default function Example() { const [size, setSize] = useState('M'); return ; } ``` ### Time The 12h / 24h format comes from the locale automatically. ```tsx live import { useState } from 'react'; import { TimePicker } from '@elik56a/react-native-pickers'; export default function Example() { const [time, setTime] = useState(new Date()); return ; } ``` ### Date (calendar) ```tsx live import { useState } from 'react'; import { DatePicker } from '@elik56a/react-native-pickers'; export default function Example() { const [date, setDate] = useState(null); return ; } ``` ### Date range ```tsx live import { useState } from 'react'; import { Text } from 'react-native'; import { DatePicker, formatDate } from '@elik56a/react-native-pickers'; import type { DateRange } from '@elik56a/react-native-pickers'; export default function Example() { const [range, setRange] = useState({ start: null, end: null }); return ( <> {range.start ? formatDate(range.start) : '—'} → {range.end ? formatDate(range.end) : '—'} ); } ``` ### Date (wheel) ```tsx live import { useState } from 'react'; import { DatePicker } from '@elik56a/react-native-pickers'; export default function Example() { const [birthday, setBirthday] = useState(new Date(1990, 4, 17)); return ; } ``` ### Select Tap the field to open a searchable bottom sheet. ```tsx live import { useState } from 'react'; import { SelectPicker } from '@elik56a/react-native-pickers'; const languages = [ { label: 'English', value: 'en' }, { label: 'Español', value: 'es' }, { label: 'Français', value: 'fr' }, { label: 'Deutsch', value: 'de' }, { label: 'עברית', value: 'he', keywords: ['hebrew'] }, ]; export default function Example() { const [lang, setLang] = useState(null); return ; } ``` ### Emoji ```tsx live import { useState } from 'react'; import { Text } from 'react-native'; import { EmojiPicker } from '@elik56a/react-native-pickers'; export default function Example() { const [text, setText] = useState(''); return ( <> {text} setText((t) => t + emoji)} /> ); } ``` ### In a bottom sheet Every picker also ships as a sheet. The sheet keeps a **draft** and only calls `onConfirm` when the user taps **Done**; **Cancel**, a backdrop tap or a swipe down discards it. ```tsx live import { useState } from 'react'; import { Pressable, Text } from 'react-native'; import { DatePickerSheet, formatDate } from '@elik56a/react-native-pickers'; import type { DateRange } from '@elik56a/react-native-pickers'; export default function Example() { const [open, setOpen] = useState(false); const [range, setRange] = useState({ start: null, end: null }); return ( <> setOpen(true)} style={{ backgroundColor: '#C8460D', padding: 14, borderRadius: 12 }}> {range.start && range.end ? `${formatDate(range.start)} → ${formatDate(range.end)}` : 'Pick dates'} setOpen(false)} title="Check-in / Check-out" mode="range" value={range} onConfirm={setRange} minDate={new Date()} /> ); } ``` ### In a form field A field opens the sheet for you and shows the confirmed value, so there's no open state to manage. Each sheet has one: `DatePickerField`, `TimePickerField`, `DateTimePickerField`, `WheelPickerField` and `EmojiPickerField`. ```tsx live import { useState } from 'react'; import { View } from 'react-native'; import { DatePickerField, TimePickerField } from '@elik56a/react-native-pickers'; export default function Example() { const [day, setDay] = useState(null); const [time, setTime] = useState(); return ( ); } ``` → [Picker fields](../components/picker-fields) ### Next - [Core concepts](./core-concepts): the few ideas every component shares - [Components](../components/wheel-picker): full props for each picker --- ## 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: ```tsx // Controlled: you own the state const [date, setDate] = useState(new Date()); // Uncontrolled: the component owns it; you only listen 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) | :::caution 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: | 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 | ```tsx Haptics.selectionAsync()} /> ``` ### Sheets and drafts `DatePickerSheet`, `TimePickerSheet` and `WheelPickerSheet` wrap a picker in a [`BottomSheet`](../components/bottom-sheet) 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. ```tsx 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`](../components/picker-fields). The field owns the open state, shows the confirmed value formatted for `locale`, and looks the same as `SelectPicker`'s field. ```tsx ``` ### Theming Three layers, each merged on top of the previous one: 1. Built-in `lightTheme` / `darkTheme`, chosen from the OS appearance 2. `` for your whole app 3. The `theme` prop on any single component ```tsx {/* green only here */} ``` → [Theming guide](../guides/theming) ### Slot styles Every visual part of a component has a named **slot**. Pass styles for any of them through `styles`: ```tsx ``` Slot names are typed, so your editor autocompletes them. Each component page lists its slots. → [Styling guide](../guides/styling) ### 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` | | `EmojiPicker` | `renderEmoji`, `categoryIcons` | | `*PickerField` / `PickerField` | `renderTrigger`, `renderChevron` | → [Render props guide](../guides/render-props) ### 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](../guides/i18n-rtl) --- ## React Native Bottom Sheet ## BottomSheet `BottomSheet` is the React Native bottom sheet every picker sheet is built on, exported so your own content can use it: filters, menus, confirmations, forms. It slides up over a dimmed backdrop, can be dragged down to dismiss, and is built on React Native's `Modal`, `Animated` and `PanResponder` only. ```tsx import { BottomSheet } from '@elik56a/react-native-pickers'; ``` ```tsx live import { useState } from 'react'; import { Pressable, Text, View } from 'react-native'; import { BottomSheet } from '@elik56a/react-native-pickers'; export default function Example() { const [open, setOpen] = useState(false); return ( <> setOpen(true)} style={{ backgroundColor: '#C8460D', padding: 14, borderRadius: 12 }}> Open sheet setOpen(false)} title="Share" footer={ setOpen(false)} style={{ backgroundColor: '#F4F2EE', padding: 14, borderRadius: 12 }}> Close } > {['Copy link', 'Send via message', 'Share to…'].map((a) => ( {a} ))} ); } ``` ### Try the props {['Copy link', 'Send via message', 'Share to…'].map((a) => ( {a} ))} ### Bottom sheet or modal `presentation` switches between a bottom sheet and a centered modal: - `'auto'` (default): a modal on web windows wider than the sheet (688 px), a bottom sheet everywhere else. - `'bottomSheet'`: always slides up from the bottom, with a drag handle. - `'modal'`: always a centered dialog that fades in, with no drag handle or swipe-to-close. ```tsx … ``` ### Opening and closing `BottomSheet` is fully controlled: render it with `visible` and change `visible` in `onClose`. `onClose` is called when the user: - taps the backdrop (`closeOnBackdropPress`, default `true`) - drags the sheet down past about 30% of its height (max 140 px), or flicks it down; on the web, scrolls the wheel down over it (`enableSwipeToClose`, default `true`) - presses Android's back button When `visible` turns `false`, the sheet animates out and then unmounts its children. `onOpened` and `onClosed` fire when the animations finish, which is handy for focusing inputs or running work after the sheet is gone. ### Header The header row shows when you pass `title`, `headerLeft` or `headerRight`. A string `title` is styled for you (use the `title` slot); a node is rendered as-is. The drag handle and header are both drag areas. ```tsx } headerRight={