react-native-multi-selectbox
v2.0.3
Published
Platform-independent select / multi-select / picker for React Native and Expo (TypeScript).
Maintainers
Readme
react-native-multi-selectbox
Cross-platform select / multi-select / picker for React Native and Expo (iOS, Android, web). Shared UI and a TypeScript-first API.
This file is the consumer documentation source of truth (npm + GitHub).
Monorepo layout / contributor commands live in the repository root README.
Live demo
sauzy34.github.io/react-native-multi-selectbox
Example app in the monorepo: apps/example · SectionListHostDemo.tsx · ScrollViewHostDemo.tsx
Release notes: CHANGELOG.md
Table of contents
Features
- Single- and multi-select with a consistent look on iOS, Android, and web (Expo / RNW)
- TypeScript types with a discriminated union on
isMulti - Searchable options (
hideInputFilterto turn search off) - Multi-select chips with remove / toggle affordances
- Host-safe by default:
virtualized={false}uses a bounded optionsScrollView(avoids nested VirtualizedList warnings under forms) - Opt-in
virtualizedFlatListfor very large option lists when there is no vertical scroll parent - Peers:
reactandreact-native— icons use built-in text glyphs
Install
npm install react-native-multi-selectbox
# or
yarn add react-native-multi-selectbox
# or
pnpm add react-native-multi-selectboxPeer dependencies
| Peer | Version |
| -------------- | --------------------------------- |
| react | ≥ 18 |
| react-native | ≥ 0.73 (Expo SDK 50+ recommended) |
No extra native modules. Works with Expo managed workflow and bare RN.
Maintainers: publish this package from
packages/multi-selectbox(orpnpm publish:libat the repo root). Do not runnpm publishat the monorepo root — that package isprivate: true.
Quick start
Options must include id and item (string label).
import { useState } from 'react'
import { View } from 'react-native'
import SelectBox, { type SelectOption } from 'react-native-multi-selectbox'
const COUNTRIES: SelectOption[] = [
{ id: 'us', item: 'United States' },
{ id: 'ca', item: 'Canada' },
{ id: 'gb', item: 'United Kingdom' },
{ id: 'in', item: 'India' },
]
export function ProfileForm() {
const [country, setCountry] = useState<SelectOption | Record<string, never>>({})
return (
<View style={{ padding: 16 }}>
<SelectBox
label="Country"
inputPlaceholder="Search countries…"
options={COUNTRIES}
value={country}
onChange={setCountry}
/>
</View>
)
}Open the live demo for more patterns (timezone, skills chips, tags, etc.).
Examples
Single select
Controlled value via value + onChange. Empty {} / missing item means “nothing selected” (1.x-compatible).
import { useState } from 'react'
import SelectBox, { type SelectOption } from 'react-native-multi-selectbox'
const TIMEZONES: SelectOption[] = [
{ id: 'utc', item: 'UTC' },
{ id: 'pt', item: 'Pacific Time (PT)' },
{ id: 'et', item: 'Eastern Time (ET)' },
]
export function TimezoneField() {
const [timezone, setTimezone] = useState<SelectOption>({ id: 'utc', item: 'UTC' })
return (
<SelectBox
label="Timezone"
inputPlaceholder="Find a timezone…"
options={TIMEZONES}
value={timezone}
onChange={setTimezone}
/>
)
}Multi select
Use isMulti, selectedValues, onMultiSelect, and onTapClose (chip remove). Parent owns the list — for example with a small toggle-by-id helper:
import { useState } from 'react'
import SelectBox, { type SelectOption } from 'react-native-multi-selectbox'
const SKILLS: SelectOption[] = [
{ id: 'ts', item: 'TypeScript' },
{ id: 'rn', item: 'React Native' },
{ id: 'react', item: 'React' },
]
function toggleById(list: SelectOption[], item: SelectOption): SelectOption[] {
return list.some((x) => x.id === item.id) ? list.filter((x) => x.id !== item.id) : [...list, item]
}
export function SkillsField() {
const [skills, setSkills] = useState<SelectOption[]>([{ id: 'ts', item: 'TypeScript' }])
return (
<SelectBox
label="Skills"
inputPlaceholder="Add skills…"
options={SKILLS}
selectedValues={skills}
onMultiSelect={(item) => setSkills((prev) => toggleById(prev, item))}
onTapClose={(item) => setSkills((prev) => toggleById(prev, item))}
isMulti
/>
)
}Reference implementation: multi cards in ScrollViewHostDemo.tsx.
Hide search / filter
For short lists, hide the filter input:
<SelectBox
label="Department"
options={DEPARTMENTS}
value={department}
onChange={setDepartment}
hideInputFilter
/>Styling & colors
<SelectBox
label="Channels"
options={CHANNELS}
selectedValues={channels}
onMultiSelect={...}
onTapClose={...}
isMulti
arrowIconColor="#4F46E5"
searchIconColor="#4F46E5"
toggleIconColor="#4F46E5"
selectedItemStyle={{ fontSize: 16, fontWeight: '500' }}
optionsLabelStyle={{ fontSize: 15 }}
multiOptionContainerStyle={{ backgroundColor: '#4F46E5', borderRadius: 999 }}
multiOptionsLabelStyle={{ color: '#fff', fontWeight: '600' }}
containerStyle={{ borderBottomColor: '#E5E7EB' }}
inputFilterStyle={{ color: '#111827' }}
/>Full style prop names are in Props and src/types.ts.
Large option lists
Default options rendering is a ScrollView (virtualized={false}) — safest inside forms. For large catalogs without an outer vertical scroll parent, opt into a windowed list:
<SelectBox
label="City"
options={MANY_CITIES}
value={city}
onChange={setCity}
virtualized // FlatList + max height + nestedScrollEnabled
listOptionProps={{ initialNumToRender: 12 }}
/>Scrolling hosts
React Native warns when a vertical VirtualizedList (FlatList / SectionList) is nested inside a vertical ScrollView (or another VirtualizedList).
| Host screen | Recommended virtualized |
| ------------------------------------- | ---------------------------- |
| FlatList / SectionList row | false (default) |
| Vertical ScrollView form | false (default) |
| Fixed / modal without list scroll | true (opt-in, large lists) |
Copy-paste hosts in the example app:
| Pattern | File | When to use |
| --------------- | ----------------------------------------------------------------------------- | ------------------------ |
| SectionList | SectionListHostDemo.tsx | Long screens (preferred) |
| ScrollView | ScrollViewHostDemo.tsx | Simple / short forms |
Tabs live in apps/example/App.tsx.
Multi chips use a horizontal ScrollView (content-sized chips) — that orientation does not conflict with a vertical page list.
React Native: VirtualizedList — avoid nesting vertical VirtualizedLists inside vertical ScrollViews of the same orientation without care; this library defaults to a non-virtualized options panel for that reason.
Additional controls (2.0.2+)
| Prop | Purpose |
| -------------------------------- | ---------------------------------------------------- |
| activeOptionsLabelStyle | Style labels of selected options in the open list |
| maxSelected | Multi only — max number of selections |
| optionIdKey / optionLabelKey | Map options from shapes like { id, name } |
| optionsAlign | left | center | right option label alignment |
| optionsMaxHeight | Options panel max height (default 180) |
| defaultOpen | Start with options open |
| onOpenChange | (open: boolean) => void when panel opens/closes |
| editable | false disables open/close |
| hideDropdownIcon | Hide the field chevron |
| hideChipClose | Hide × on multi chips |
| renderMultiChipLeading | Leading node on chips (e.g. avatar) |
Props
Overview of the public surface. See src/types.ts for the authoritative TypeScript definitions.
| Prop | Description |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| options | SelectOption[] — { id, item }[] (non-arrays treated as []) |
| label | Field label above the control |
| inputPlaceholder | Placeholder for closed field / filter |
| value / onChange | Single select (when isMulti is omitted or false) |
| selectedValues / onMultiSelect / onTapClose | Multi select (isMulti: true) |
| isMulti | true enables multi mode (discriminated props) |
| hideInputFilter | Hide the search field in the open panel |
| virtualized | false (default): options ScrollView; true: options FlatList |
| listOptionProps | Extra props for options FlatList (virtualized={true}) |
| listScrollViewProps | Extra props for options ScrollView (default mode) |
| multiSelectInputFieldProps | Extra props for the chips horizontal ScrollView |
| searchInputProps | Extra TextInput props for the filter |
| listEmptyText | Empty-state copy in the options panel |
| selectIcon | Custom chevron / icon node |
| arrowIconColor / searchIconColor / toggleIconColor | Icon paint colors |
| Style props | labelStyle, containerStyle, selectedItemStyle, optionsLabelStyle, optionContainerStyle, multiOptionContainerStyle, multiOptionsLabelStyle, multiListEmptyLabelStyle, listEmptyLabelStyle, inputFilterStyle, inputFilterContainerStyle |
| width | Field width (DimensionValue, default '100%') |
Test IDs for automation: TEST_IDS (exported from the package).
Types
import SelectBox, {
type SelectOption,
type SelectBoxProps,
type SelectBoxSingleProps,
type SelectBoxMultiProps,
type OptionsListProps,
type OptionsScrollViewProps,
type MultiSelectFieldProps,
TEST_IDS,
} from 'react-native-multi-selectbox'SelectBoxProps is a discriminated union on isMulti:
- Single:
isMultiomitted /false→value?,onChange? - Multi:
isMulti: true→selectedValues?,onMultiSelect?,onTapClose?
export type SelectOption = {
id: string | number
item: string
}References
| Resource | Link |
| ---------------------- | ------------------------------------------------------------------------------------------------------------ |
| Live web demo | sauzy34.github.io/react-native-multi-selectbox |
| npm | npmjs.com/package/react-native-multi-selectbox |
| Public types | src/types.ts |
| Changelog | CHANGELOG.md |
| Example app (monorepo) | apps/example |
| Repository | github.com/sauzy34/react-native-multi-selectbox |
| Issues | GitHub Issues |
