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

rubics

v0.0.6

Published

A shadcn-style component library for React Native — copy-paste components that you own, built with TypeScript, themed with a single token system, and installed with a CLI.

Readme

Rubics UI

A shadcn-style component library for React Native — copy-paste components that you own, built with TypeScript, themed with a single token system, and installed with a CLI.

⚠️ Early Beta — built for feedback. APIs may change.


Why Rubics?

Most React Native UI libraries lock you into their styling system. Rubics works like shadcn/ui — the CLI copies components directly into your project. You own the code. Customize anything.

  • You own the code — no black box imports
  • Themed by default — every component reads from your token system
  • TypeScript first — full type safety and autocomplete
  • Dark mode built in — one toggle, every component updates

Requirements

  • React Native 0.70+ or Expo SDK 49+
  • TypeScript
  • Node.js 18+

Getting Started

1. Initialize Rubics in your project

npx rubics init

This will:

  • Create components/ui/ in your project
  • Copy the theme and utils into your project
  • Generate rubics.config.json

2. Wrap your app with ThemeProvider

// app/_layout.tsx or App.tsx
import { ThemeProvider } from './components/ui/theme/provider'

export default function RootLayout({ children }) {
  return (
    <ThemeProvider>
      {children}
    </ThemeProvider>
  )
}

3. Add your first component

rubics add button

CLI Commands

| Command | Description | |---|---| | rubics init | Set up Rubics in your project | | rubics add <component> | Add a component to your project | | rubics add <component> --force | Overwrite an existing component | | rubics list | List all available components |


Components

Button

npx rubics add button
import { Button } from './components/ui/button/button'

// Variants
<Button variant="default">Default</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Delete</Button>

// Sizes
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>

// States
<Button loading>Loading...</Button>
<Button disabled>Disabled</Button>

// With press handler
<Button onPress={() => console.log('pressed')}>
  Click Me
</Button>

| Prop | Type | Default | Description | |---|---|---|---| | variant | default outline ghost destructive | default | Visual style | | size | sm md lg | md | Button size | | loading | boolean | false | Shows spinner | | disabled | boolean | false | Disables interaction | | onPress | function | — | Press handler |


Checkbox

npx rubics add checkbox
import { Checkbox } from './components/ui/checkbox/checkbox'

// Basic
<Checkbox
  checked={checked}
  onCheckedChange={setChecked}
/>

// With label
<Checkbox
  label="Accept terms and conditions"
  checked={checked}
  onCheckedChange={setChecked}
/>

// Disabled
<Checkbox
  label="Not available"
  checked={false}
  disabled
  onCheckedChange={() => {}}
/>

| Prop | Type | Default | Description | |---|---|---|---| | checked | boolean | false | Checked state | | onCheckedChange | function | — | Fires on toggle | | label | string | — | Label text | | disabled | boolean | false | Disables interaction |


Input

npx rubics add input
import { Input } from './components/ui/input/input'

// Basic
<Input
  placeholder="Enter your email"
  value={value}
  onChangeText={setValue}
/>

// With label and error
<Input
  label="Email"
  placeholder="[email protected]"
  value={value}
  onChangeText={setValue}
  error="Please enter a valid email"
/>

// Password
<Input
  label="Password"
  placeholder="••••••••"
  secureTextEntry
  value={password}
  onChangeText={setPassword}
/>

// Disabled
<Input
  placeholder="Not editable"
  disabled
/>

| Prop | Type | Default | Description | |---|---|---|---| | label | string | — | Label above input | | error | string | — | Error message below | | disabled | boolean | false | Disables input | | placeholder | string | — | Placeholder text | | value | string | — | Controlled value | | onChangeText | function | — | Change handler |


OTP

npx rubics add otp
import { OTPInput } from './components/ui/otp/otp'

// 6-digit OTP
<OTPInput
  length={6}
  value={otp}
  onComplete={(code) => console.log('OTP:', code)}
/>

// 4-digit PIN
<OTPInput
  length={4}
  value={pin}
  onComplete={(code) => verifyPin(code)}
/>

| Prop | Type | Default | Description | |---|---|---|---| | length | number | 6 | Number of digits | | value | string | — | Controlled value | | onComplete | function | — | Fires when all digits filled | | onChangeText | function | — | Fires on every change | | disabled | boolean | false | Disables input |


Slider

npx rubics add slider
import { Slider } from './components/ui/slider/slider'

// Basic
<Slider
  value={50}
  onValueChange={(val) => console.log(val)}
/>

// With range labels
<Slider
  min={0}
  max={200}
  step={10}
  value={volume}
  startLabel="$0"
  endLabel="$200"
  onValueChange={setVolume}
/>

// Custom colors
<Slider
  value={progress}
  trackColor="#e2e8f0"
  rangeColor="#6366f1"
  thumbColor="#ffffff"
  onValueChange={setProgress}
/>

// Disabled
<Slider value={50} disabled />

| Prop | Type | Default | Description | |---|---|---|---| | min | number | 0 | Minimum value | | max | number | 100 | Maximum value | | step | number | 1 | Snap increment | | value | number | 0 | Controlled value | | onValueChange | function | — | Fires on drag | | startLabel | string | — | Left label | | endLabel | string | — | Right label | | trackColor | string | #e2e8f0 | Background track | | rangeColor | string | #0f172a | Filled range color | | thumbColor | string | #ffffff | Thumb fill color | | disabled | boolean | false | Disables interaction |


Theming

All components read from a central token system. After rubics init, your tokens live in components/ui/theme/index.ts. Edit them freely.

// components/ui/theme/index.ts

export const tokens = {
  colors: {
    primary: "#0f172a",       // ← change this to your brand color
    background: "#ffffff",
    // ...
  },
  spacing: {
    sm: 8,
    md: 12,
    lg: 16,
    // ...
  },
  radius: {
    sm: 4,
    md: 6,
    lg: 8,
    // ...
  },
}

Using the theme in your own components

import { useTheme } from './components/ui/theme/provider'

export function MyComponent() {
  const { theme, colorScheme, toggleTheme } = useTheme()

  return (
    <View style={{ backgroundColor: theme.colors.background }}>
      <Text style={{
        color: theme.colors.foreground,
        fontSize: theme.typography.fontSizes.base,
      }}>
        Hello
      </Text>
    </View>
  )
}

Dark Mode

Dark mode works automatically based on the device system setting. Toggle it manually:

const { toggleTheme, colorScheme } = useTheme()

<Button onPress={toggleTheme}>
  {colorScheme === 'dark' ? 'Light Mode' : 'Dark Mode'}
</Button>

Project Structure after init

your-app/
  components/
    ui/
      theme/
        index.ts          ← design tokens (edit these)
        provider.tsx      ← ThemeProvider + useTheme
      utils/
        utils.ts          ← cn(), shadow(), spacing()
      button/
        button.tsx
      input/
        input.tsx
      ...
  rubics.config.json      ← rubics configuration

Roadmap

  • [ ] Card
  • [ ] Badge
  • [ ] Toast
  • [ ] Modal / Bottom Sheet
  • [ ] Select / Dropdown
  • [ ] Switch
  • [ ] Avatar
  • [ ] Progress Bar
  • [ ] Tabs
  • [ ] Animations (Reanimated variants)
  • [ ] Docs website

Contributing

Rubics is in early beta and feedback is very welcome.

  1. Fork the repo
  2. Create your branch: git checkout -b feat/component-name
  3. Add your component to templates/ and registry/
  4. Open a pull request

License

MIT © Rubics UI