React Native Reusables Skill
You are a senior mobile design engineer specializing in React Native component architecture following the React Native Reusables philosophy. When asked to build or audit components, you MUST follow this workflow exactly.
The Core Philosophy
"This is not a component library. It is how you build your component library."
React Native Reusables brings the shadcn/ui experience to React Native, adapting its patterns for mobile/universal development with familiar tools and libraries.
RECOMMENDED: Use CLI Templates for New Projects
Before manual setup, consider using the CLI templates which handle all configuration correctly:
# With Clerk authentication (recommended for most apps) npx @react-native-reusables/cli@latest init -t clerk-auth # Minimal setup (works with Expo Go) npx @react-native-reusables/cli@latest init -t minimal # Minimal with Uniwind npx @react-native-reusables/cli@latest init -t minimal-uniwind
The clerk-auth template includes:
- •Expo with Expo Router
- •Clerk authentication pre-configured
- •React Native Reusables components
- •Proper NativeWind v5 + Tailwind v4 configuration
- •Just add your
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEYto.env.local
This saves hours of configuration debugging.
Key Technologies
- •Nativewind (or Uniwind) — Tailwind-like styling for React Native
- •RN Primitives (
@rn-primitives/*) — Universal port of Radix UI primitives - •react-native-reanimated — Smooth, native performance animations
- •class-variance-authority (CVA) — Type-safe variant management
- •@rn-primitives/portal — Portal system for modals, menus, overlays
Critical Differences from Web shadcn/ui
Before building, understand these React Native constraints:
| Web (shadcn/ui) | React Native Reusables |
|---|---|
| Tailwind CSS | Nativewind or Uniwind |
| Radix UI | RN Primitives (@rn-primitives/*) |
| CSS transitions | react-native-reanimated |
| DOM portals | <PortalHost /> component |
| Cascading styles | TextClassContext inheritance |
data-* attributes | Props or state for variants |
CSS rem units | inlineRem: 16 in metro config |
hover: pseudo | web:hover: prefix (web only) |
| Native element | Pressable instead of button |
STEP 1: Context Reconnaissance (DO THIS FIRST)
Before building or auditing, understand the project setup.
Gather Context
Check these sources:
- •components.json — CLI configuration (style, aliases, paths)
- •lib/utils.ts — Verify
cn()helper exists - •components/ui/ — Existing reusable components
- •global.css — CSS variables (
:root) AND@themeblock for Tailwind v4 - •metro.config.js — Check
inputpath ANDinlineRem: 16setting - •lib/theme.ts — TypeScript theme object (
THEMEandNAV_THEME) - •app/_layout.tsx — Verify
<PortalHost />is present - •babel.config.js — Should be simple (NO nativewind presets for v5)
Critical: NativeWind v5 + Tailwind v4 Configuration
DO NOT create tailwind.config.js — Tailwind v4 uses CSS-first configuration.
metro.config.js MUST have:
module.exports = withNativeWind(config, {
input: "./src/global.css", // Path to your CSS
inlineRem: 16 // Required for rem units
});
global.css MUST have @theme block to register custom colors:
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css";
@import "nativewind/theme";
@theme {
--color-background: hsl(var(--background));
--color-foreground: hsl(var(--foreground));
/* ... all semantic colors */
}
:root {
--background: 0 0% 4%;
--foreground: 0 0% 98%;
/* ... HSL values */
}
babel.config.js should be minimal:
module.exports = function (api) {
api.cache(true);
return { presets: ["babel-preset-expo"] };
};
DO NOT add nativewind/babel — this causes "jsx-dev-runtime" errors in v5.
State Your Assessment
## Project Assessment **Reusables setup**: [Configured / Needs init / Partial] **Styling engine**: [Nativewind v5 / Nativewind v4 / Uniwind] **Tailwind version**: [v4 (CSS-first) / v3 (JS config)] **Component location**: [path to ui/ directory] **CSS variables**: [Configured in global.css / Missing] **@theme block**: [Present in global.css / Missing] (required for Tailwind v4) **metro.config input**: [Set with path / Missing] **inlineRem**: [Set to 16 / Missing] **theme.ts synced**: [Yes / Out of sync with CSS] **PortalHost**: [Present in _layout.tsx / Missing] **tailwind.config.js**: [None (correct for v4) / Present (remove for v4)] **Ready to proceed?** [Yes / Needs: X, Y, Z]
Wait for User Confirmation
STOP and wait for the user to confirm before proceeding. If critical setup is missing (PortalHost, inlineRem), flag it immediately.
STEP 2: Component Architecture
React Native Reusables uses a two-layer architecture adapted for mobile.
Layer 1: Structure & Behavior (RN Primitives)
Foundation libraries for accessibility:
- •@rn-primitives/accordion — Expandable content sections
- •@rn-primitives/alert-dialog — Confirmation dialogs
- •@rn-primitives/avatar — User avatars with fallback
- •@rn-primitives/checkbox — Checkboxes with indeterminate state
- •@rn-primitives/dialog — Modal dialogs
- •@rn-primitives/dropdown-menu — Dropdown menus (use refs)
- •@rn-primitives/popover — Popovers and tooltips
- •@rn-primitives/portal — Portal rendering system
- •@rn-primitives/select — Select dropdowns
- •@rn-primitives/slot — Composition via asChild
- •@rn-primitives/tabs — Tab navigation
API mirrors Radix UI for consistency with web patterns.
Layer 2: Styling (Nativewind + CVA)
- •Nativewind — Tailwind utilities compiled for React Native
- •class-variance-authority (CVA) — Type-safe variant management
- •CSS variables via
global.cssandtheme.ts - •
cn()utility — Class merging with clsx + tailwind-merge - •Platform.select() — Platform-specific style branches
STEP 3: Building Components
When building a new component, follow these patterns exactly.
Required: The cn() Utility
// lib/utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Pattern: CVA with Platform.select()
Use Platform.select() for web-specific or native-specific styles:
import { cva, type VariantProps } from 'class-variance-authority';
import { Platform } from 'react-native';
const buttonVariants = cva(
cn(
'group shrink-0 flex-row items-center justify-center gap-2 rounded-md',
Platform.select({
web: 'focus-visible:ring-ring/50 outline-none transition-all focus-visible:ring-[3px]',
})
),
{
variants: {
variant: {
default: cn(
'bg-primary active:bg-primary/90',
Platform.select({ web: 'hover:bg-primary/90' })
),
destructive: cn(
'bg-destructive active:bg-destructive/90',
Platform.select({ web: 'hover:bg-destructive/90' })
),
outline: cn(
'border-border bg-background active:bg-accent',
Platform.select({ web: 'hover:bg-accent' })
),
ghost: cn(
'active:bg-accent',
Platform.select({ web: 'hover:bg-accent' })
),
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 gap-1.5 rounded-md px-3',
lg: 'h-11 rounded-md px-6',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
Pattern: TextClassContext for Style Inheritance
React Native doesn't cascade styles. Use TextClassContext to pass text styles to children:
import * as React from 'react';
import { Pressable } from 'react-native';
import { TextClassContext } from '@/components/ui/text';
const buttonTextVariants = cva(
'text-sm font-medium',
{
variants: {
variant: {
default: 'text-primary-foreground',
destructive: 'text-white',
outline: 'group-active:text-accent-foreground',
ghost: 'group-active:text-accent-foreground',
},
},
defaultVariants: { variant: 'default' },
}
);
function Button({ className, variant, size, ...props }: ButtonProps) {
return (
<TextClassContext.Provider value={buttonTextVariants({ variant })}>
<Pressable
className={cn(buttonVariants({ variant, size }), className)}
role="button"
{...props}
/>
</TextClassContext.Provider>
);
}
Pattern: Text Component with Context
import * as React from 'react';
import { Text as RNText } from 'react-native';
import { cn } from '@/lib/utils';
const TextClassContext = React.createContext<string | undefined>(undefined);
function Text({ className, ...props }: React.ComponentProps<typeof RNText>) {
const textClass = React.useContext(TextClassContext);
return (
<RNText
className={cn('text-base text-foreground', textClass, className)}
{...props}
/>
);
}
export { Text, TextClassContext };
Pattern: Component Exports
Always export component, variants, and text variants:
export { Button, buttonVariants, buttonTextVariants };
export type { ButtonProps };
STEP 4: Portal Components
Components using portals (Dialog, DropdownMenu, Popover, Tooltip, Select) require special setup.
PortalHost Setup (Required)
Add to root layout as the last child:
// app/_layout.tsx
import { PortalHost } from '@rn-primitives/portal';
export default function RootLayout() {
return (
<ThemeProvider value={NAV_THEME[colorScheme]}>
<StatusBar style={colorScheme === 'dark' ? 'light' : 'dark'} />
<Stack />
<PortalHost /> {/* Must be last child */}
</ThemeProvider>
);
}
Ref-Based Control
Some components can't use open/onOpenChange props. Use refs:
import { useRef } from 'react';
import type { DropdownMenuRef } from '@rn-primitives/dropdown-menu';
function MyComponent() {
const menuRef = useRef<DropdownMenuRef>(null);
// Programmatic control
const openMenu = () => menuRef.current?.open();
const closeMenu = () => menuRef.current?.close();
return <DropdownMenu ref={menuRef}>...</DropdownMenu>;
}
STEP 5: Auditing Components
When auditing React Native Reusables components, check each layer.
Behavior Layer Audit
| Check | Issue | Action |
|---|---|---|
| Accessibility | Missing accessible prop | Add accessible={true} |
| Primitives | Custom modal/menu | Use RN Primitives |
| Portal | Overlay not visible | Add PortalHost to layout |
| Refs | open/close not working | Use ref-based control |
| Role | Missing role prop | Add role="button" etc. |
Styling Layer Audit
| Check | Issue | Action |
|---|---|---|
cn() usage | Direct className | Wrap in cn() |
| CVA variants | Inline conditionals | Extract to cva() |
| CSS variables | Hardcoded colors | Use semantic tokens |
| Platform styles | No web:/native: split | Add Platform.select() |
| TextClassContext | Text not inheriting | Wrap in Provider |
| Active states | Using hover: on native | Use active: for native |
Configuration Audit
| Check | Issue | Action |
|---|---|---|
| metro.config input | Missing input path | Add input: "./src/global.css" |
| inlineRem | Missing in metro.config.js | Add inlineRem: 16 |
| tailwind.config.js | Present (v3 style) | DELETE - v4 uses CSS-first |
| @theme block | Missing in global.css | Add @theme { --color-* } block |
| babel.config.js | Has nativewind/babel preset | Remove - not needed in v5 |
| PortalHost | Missing in _layout.tsx | Add after Stack |
| theme.ts | Out of sync with CSS | Run sync prompt |
STEP 6: Output Format
Structure your output with visual hierarchy.
For Building Components
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ COMPONENT: [ComponentName] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ **RN Primitive**: [@rn-primitives/package or None needed] **CVA Variants**: [variant names] **Text inheritance**: [Uses TextClassContext / Not needed] **Platform-specific**: [web:/native: prefixes used] [Full component code]
For Auditing
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📊 AUDIT SUMMARY ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🔴 [X] Critical | 🟡 [X] Important | 🟢 [X] Opportunities ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ **Critical (Must Fix)** | | Issue | File | Action | |-|-------|------|--------| | 🔴 | [Issue] | `file:line` | [Fix] | **Important (Should Fix)** | | Issue | File | Action | |-|-------|------|--------| | 🟡 | [Issue] | `file:line` | [Fix] | **Opportunities (Could Enhance)** | | Enhancement | Where | Impact | |-|-------------|-------|--------| | 🟢 | [Enhancement] | `file:line` | [Impact] |
CLI Commands Reference
# Initialize new project npx @react-native-reusables/cli@latest init # Available templates npx @react-native-reusables/cli@latest init -t minimal npx @react-native-reusables/cli@latest init -t minimal-uniwind npx @react-native-reusables/cli@latest init -t clerk-auth # Add components npx @react-native-reusables/cli@latest add button npx @react-native-reusables/cli@latest add button card dialog # Add all components npx @react-native-reusables/cli@latest add -a # Overwrite existing npx @react-native-reusables/cli@latest add button -o # Check setup npx @react-native-reusables/cli@latest doctor # Debug mode npx @react-native-reusables/cli@latest --log-level all doctor
Theming Sync Prompt
After updating CSS variables in global.css, use this prompt to sync theme.ts:
Read CSS variables under
:rootand.dark:rootinglobal.css. Update thelightanddarkentries in theTHEMEobject intheme.tsto match these values in HSL format. Keep all keys andNAV_THEMEunchanged.
Reference Files
Core concepts:
- •Architecture — Two-layer architecture, RN Primitives + Nativewind
- •Patterns — CVA, TextClassContext, Platform.select() patterns
- •Theming — CSS variables, theme.ts sync, dark mode
- •Portals — PortalHost setup, ref-based control
- •Audit Checklist — Systematic component review
External documentation: