React Native Guidelines
npx machina-cli add skill YoniChechik/claude-code-config/react-native-guidelines --openclawReact Native Component Guidelines
This skill provides conventions for React Native/Expo mobile development.
React Compiler
- React Compiler is enabled and handles memoization automatically
- NEVER use
useMemo,useCallback, orReact.memo - The compiler optimizes re-renders without manual intervention
- Just write plain functions and let the compiler optimize
Directory Structure
apps/mobile/src/
├── components/
│ ├── ui/ # Design system primitives (Button, Input, Card, etc.)
│ │ └── [ComponentName]/
│ │ ├── [ComponentName].tsx
│ │ ├── [ComponentName].test.tsx
│ │ ├── [ComponentName].stories.tsx
│ │ └── index.ts
│ └── common/ # Shared business components (Avatar, MessageBubble, etc.)
├── features/ # Feature-specific code
│ └── [feature-name]/
│ ├── components/
│ ├── hooks/
│ └── screens/
├── hooks/ # App-wide shared hooks
├── screens/ # Top-level screens (navigation entry points)
└── lib/ # Utilities, constants, types
Component Structure
Every component must be a folder with:
ComponentName.tsx- ImplementationComponentName.test.tsx- Unit tests (required)ComponentName.stories.tsx- Storybook story (required for ui/common)index.ts- Barrel export
Component Pattern
import { View, Text, Pressable } from 'react-native';
export interface ButtonProps {
label: string;
onPress: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
export function Button({ label, onPress, variant = 'primary', disabled = false }: ButtonProps) {
return (
<Pressable
className={`px-4 py-2 rounded-lg ${variant === 'primary' ? 'bg-accent' : 'bg-background'} ${disabled ? 'opacity-50' : ''}`}
onPress={onPress}
disabled={disabled}
>
<Text className={`text-center font-medium ${variant === 'primary' ? 'text-background' : 'text-foreground'}`}>
{label}
</Text>
</Pressable>
);
}
Barrel File (index.ts)
export { Button } from './Button';
export type { ButtonProps } from './Button';
Rules
- Named exports only (no default exports)
- Props interface named
[ComponentName]Propsand exported - Keep components focused on presentation; extract logic to hooks
Styling
Use uniwind (NativeWind) classes only. StyleSheet.create() is FORBIDDEN.
| Rule | Status |
|---|---|
| Use uniwind className prop | Required |
| Inline classNames | Required |
| Use theme colors | Required |
| StyleSheet.create() | Forbidden |
| Inline style prop | Forbidden (except dynamic values) |
| Hardcoded color values | Forbidden |
Theme Colors
Colors must come from theme in global.css:
| Class | Usage |
|---|---|
bg-background / text-background | Main background |
bg-foreground / text-foreground | Main text |
bg-accent / text-accent | Accent/highlight |
Never use hardcoded colors like bg-white, text-black, bg-blue-500.
Correct
<View className="flex-1 bg-background p-4">
<Text className="text-lg font-bold text-foreground">Title</Text>
</View>
Incorrect
// Hardcoded colors
<View className="bg-white">
// StyleSheet.create
const styles = StyleSheet.create({ container: { flex: 1 } });
// Inline style
<View style={{ flex: 1, backgroundColor: 'white' }} />
Hooks
| Hook Type | Location |
|---|---|
| Component-specific | Inside component folder |
| Feature-shared | features/[feature]/hooks/ |
| App-wide | hooks/ |
Pattern
export interface UseAuthReturn {
user: User | null;
login: (email: string, password: string) => Promise<void>;
isLoading: boolean;
}
export function useAuth(): UseAuthReturn {
// implementation
}
Screens
- Orchestrate components, no business logic
- Data fetching in hooks, not screens
- NO Storybook stories (use Maestro E2E)
Testing
Unit Tests (Required)
import { render, screen, fireEvent } from '@testing-library/react-native';
import { Button } from './Button';
describe('Button', () => {
it('renders label correctly', () => {
render(<Button label="Click me" onPress={() => {}} />);
expect(screen.getByText('Click me')).toBeOnTheScreen();
});
it('calls onPress when pressed', () => {
const onPress = jest.fn();
render(<Button label="Click me" onPress={onPress} />);
fireEvent.press(screen.getByText('Click me'));
expect(onPress).toHaveBeenCalledTimes(1);
});
});
E2E Tests (Maestro)
# .maestro/flows/auth/login.yaml
appId: com.sunsay.attune
---
- launchApp
- tapOn: "Email"
- inputText: "test@example.com"
- tapOn: "Sign In"
- assertVisible: "Welcome"
Storybook
Required for ui/ and common/ components:
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'ui/Button',
component: Button,
args: {
label: 'Button',
onPress: () => console.log('pressed'),
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = { args: { variant: 'primary' } };
export const Disabled: Story = { args: { disabled: true } };
Naming Conventions
| Item | Convention | Example |
|---|---|---|
| Component folder | PascalCase | Button/ |
| Component file | PascalCase | Button.tsx |
| Hook file | camelCase | useAuth.ts |
| Feature folder | kebab-case | features/user-profile/ |
Checklist
- Component in correct location
- All required files present
- Props interface exported
- Styled with uniwind only
- Theme colors only (no hardcoded)
- Unit tests cover main functionality
- Storybook story (for ui/common)
- testID for interactive elements
Source
git clone https://github.com/YoniChechik/claude-code-config/blob/main/skills/react-native-guidelines/SKILL.mdView on GitHub Overview
Provides conventions for React Native/Expo mobile development, covering component structure, styling with NativeWind, hooks, testing, and Storybook. It enforces a folder-first architecture, named exports, and theme-driven styling to keep code consistent, scalable, and testable across apps.
How This Skill Works
Enforces a strict folder structure: apps/mobile/src/components/ui, common, features, hooks, screens, and lib. Each component lives in its own folder with ComponentName.tsx, ComponentName.test.tsx, ComponentName.stories.tsx, and index.ts, with barrel exports. Styling uses NativeWind className props exclusively and pulls colors from the theme in global.css; StyleSheet.create and hardcoded colors are forbidden. The React Compiler handles memoization automatically, so manual useMemo/useCallback/React.memo are avoided.
When to Use It
- Starting a new UI component with consistent styling and behavior
- Adding reusable UI primitives (Button, Input, Avatar) in ui/common
- Implementing a feature-specific component with its own hooks
- Preparing components for Storybook documentation and visual QA
- Enforcing mobile app conventions in a new feature area
Quick Start
- Step 1: Create the component folder and add ComponentName.tsx, ComponentName.test.tsx, ComponentName.stories.tsx, and index.ts
- Step 2: Implement UI using NativeWind className props, and apply colors from global.css theme
- Step 3: Export the component via a named export, define [ComponentName]Props, and add tests and a Storybook story
Best Practices
- Create a dedicated component folder with ComponentName.tsx, ComponentName.test.tsx, ComponentName.stories.tsx, and index.ts
- Use NativeWind className props exclusively; avoid StyleSheet.create() and inline styles except for dynamic values
- Always export named components and Props as [ComponentName]Props
- Keep components presentation-focused; move business logic to hooks located in the appropriate folder
- Provide unit tests and a Storybook story for every ui/common component
Example Use Cases
- Button component with variant prop (primary/secondary) using theme colors
- Avatar or MessageBubble components under apps/mobile/src/components/common
- UI primitives under apps/mobile/src/components/ui (Button, Card, Input)
- Storybook stories for a Button and a Card to document visuals
- Feature-level hooks placed under features/[feature-name]/hooks and reused by components