SelectPicker
SelectPicker is a React Native form field that opens a bottom sheet with a searchable list: a dropdown / select that feels native on phones. Single or multiple selection, with groups, icons, descriptions, chips and a clear button.
import { SelectPicker } from '@elik56a/react-native-pickers';
import { useState } from 'react'; import { SelectPicker } from '@elik56a/react-native-pickers'; const fruits = ['Apple', 'Banana', 'Cherry', 'Mango', 'Orange', 'Peach', 'Pear', 'Plum', 'Strawberry', 'Watermelon'].map((f) => ({ label: f, value: f.toLowerCase(), })); export default function Example() { const [fruit, setFruit] = useState<string | null>(null); return <SelectPicker label="Favourite fruit" placeholder="Pick one" options={fruits} value={fruit} onChange={setFruit} />; }
SelectPicker doesn't store the selection itself. Pass value and update it from onChange.
Try the propsโ
presentation<SelectPicker
label="Fruit"
options={fruits}
value={value}
onChange={setValue}
searchable
/>
Optionsโ
interface SelectOption<T> {
label: string; // row text, and what search matches first
value: T; // any type; compared with isEqual (default Object.is)
description?: string; // second line, also searchable
icon?: React.ReactNode; // leading element: flag, avatar, icon
group?: string; // section header
disabled?: boolean;
keywords?: string[]; // extra search terms, e.g. other languages or synonyms
}
import { useState } from 'react'; import { Text } from 'react-native'; import { SelectPicker } from '@elik56a/react-native-pickers'; const countries = [ { label: 'Israel', value: 'IL', icon: <Text>๐ฎ๐ฑ</Text>, group: 'Middle East', keywords: ['ืืฉืจืื'] }, { label: 'Jordan', value: 'JO', icon: <Text>๐ฏ๐ด</Text>, group: 'Middle East' }, { label: 'France', value: 'FR', icon: <Text>๐ซ๐ท</Text>, group: 'Europe', description: 'Rรฉpublique franรงaise' }, { label: 'Germany', value: 'DE', icon: <Text>๐ฉ๐ช</Text>, group: 'Europe', keywords: ['deutschland'] }, { label: 'Spain', value: 'ES', icon: <Text>๐ช๐ธ</Text>, group: 'Europe', disabled: true, description: 'Not shipping yet' }, { label: 'Japan', value: 'JP', icon: <Text>๐ฏ๐ต</Text>, group: 'Asia', keywords: ['nippon'] }, ]; export default function Example() { const [country, setCountry] = useState<string | null>('FR'); return <SelectPicker label="Ship to" options={countries} value={country} onChange={setCountry} searchable />; }
Groups appear in the order their first option appears in options. Options without a group are listed together without a header.
Multiple selectionโ
multiple switches value to an array and onChange to (values, options). The sheet gets checkboxes and a Done button, and the field shows removable chips (tap a chip to remove it). maxSelected caps the selection; once reached, the remaining options are disabled.
import { useState } from 'react'; import { SelectPicker } from '@elik56a/react-native-pickers'; const skills = ['React', 'React Native', 'TypeScript', 'Expo', 'Node.js', 'GraphQL', 'Swift', 'Kotlin'].map((s) => ({ label: s, value: s })); export default function Example() { const [picked, setPicked] = useState<string[]>(['React Native', 'TypeScript']); return ( <SelectPicker multiple label="Skills (max 4)" placeholder="Add skills" options={skills} value={picked} onChange={setPicked} maxSelected={4} clearable /> ); }
Set chips={false} to show a comma-separated list instead of chips.
Searchโ
As a dropdown (the default on the web), the user types right in the field to filter the options; searchable={false} keeps the field read-only. In a sheet, the search field appears automatically when there are more than 8 options; force it with searchable / searchable={false}. The default filter:
- matches
label,descriptionandkeywords - is case-insensitive and accent-insensitive (
cremefindsCrรจme), and ignores Hebrew niqqud - splits the query on spaces and requires every word to match (
new yofindsNew York)
Provide filterOption for your own logic, for example matching only from the start of the label:
<SelectPicker
options={options}
value={value}
onChange={setValue}
filterOption={(option, query) => option.label.toLowerCase().startsWith(query.toLowerCase())}
/>
The default is exported as defaultFilter, so you can extend it:
import { defaultFilter } from '@elik56a/react-native-pickers';
filterOption={(o, q) => defaultFilter(o, q) || o.value === q.toUpperCase()}
Clearingโ
clearable shows a ร in the field when something is selected.
- In multiple mode, ร calls
onChange([], [])and thenonClear. - In single mode, ร calls only
onClear. Reset your state there:
import { useState } from 'react'; import { SelectPicker } from '@elik56a/react-native-pickers'; export default function Example() { const [v, setV] = useState<string | null>('b'); return ( <SelectPicker label="Priority" clearable onClear={() => setV(null)} options={[ { label: 'Low', value: 'a' }, { label: 'Medium', value: 'b' }, { label: 'High', value: 'c', description: 'Pages on-call' }, ]} value={v} onChange={setV} /> ); }
Closing behaviourโ
In single mode, the value changes as soon as an option is tapped and the sheet or dropdown closes a moment later, once the row's ripple has played (closeOnSelect, default true). With closeOnSelect={false} the sheet stays open and shows a Done button. Multiple mode always shows Done. onOpen and onClose fire when the sheet opens and closes.
Hover and rippleโ
Rows tint while the pointer is over them and ripple from the touch point when picked; in single mode the list closes right after the ripple plays. The field tints on hover but doesn't ripple. Both colours come from the theme, so a brand tint is one prop:
import { useState } from 'react'; import { SelectPicker } from '@elik56a/react-native-pickers'; const frameworks = ['Expo', 'React Native CLI', 'React Native Web', 'Next.js', 'Remix', 'Vite'].map((f) => ({ label: f, value: f })); export default function Example() { const [framework, setFramework] = useState<string | null>(null); return ( <SelectPicker label="Framework" placeholder="Choose a framework" options={frameworks} value={framework} onChange={setFramework} theme={{ colors: { hover: 'rgba(200,70,13,0.08)', ripple: 'rgba(200,70,13,0.24)' } }} /> ); }
See Theming โ Hover and ripple to change them everywhere or turn them off.
Custom triggerโ
renderTrigger replaces the field. Call open() to show the sheet:
import { useState } from 'react'; import { Pressable, Text } from 'react-native'; import { SelectPicker } from '@elik56a/react-native-pickers'; const sorts = [ { label: 'Most relevant', value: 'relevance' }, { label: 'Newest first', value: 'newest' }, { label: 'Price: low to high', value: 'price_asc' }, { label: 'Price: high to low', value: 'price_desc' }, ]; export default function Example() { const [sort, setSort] = useState<string | null>('relevance'); return ( <SelectPicker options={sorts} value={sort} onChange={setSort} sheetTitle="Sort by" renderTrigger={({ open, selected }) => ( <Pressable onPress={open} style={{ alignSelf: 'flex-start', paddingVertical: 8, paddingHorizontal: 14, borderRadius: 999, backgroundColor: '#EEF2FF' }}> <Text style={{ color: '#C8460D', fontWeight: '600' }}>โ {selected[0]?.label ?? 'Sort'}</Text> </Pressable> )} /> ); }
Custom chevronโ
The field's chevron rotates when it opens and closes. renderChevron swaps the icon and keeps the rotation, so any icon works, an SVG included:
import { useState } from 'react'; import { Text } from 'react-native'; import { SelectPicker } from '@elik56a/react-native-pickers'; const sizes = ['Small', 'Medium', 'Large'].map((s) => ({ label: s, value: s })); export default function Example() { const [size, setSize] = useState<string | null>(null); return ( <SelectPicker label="Size" options={sizes} value={size} onChange={setSize} renderChevron={({ theme }) => <Text style={{ color: theme.colors.primary, fontSize: 16 }}>โ</Text>} /> ); }
Custom optionsโ
renderOption replaces each row's content (taps, selection background and accessibility are still handled). renderGroupHeader replaces section headers.
import { useState } from 'react'; import { View, Text } from 'react-native'; import { SelectPicker } from '@elik56a/react-native-pickers'; const people = [ { label: 'Ada Lovelace', value: 'ada', description: 'Mathematician', color: '#F59E0B' }, { label: 'Alan Turing', value: 'alan', description: 'Computer scientist', color: '#10B981' }, { label: 'Grace Hopper', value: 'grace', description: 'Rear admiral', color: '#6366F1' }, ]; export default function Example() { const [who, setWho] = useState<string | null>(null); return ( <SelectPicker label="Assignee" options={people} value={who} onChange={setWho} renderOption={({ option, selected, theme }) => { const p = people.find((x) => x.value === option.value)!; return ( <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12, flex: 1 }}> <View style={{ width: 36, height: 36, borderRadius: 18, backgroundColor: p.color, alignItems: 'center', justifyContent: 'center' }}> <Text style={{ color: 'white', fontWeight: '700' }}>{p.label[0]}</Text> </View> <View style={{ flex: 1 }}> <Text style={{ color: theme.colors.text, fontWeight: selected ? '700' : '500' }}>{p.label}</Text> <Text style={{ color: theme.colors.textSecondary, fontSize: 12 }}>{p.description}</Text> </View> {selected && <Text style={{ color: theme.colors.primary }}>โ</Text>} </View> ); }} /> ); }
Presentationโ
presentation picks how the options open:
'auto'(default): a dropdown on the web, a bottom sheet on native.'bottomSheet': always a bottom sheet.'modal': always a centered modal.'dropdown': a popover under the field, as wide as the field. It flips above the field when there's more room there, and a tap outside closes it.
import { useState } from 'react'; import { SelectPicker } from '@elik56a/react-native-pickers'; export default function Example() { const [value, setValue] = useState(null); return ( <SelectPicker label="Framework" value={value} onChange={setValue} options={['Expo', 'React Native CLI', 'Next.js', 'Vite'].map((label) => ({ label, value: label }))} /> ); }
Sheet optionsโ
Customise the sheet through sheet, which takes PickerSheet props such as confirmLabel and sheetProps (passed through to BottomSheet):
<SelectPicker
options={options}
value={value}
onChange={setValue}
sheetTitle="Choose a city"
sheet={{ confirmLabel: 'Apply', sheetProps: { height: 0.7, enableSwipeToClose: false } }}
listHeight={400}
autoFocusSearch
/>
Expanding the sheetโ
Long lists open at their content height and can grow to full screen: drag the header up on iOS and Android, or scroll the wheel up over the sheet on the web. The list grows with the sheet. Set the expanded height through sheet.sheetProps:
import { useState } from 'react'; import { SelectPicker } from '@elik56a/react-native-pickers'; const countries = [ 'Argentina', 'Australia', 'Austria', 'Belgium', 'Brazil', 'Canada', 'Chile', 'China', 'Denmark', 'Egypt', 'Finland', 'France', 'Germany', 'Greece', 'India', 'Ireland', 'Israel', 'Italy', 'Japan', 'Mexico', 'Netherlands', 'Norway', 'Poland', 'Portugal', 'Spain', 'Sweden', 'Switzerland', 'Turkey', 'United Kingdom', 'United States', ].map((c) => ({ label: c, value: c })); export default function Example() { const [country, setCountry] = useState<string | null>(null); return ( <SelectPicker label="Country" options={countries} value={country} onChange={setCountry} presentation="bottomSheet" sheet={{ sheetProps: { expandedHeight: 0.9, draggable: true } }} /> ); }
expandable: false keeps the sheet at its content height; draggable: false turns off dragging on native. See BottomSheet for all the options.
Propsโ
Valueโ
| Prop | Type | Default | Description |
|---|---|---|---|
options | ReadonlyArray<SelectOption<T>> | required | The options. |
multiple | boolean | false | Multi-select mode. |
value | T | null ยท T[] | โ | Selected value(s). |
onChange | (value: T, option: SelectOption<T>) => void ยท (values: T[], options: SelectOption<T>[]) => void | โ | Single ยท multiple. |
maxSelected | number | โ | Multiple only: maximum selected options. |
isEqual | (a: T, b: T) => boolean | Object.is | Value equality. |
Fieldโ
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | โ | Label above the field; also the default sheet title. |
placeholder | string | 'Selectโฆ' | Shown when nothing is selected. |
disabled | boolean | false | Disable the field. |
clearable | boolean | false | Show a ร button when something is selected. |
onClear | () => void | โ | Called by ร. In single mode, reset value here. |
chips | boolean | true | Multiple: show selections as removable chips. |
renderChevron | (info: { open, theme }) => ReactNode | โ | Custom chevron icon (an SVG, an icon fontโฆ). It still rotates on open and close. |
renderTrigger | (info: { open, selected, disabled }) => ReactNode | โ | Replace the field entirely. |
style | StyleProp<ViewStyle> | โ | Wrapper style (label + field). |
Sheet & listโ
| Prop | Type | Default | Description |
|---|---|---|---|
presentation | 'auto' | 'bottomSheet' | 'modal' | 'dropdown' | 'auto' | How the options open. auto: dropdown on the web, bottom sheet on native. |
sheetTitle | string | label | Sheet header title. |
closeOnSelect | boolean | true | Single: close on tap. false shows a Done button. |
sheet | Partial<SheetChromeProps> | โ | confirmLabel, cancelLabel, showCancel, title, sheetProps. |
onOpen / onClose | () => void | โ | Sheet open / close. |
searchable | boolean | dropdown: true ยท sheet: options.length > 8 | Dropdown: type in the field to filter. Sheet: show the search field. |
searchPlaceholder | string | 'Search' | Search hint. |
filterOption | (option, query) => boolean | defaultFilter | Custom search. |
emptyText | string | 'No results' | Shown when nothing matches. |
showCheckmarks | boolean | true | Check marks / checkboxes on rows. |
renderOption | (info: { option, selected, disabled, theme }) => ReactNode | โ | Custom row content. |
renderGroupHeader | (group: string, theme) => ReactNode | โ | Custom section header. |
listHeight | number | grows with content | Fixed list height. |
autoFocusSearch | boolean | false | Focus search when the sheet opens. |
styles | SlotStyles<SelectSlot | SelectTriggerSlot> | โ | Slots for both the field and the list. |
theme | DeepPartial<PickerTheme> | โ | Theme override. |
testID | string | โ | Test ID on the list. |
Slotsโ
Field (SelectTriggerSlot):
| Slot | Element |
|---|---|
label | Label text above the field |
trigger | The field box |
triggerText | Selected label(s) |
placeholder | Placeholder text |
chip / chipText | Multi-select chips |
chevron | The drawn chevron (a bordered View: borderColor sets its color, width / height its size) |
clear | ร button text |
List: see SelectList slots.
Accessibilityโ
The field is a button labelled with label (or the placeholder), with the current selection as its hint and an expanded state. Options are buttons (single) or checkboxes (multiple) with selected / checked / disabled states, and chips announce Remove {label}.
Relatedโ
- SelectList: the list without the field and sheet
- Country select recipe
- Tag picker recipe