AgentSkillsCN

react-native-reusables

精通 React Native Reusables 组件的构建与审核。无论您是按照 shadcn/ui 的设计理念开发、审查,还是扩展 React Native 组件,此技能都能助您事半功倍。它特别强调 Nativewind 风格化设计、RN Primitives 原生组件、TextClassContext 继承机制,以及面向平台的灵活设计模式。

SKILL.md
--- frontmatter
name: react-native-reusables
description: Expert React Native Reusables component builder and auditor. Use when building, reviewing, or extending React Native components following the shadcn/ui philosophy adapted for mobile. Emphasizes Nativewind styling, RN Primitives, TextClassContext inheritance, and platform-aware patterns.

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:

bash
# 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_KEY to .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 CSSNativewind or Uniwind
Radix UIRN Primitives (@rn-primitives/*)
CSS transitionsreact-native-reanimated
DOM portals<PortalHost /> component
Cascading stylesTextClassContext inheritance
data-* attributesProps or state for variants
CSS rem unitsinlineRem: 16 in metro config
hover: pseudoweb:hover: prefix (web only)
Native elementPressable instead of button

STEP 1: Context Reconnaissance (DO THIS FIRST)

Before building or auditing, understand the project setup.

Gather Context

Check these sources:

  1. components.json — CLI configuration (style, aliases, paths)
  2. lib/utils.ts — Verify cn() helper exists
  3. components/ui/ — Existing reusable components
  4. global.css — CSS variables (:root) AND @theme block for Tailwind v4
  5. metro.config.js — Check input path AND inlineRem: 16 setting
  6. lib/theme.ts — TypeScript theme object (THEME and NAV_THEME)
  7. app/_layout.tsx — Verify <PortalHost /> is present
  8. 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:

js
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:

css
@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:

js
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

code
## 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.css and theme.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

typescript
// 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:

typescript
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:

typescript
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

typescript
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:

typescript
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:

typescript
// 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:

typescript
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

CheckIssueAction
AccessibilityMissing accessible propAdd accessible={true}
PrimitivesCustom modal/menuUse RN Primitives
PortalOverlay not visibleAdd PortalHost to layout
Refsopen/close not workingUse ref-based control
RoleMissing role propAdd role="button" etc.

Styling Layer Audit

CheckIssueAction
cn() usageDirect classNameWrap in cn()
CVA variantsInline conditionalsExtract to cva()
CSS variablesHardcoded colorsUse semantic tokens
Platform stylesNo web:/native: splitAdd Platform.select()
TextClassContextText not inheritingWrap in Provider
Active statesUsing hover: on nativeUse active: for native

Configuration Audit

CheckIssueAction
metro.config inputMissing input pathAdd input: "./src/global.css"
inlineRemMissing in metro.config.jsAdd inlineRem: 16
tailwind.config.jsPresent (v3 style)DELETE - v4 uses CSS-first
@theme blockMissing in global.cssAdd @theme { --color-* } block
babel.config.jsHas nativewind/babel presetRemove - not needed in v5
PortalHostMissing in _layout.tsxAdd after Stack
theme.tsOut of sync with CSSRun sync prompt

STEP 6: Output Format

Structure your output with visual hierarchy.

For Building Components

code
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
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

code
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 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

bash
# 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 :root and .dark:root in global.css. Update the light and dark entries in the THEME object in theme.ts to match these values in HSL format. Keep all keys and NAV_THEME unchanged.


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: