Skip to content

tenkaipl/react-native-select

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

React Native Select

npm version npm downloads license

Why?

Building a select in React Native looks simple — until it meets a real app. Small screens, layered rendering contexts, and platform quirks turn a basic dropdown into a source of edge-case bugs that are painful to track down.

@tenkaipl/react-native-select was built to handle these contexts correctly out of the box:

  • Plain Views — opens and positions correctly regardless of where the component sits in the layout
  • ScrollView / FlatList — no conflicts with gesture or tap interception (see ScrollView compatibility below)
  • Modal — proper z-index handling; the list never hides behind other UI layers
  • On-screen keyboard — the dropdown doesn't collide with an open keyboard
  • Landscape orientation — adapts gracefully to limited vertical space
  • SafeArea — respects safe areas on notched and edge-to-edge devices

About

A fully customizable select/dropdown for React Native. Inspired by react-select, built for native environments.

✨ Single select · search · clear · groups · disabled · dark theme
🎨 Custom colors, sizes, typography, icons, trigger, group header
📱 Native-first — keyboard, modal, layout handled correctly
⚡ Works with Expo & bare React Native

→ Try it live on Expo Snack

→ See all examples in the repository


Quick example

import React, { useState } from 'react';
import ReactNativeSelect from '@tenkaipl/react-native-select';

const OPTIONS = [
  { value: 'apple',  label: 'Apple' },
  { value: 'banana', label: 'Banana' },
  { value: 'cherry', label: 'Cherry' },
];

export default function App() {
  const [value, setValue] = useState(null);

  return (
    <ReactNativeSelect
      options={OPTIONS}
      value={value}
      onChange={(item) => setValue(item.value)}
      placeholder="Select a fruit..."
      searchable
      clearable
    />
  );
}

Installation

npm install @tenkaipl/react-native-select react-native-safe-area-context @expo/vector-icons 

SafeAreaProvider (required)

Wrap your app root with SafeAreaProvider. Without it, modal layout may be off on notched devices.

import { SafeAreaProvider } from 'react-native-safe-area-context';

export default function App() {
  return (
    <SafeAreaProvider>
      {/* your app */}
    </SafeAreaProvider>
  );
}

ScrollView compatibility

⚠️ This is important if you plan to use this component inside a ScrollView, FlatList, or SectionList: please add keyboardShouldPersistTaps="handled" to the parent:

<ScrollView keyboardShouldPersistTaps="handled">
  <ReactNativeSelect ... />
</ScrollView>

Without it, the first tap on an option dismisses the keyboard instead of selecting — you'd need a second tap. This is caused by how React Native's touch responder system works: the parent ScrollView intercepts the first tap even when the select renders inside a Modal.


Usage

Basic

Same as the Quick example above. onChange receives the full option object { value, label } — store whichever part you need.

With groups

const OPTIONS = [
  { type: 'group',  label: 'Fruits' },
  { type: 'option', value: 'apple',  label: 'Apple' },
  { type: 'option', value: 'banana', label: 'Banana' },
  { type: 'group',  label: 'Vegetables' },
  { type: 'option', value: 'carrot',   label: 'Carrot' },
  { type: 'option', value: 'broccoli', label: 'Broccoli' },
];

During search, group headers are shown only when at least one of their options matches the query.

flattenGroupedOptions helper

If your data comes from an API in nested format, use the built-in helper:

import ReactNativeSelect, { flattenGroupedOptions } from '@tenkaipl/react-native-select';

const GROUPED_DATA = [
  {
    label: 'Fruits',
    options: [
      { value: 'apple',  label: 'Apple' },
      { value: 'banana', label: 'Banana' },
    ],
  },
  {
    label: 'Vegetables',
    options: [{ value: 'carrot', label: 'Carrot' }],
  },
];

// Static data — flatten once, outside the component
const OPTIONS = flattenGroupedOptions(GROUPED_DATA);

// Dynamic data (e.g. fetched) — use useMemo
// const options = useMemo(() => flattenGroupedOptions(data), [data]);

Options format

// Regular option (selectable)
{ type?: 'option', value: string | number, label: string }

// Group header (non-selectable, skipped during search matching)
{ type: 'group', label: string }

type is optional for regular options — plain { value, label } objects work as-is.


Props

Data

Prop Type Default Description
options Option[] [] List of options. See Options format.
value string | number | null Currently selected value (not the full object).
onChange (item: Option) => void Called when the user selects an option. Receives the full { value, label } object.
placeholder string '' Text shown in the trigger when nothing is selected.
placeholderText string 'No results' Text shown in the list when the filtered result is empty.

Behavior

