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 initThis 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 buttonCLI 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 buttonimport { 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 checkboximport { 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 inputimport { 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 otpimport { 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 sliderimport { 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 configurationRoadmap
- [ ] 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.
- Fork the repo
- Create your branch:
git checkout -b feat/component-name - Add your component to
templates/andregistry/ - Open a pull request
License
MIT © Rubics UI
