npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tenkaipl/react-native-select

v0.1.2

Published

Customizable select/dropdown for React Native

Readme

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

→ 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 🇵🇱