Prop Type Default Description
searchable boolean false Enables the search input inside the modal.
clearable boolean false Shows a clear (×) button when a value is selected or search text is present.
disabled boolean false Disables the trigger and prevents the dropdown from opening.
autoFocus boolean true Focuses the search input when the modal opens. On Android handled via onShow + setTimeout.
animationType 'fade' | 'slide' | 'none' 'fade' Animation used by the underlying <Modal>.
transparent boolean true Controls the transparent prop of <Modal>.
cursorColor string | null null Cursor color of the search TextInput.
pressedOpacity number 0.6 Opacity applied to all Pressable elements when pressed.
hideDivider boolean false Hides the vertical separator between action buttons and the chevron.
hideItemSeparator boolean false Hides horizontal separators between list items.
itemLabelSingleLine boolean false Renders option labels as a single line with ellipsis instead of wrapping.
autoCorrect boolean false Controls autoCorrect on the search TextInput.
spellCheck boolean false Controls spellCheck on the search TextInput. Note: when enabled, the first tap on the clear button may not register.

Render props

Prop Type Description
renderTrigger ({ onPress, disabled, selectedLabel, hasValue, onClear }) => ReactNode Fully replaces the default trigger button.
renderGroupHeader ({ item }) => ReactNode Replaces the default group header row.

Design tokens

Prop Type Default Description
theme 'light' | 'dark' 'light' Base color preset.
colors Partial<Colors> {} Overrides individual colors. Merged with the active theme. See Colors.
sizes Partial<Sizes> {} Overrides dimensions. See Sizes.
typography Partial<Typography> {} Overrides font settings. See Typography.
icons Partial<Icons> {} Overrides icon names (Ionicons). See Icons.

Style overrides

Prop Type Description
triggerStyle ViewStyle Additive style applied to the trigger container.
dropdownStyle ViewStyle Additive style applied to the dropdown container.
itemStyle ViewStyle Additive style applied to each option row.
groupHeaderStyle ViewStyle Additive style applied to each group header row.

⚠️ These are additive overrides — they do not replace internal styles. Overriding height, flex, overflow, or positioning properties may break the layout.


Colors

Key Description
primary Background of the trigger and dropdown.
secondary Color of the chevron and close icons.
colorTextPrimary Color of the selected label and option text.
colorTextSecondary Color of the placeholder and group header text.
lines Color of separators. Used as borderColor if border is not set.
border (optional) Explicit border color. Falls back to lines if omitted.
disabled Background of the trigger when disabled={true}.
shadow shadowColor / elevation color.
selected Background of the selected option row.

Sizes

Key Default Description
triggerMinWidth 180 Minimum width of the trigger.
itemHeight 50 Height of each row (trigger, option rows, search bar). Clamped between 3570.
maxListWidth 600 Maximum width of the dropdown.
maxListHeight undefined Maximum height of the dropdown.
separatorWidth hairlineWidth Height of horizontal separators.
borderRadius 15 Border radius of the trigger and dropdown.
borderWidth hairlineWidth Border width of the trigger and dropdown.
iconSize 24 Size of the chevron and close icons.
inputPaddingLeft 15 Left padding of the text / search input.
inputPaddingRight 5 Right padding inside the trigger and search bar.
itemPaddingHorizontal 15 Horizontal padding of option and group header rows.

itemHeight is clamped between 35 and 70. The lower bound exists because Android breaks the TextInput layout in the search bar at smaller heights.


Typography

Key Default Description
fontSize 16 Font size for option labels, placeholder, and group header text.
fontFamily undefined Font family applied to all text inside the component.

Icons

Icon names from @expo/vector-icons Ionicons.

Key Default Description
chevron 'chevron-down' Icon on the right side of the trigger and modal header.
close 'close' Icon for the clear button.

Roadmap

  • Multi-select
  • Async options (loadOptions)
  • Creatable (add a new option not in the list)
  • Fixed options
  • RTL – Support for right-to-left languages
  • Colored list items
  • Inline autocomplete (ghost text)
  • onSubmit – select the best match on Enter / search submit
  • Keyboard navigation (for web)
  • Animations during filtering
  • More efficient list rendering for large option sets (maybe using FlashList)

Comparison with react-select

If you're coming from a web project, here's where things stand:

Feature react-select React Native Select
Single select
Searchable
Clearable
Disabled state
Option groups
Multi-select ❌ planned
Async options ❌ planned
Creatable ❌ planned

Compatibility & status

Platform Supported
iOS
Android
Web
Expo

This is v1. The core API and behavior are stable; some edge cases and advanced features are still being refined.


Feedback

If something doesn't work as expected — especially on specific screens (modals, lists, keyboard-heavy views) — please open an issue. Real-world edge cases are exactly what this library is designed to solve.


License

MIT — see LICENSE.


Keywords

  • react-native
  • expo
  • react native select
  • react native dropdown
  • custom select
  • custom dropdown
  • headless ui
  • react native picker alternative

Built by Tenkai Software House 🇵🇱

About

A fully customizable, accessible select/dropdown for React Native — searchable, groupable, themeable. Inspired by react-select.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors