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

@alex8203/rn-components

v0.2.0

Published

A lightweight, native React Native component library with dark/light theme support

Readme

rn-components

A lightweight React Native component library with dark/light theme support, built with TypeScript strict mode and native platform best practices.

  • ✅ iOS & Android native feel (Pressable, android_ripple, Platform.select)
  • ✅ Dark / Light / System theme via ThemeProvider
  • ✅ Optional react-hook-form integration
  • ✅ Fully typed — strict: true, zero any
  • ✅ Tree-shakeable — CJS + ESM builds

Installation

npm install @alex8203/rn-components
# or
pnpm add @alex8203/rn-components
# or
bun add @alex8203/rn-components

Peer dependencies

npm install react react-native
# react-hook-form is optional
npm install react-hook-form

Setup

Wrap your app with ThemeProvider. That's it.

import { ThemeProvider } from 'rn-components';

export default function App() {
  return (
    <ThemeProvider mode="system">
      {/* your app */}
    </ThemeProvider>
  );
}

| mode | Description | |--------|-------------| | "system" | Follows device dark/light setting (default) | | "light" | Always light | | "dark" | Always dark |

Custom theme

import { ThemeProvider } from 'rn-components';
import type { PartialTheme } from 'rn-components';

const myTheme: PartialTheme = {
  colors: {
    primary: '#6366F1',
    secondary: '#8B5CF6',
  },
  radii: {
    md: 12,
  },
};

<ThemeProvider mode="system" theme={myTheme}>
  {/* your app */}
</ThemeProvider>

useTheme

import { useTheme } from 'rn-components';

function MyComponent() {
  const { theme, mode } = useTheme();
  return <View style={{ backgroundColor: theme.colors.background }} />;
}

Components

Button

import { Button } from 'rn-components';

<Button label="Save" variant="primary" onPress={handleSave} />
<Button label="Delete" variant="destructive" />
<Button label="Loading..." loading />
<Button label="Full width" fullWidth />

| Prop | Type | Default | |------|------|---------| | label | string | required | | variant | primary \| secondary \| outline \| ghost \| destructive | primary | | size | sm \| md \| lg | md | | loading | boolean | false | | disabled | boolean | false | | fullWidth | boolean | false | | leftIcon | ReactNode | — | | rightIcon | ReactNode | — |


IconButton

import { IconButton } from 'rn-components';
import { Ionicons } from '@expo/vector-icons';

<IconButton
  icon={<Ionicons name="heart" size={20} color="#fff" />}
  accessibilityLabel="Like"
  variant="primary"
/>

| Prop | Type | Default | |------|------|---------| | icon | ReactNode | required | | accessibilityLabel | string | required | | variant | same as Button | primary | | size | sm \| md \| lg | md |


Text

import { Text } from 'rn-components';

<Text variant="h1">Heading 1</Text>
<Text variant="body" color="#666">Paragraph</Text>

| variant | Usage | |-----------|-------| | h1 h2 h3 | Headings | | body bodySmall | Paragraphs | | label | Form labels | | caption | Helper / hint text | | overline | Section labels (uppercase) |


TextInput

import { TextInput } from 'rn-components';

<TextInput
  label="Email"
  placeholder="[email protected]"
  value={email}
  onChangeText={setEmail}
  helperText="We'll never share your email"
  keyboardType="email-address"
  autoCapitalize="none"
/>

// With error
<TextInput
  label="Username"
  value={username}
  onChangeText={setUsername}
  errorMessage="Username already taken"
/>

With react-hook-form

import { useForm } from 'react-hook-form';
import { TextInput } from 'rn-components';

const { control } = useForm();

<TextInput
  label="Email"
  name="email"
  control={control}
  rules={{ required: 'Required', pattern: { value: /\S+@\S+/, message: 'Invalid email' } }}
/>

PasswordInput

import { PasswordInput } from 'rn-components';

// Built-in eye icon
<PasswordInput label="Password" value={password} onChangeText={setPassword} />

// Custom icon (e.g. Ionicons)
<PasswordInput
  label="Password"
  value={password}
  onChangeText={setPassword}
  renderEyeIcon={({ visible, color }) => (
    <Ionicons name={visible ? 'eye' : 'eye-off'} size={20} color={color} />
  )}
/>

Checkbox

import { Checkbox } from 'rn-components';

<Checkbox
  state={checked}
  label="Accept terms and conditions"
  onChange={(isChecked) => setChecked(isChecked ? 'checked' : 'unchecked')}
/>

| state | checked \| unchecked \| indeterminate | |---------|-----------------------------------------|


