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

react-native-modern-select

v0.4.0

Published

A fully type-safe, headless-friendly Select and Multi-Select component for React Native, powered by [@gorhom/bottom-sheet](https://github.com/gorhom/react-native-bottom-sheet).

Readme

react-native-modern-select

A fully type-safe, headless-friendly Select and Multi-Select component for React Native, powered by @gorhom/bottom-sheet.

react-native-modern-select is designed for real production apps and internal design systems where:

  • the data shape is not fixed
  • strong typing matters
  • UI must be customizable
  • and large option lists must remain usable

✨ Features

  • ✅ Single select & multi select
  • ✅ Fully type-safe generic API (Select<T>)
  • ✅ Works with any data shape (no forced {label, value} model)
  • ✅ Built-in search
  • ✅ Bottom sheet powered by @gorhom/bottom-sheet
  • ✅ Custom input renderer
  • ✅ Custom option renderer
  • ✅ Custom footer (for multi-select)
  • ✅ Confirm / close footer for multi-select
  • ✅ Styling hooks for default UI
  • ✅ Suitable for design systems and component libraries

📦 Installation

npm install react-native-modern-select

or

yarn add react-native-modern-select

⚠️ Peer Dependencies (required)

This package depends on the following libraries which must already be installed in your app:

npm install @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler

🛠 Required setup

Because this component uses @gorhom/bottom-sheet, your application must already be configured correctly.


1. Gesture handler root

Wrap your application root:

import { GestureHandlerRootView } from "react-native-gesture-handler";

<GestureHandlerRootView style={{ flex: 1 }}>
    <App />
</GestureHandlerRootView>;

2. Reanimated babel plugin

In babel.config.js:

module.exports = {
    presets: ["module:metro-react-native-babel-preset"],
    plugins: ["react-native-reanimated/plugin"],
};

3. Bottom sheet provider (recommended)

import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";

<BottomSheetModalProvider>
    <App />
</BottomSheetModalProvider>;

🚀 Basic usage – single select

import { Select } from "react-native-modern-select";

type User = {
    id: string;
    name: string;
};

const [user, setUser] = useState<User | null>(null);

<Select
    value={user}
    options={users}
    onChange={setUser}
    getKey={(u) => u.id}
    getLabel={(u) => u.name}
/>;

🚀 Multi select

const [selectedUsers, setSelectedUsers] = useState<User[]>([]);

<Select
    multiple
    value={selectedUsers}
    options={users}
    onChange={setSelectedUsers}
    getKey={(u) => u.id}
    getLabel={(u) => u.name}
/>;

In multi-select mode:

  • selections are toggled immediately
  • the bottom sheet remains open
  • a footer is displayed for confirmation / closing

🧠 Type safety

The component uses a discriminated union based on the multiple flag.

Single select

multiple?: false
value: T | null
onChange: (value: T) => void

Multi select

multiple: true
value: readonly T[]
onChange: (value: T[]) => void

TypeScript will enforce the correct contract automatically.


⚠️ Type inference when value is null

In most cases, you do not need to explicitly pass the generic type to Select.

TypeScript automatically infers the item type from the options (and/or value) prop:

<Select
  options={users}
  value={selectedUser}
  ...
/>

However, if you use null as the value and your options are not strongly typed (for example, inline array literals or an empty array), TypeScript may not be able to correctly infer the generic type.

In that case, pass the type explicitly to the component:

<Select
  options={users}
  value={null}
  ...
/>

This helps TypeScript resolve the correct type and prevents inference-related errors.


🔎 Search

Search is enabled by default.

<Select isSearchable searchPlaceholder="Search users..." />

Disable search:

<Select isSearchable={false} />

🎨 Custom input UI

<Select
  ...
  renderInput={(label) => (
    <MyCustomInput value={label ?? "Select user"} />
  )}
/>

When renderInput is provided, the default input UI is not rendered.


🧩 Custom option row

<Select
  ...
  renderOption={(item, selected) => (
    <View style={{ flexDirection: "row" }}>
      <Text>{item.name}</Text>
      {selected && <CheckIcon />}
    </View>
  )}
/>

🧾 Multi-select footer

A default footer button is shown for multi-select.

You can fully replace it:

<Select
  multiple
  ...
  renderFooter={({ selected, confirm }) => (
    <MyButton
      title={`Apply (${selected.length})`}
      onPress={confirm}
    />
  )}
/>

Footer render context:

{
  selected: readonly T[]
  confirm: () => void
  close: () => void
}

🎨 Styling the default UI

<Select
  ...
  containerStyle={{ marginTop: 12 }}
  inputStyle={{ borderColor: "#4f46e5" }}
  optionStyle={{ paddingHorizontal: 20 }}
  optionTextStyle={{ fontSize: 14 }}
/>

These style props affect only the default UI. They are ignored when you use renderInput or renderOption.


⚙️ Bottom sheet height

<Select snapPoints={["50%", "80%"]} />

Default:

["60%"];

📚 Props reference

Select<T> is fully generic and controlled.


Common props (single & multi)

| Prop | Type | Description | | ------------------- | ------------------------------------------- | --------------------------------------- | | options | readonly T[] | List of selectable items | | getLabel | (item: T) => string | Returns the label shown for an item | | getKey | (item: T) => string | Returns a unique stable key for an item | | placeholder | string | Placeholder text for the default input | | disabled | boolean | Disables opening the select | | isSearchable | boolean | Enables or disables the search field | | searchPlaceholder | string | Placeholder for the search input | | renderInput | (label: string \| null) => ReactNode | Replaces the default input UI | | renderOption | (item: T, selected: boolean) => ReactNode | Replaces the default option row | | snapPoints | (string \| number)[] | Bottom sheet snap points | | containerStyle | StyleProp<ViewStyle> | Style for the pressable wrapper | | inputStyle | StyleProp<ViewStyle> | Style for the default input container | | optionStyle | StyleProp<ViewStyle> | Style for each option row | | optionTextStyle | StyleProp<TextStyle> | Style for the default option text | | confirmText | string | Label for the default footer button | | renderFooter | (ctx) => ReactNode | Custom footer renderer for multi-select |


Single select only

| Prop | Type | Description | | ---------- | -------------------- | ------------------------------- | | multiple | false \| undefined | Enables single-select mode | | value | T \| null | Selected value | | onChange | (value: T) => void | Called when a value is selected |


Multi select only

| Prop | Type | Description | | ---------- | ---------------------- | ----------------------------- | | multiple | true | Enables multi-select mode | | value | readonly T[] | Selected values | | onChange | (value: T[]) => void | Called when selection changes |


⚠️ Important behavioral notes

  • This component is fully controlled.
  • It does not store selection state internally.
  • In multi-select mode, selections are applied immediately.
  • The footer is intended for UX confirmation and closing only.

🧱 Design philosophy

This component intentionally avoids enforcing a { label, value } model.

Instead, you provide:

getLabel(item);
getKey(item);

This allows the component to work directly with domain models such as:

  • users
  • products
  • roles
  • tags
  • countries
  • CRM / ERP entities

without intermediate mapping layers.


🧑‍💻 Contributing

Contributions are welcome.


Local development

npm install
npm run build

To test the package inside a React Native app:

npm install ../react-native-modern-select

(Using a local file install is recommended instead of npm link for React Native projects.)


Contribution guidelines

  • Keep the public API backward compatible

  • Do not introduce hard dependencies on navigation, forms or state libraries

  • Do not bundle native dependencies

  • All new features must be fully typed

  • Prefer extending the existing headless API:

    • renderInput
    • renderOption
    • renderFooter
  • Keep default UI minimal and unopinionated


🐞 Bug reports & feature requests

Please include:

  • React Native version
  • iOS / Android
  • minimal reproduction steps

📄 License

MIT


🙌 Credits

Built on top of:

  • @gorhom/bottom-sheet
  • react-native-reanimated
  • react-native-gesture-handler