Switch

import { Switch } from 'rn-components';

<Switch label="Notifications" value={enabled} onValueChange={setEnabled} />

Uses React Native's native Switch — looks different on iOS/Android intentionally. For react-hook-form, wrap with <Controller>.


Select

import { Select } from 'rn-components';

<Select
  label="Country"
  placeholder="Select a country"
  value={country}
  onValueChange={setCountry}
  options={[
    { label: 'Cuba', value: 'cu' },
    { label: 'Mexico', value: 'mx' },
    { label: 'Spain', value: 'es' },
  ]}
/>

SearchSelect

Like Select but with a search input to filter options.

import { SearchSelect } from 'rn-components';
import { Ionicons } from '@expo/vector-icons';

<SearchSelect
  label="Country"
  options={countries}
  value={country}
  onValueChange={setCountry}
  searchIcon={<Ionicons name="search" size={16} color="#999" style={{ marginRight: 8 }} />}
  clearIcon={<Ionicons name="close-circle" size={16} color="#999" />}
/>

OTPInput

import { OTPInput } from 'rn-components';

<OTPInput
  length={6}
  value={code}
  onChangeText={setCode}
  onComplete={(value) => verifyCode(value)}
/>

Avatar

import { Avatar } from 'rn-components';

// Image with fallback to initials
<Avatar uri="https://example.com/photo.jpg" name="John Doe" size="md" />

// Initials only
<Avatar name="Ana García" size="lg" backgroundColor="#5856D6" />

| size | Dimensions | |--------|-----------| | sm | 32px | | md | 40px | | lg | 56px | | xl | 72px |


Badge

import { Badge } from 'rn-components';

<Badge label="New" variant="success" />
<Badge label="3" variant="error" size="sm" />

| variant | info \| success \| warning \| error \| neutral |


Card

import { Card } from 'rn-components';

<Card variant="elevated">
  <Text variant="label">Title</Text>
  <Text variant="caption">Subtitle</Text>
</Card>

| variant | Description | |-----------|-------------| | elevated | Shadow (iOS) / elevation (Android) | | outlined | Border, no shadow | | filled | Surface background |


Divider

import { Divider } from 'rn-components';

<Divider />
<Divider orientation="vertical" style={{ height: 24 }} />

Spinner

import { Spinner } from 'rn-components';

<Spinner size="md" />
<Spinner size="lg" color="#34C759" />

Modal

import { Modal } from 'rn-components';

<Modal visible={open} onClose={() => setOpen(false)}>
  <Text variant="h3">Confirm action</Text>
  <Button label="Confirm" onPress={handleConfirm} />
</Modal>

Toast

Wrap your app with ToastProvider and use the useToast hook anywhere.

// App.tsx
import { ToastProvider } from 'rn-components';

<ThemeProvider mode="system">
  <ToastProvider>
    <App />
  </ToastProvider>
</ThemeProvider>
// Any component
import { useToast } from 'rn-components';

function MyComponent() {
  const { show } = useToast();

  return (
    <Button
      label="Save"
      onPress={() => show({ message: 'Saved!', variant: 'success' })}
    />
  );
}

| Option | Type | Default | |--------|------|---------| | message | string | required | | variant | info \| success \| warning \| error | info | | position | top \| bottom | bottom | | duration | number (ms) | 3000 |


SafeAreaView

import { SafeAreaView } from 'rn-components';

<SafeAreaView>
  {/* content */}
</SafeAreaView>

Handles safe area insets on both iOS and Android automatically.


KeyboardAvoidingView

import { KeyboardAvoidingView } from 'rn-components';

<SafeAreaView>
  <KeyboardAvoidingView>
    <ScrollView>
      <TextInput label="Email" ... />
      <PasswordInput label="Password" ... />
    </ScrollView>
  </KeyboardAvoidingView>
</SafeAreaView>

Uses behavior="padding" on iOS and behavior="height" on Android automatically.


react-hook-form

TextInput, PasswordInput, and Checkbox have built-in RHF support via name + control props.

For Switch and Select, use RHF's <Controller> directly:

import { Controller, useForm } from 'react-hook-form';
import { Switch, Select } from 'rn-components';

const { control } = useForm();

<Controller
  name="notifications"
  control={control}
  render={({ field: { value, onChange } }) => (
    <Switch label="Notifications" value={value} onValueChange={onChange} />
  )}
/>

<Controller
  name="country"
  control={control}
  render={({ field: { value, onChange } }) => (
    <Select options={countries} value={value} onValueChange={onChange} />
  )}
/>

License

MIT