skills/react-native/SKILL.md
React Native patterns for mobile app development with Expo and bare workflow. Trigger: When building mobile apps, working with React Native components, using Expo, React Navigation, or NativeWind.
npx skillsauth add fearovex/claude-config react-nativeInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Triggers: When building mobile apps, working with React Native components, using Expo, React Navigation, or NativeWind.
Load this skill when:
src/
├── app/ # Expo Router screens
│ ├── (tabs)/ # Tab navigator group
│ ├── (auth)/ # Auth flow group
│ └── _layout.tsx # Root layout
├── components/
│ ├── ui/ # Reusable UI components
│ └── features/ # Feature-specific components
├── hooks/ # Custom hooks
├── services/ # API and external services
├── stores/ # State management (Zustand)
├── utils/ # Utility functions
├── constants/ # App constants, themes
└── types/ # TypeScript types
import { View, Text, Pressable } from 'react-native';
interface ButtonProps {
title: string;
onPress: () => void;
variant?: 'primary' | 'secondary';
disabled?: boolean;
}
export function Button({
title,
onPress,
variant = 'primary',
disabled = false
}: ButtonProps) {
return (
<Pressable
onPress={onPress}
disabled={disabled}
style={({ pressed }) => [
styles.button,
variant === 'secondary' && styles.buttonSecondary,
pressed && styles.buttonPressed,
disabled && styles.buttonDisabled,
]}
>
<Text style={styles.buttonText}>{title}</Text>
</Pressable>
);
}
import { Platform, StyleSheet } from 'react-native';
const styles = StyleSheet.create({
container: {
paddingTop: Platform.select({ ios: 44, android: 0 }),
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
android: { elevation: 4 },
}),
},
});
// Or use file extensions:
// Component.ios.tsx
// Component.android.tsx
// app/_layout.tsx
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
export default function RootLayout() {
return (
<>
<StatusBar style="auto" />
<Stack screenOptions={{ headerShown: false, animation: 'slide_from_right' }}>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="modal"
options={{ presentation: 'modal', animation: 'slide_from_bottom' }}
/>
</Stack>
</>
);
}
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { userService } from '@/services/user';
export function useUser(userId: string) {
return useQuery({
queryKey: ['user', userId],
queryFn: () => userService.getById(userId),
staleTime: 5 * 60 * 1000,
});
}
export function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: UpdateUserInput) => userService.update(data),
onSuccess: (_, variables) => {
queryClient.invalidateQueries({ queryKey: ['user', variables.id] });
},
});
}
import { KeyboardAvoidingView, Platform } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
export function ScreenWrapper({ children }: { children: React.ReactNode }) {
return (
<SafeAreaView style={{ flex: 1 }} edges={['top', 'left', 'right']}>
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : 20}
>
{children}
</KeyboardAvoidingView>
</SafeAreaView>
);
}
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface AuthState {
token: string | null;
user: User | null;
isAuthenticated: boolean;
login: (token: string, user: User) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
token: null,
user: null,
isAuthenticated: false,
login: (token, user) => set({ token, user, isAuthenticated: true }),
logout: () => set({ token: null, user: null, isAuthenticated: false }),
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
// ❌ Bad
<View style={{ flex: 1, padding: 16, backgroundColor: '#fff' }}>
// ✅ Good - StyleSheet or NativeWind
const styles = StyleSheet.create({
container: { flex: 1, padding: 16, backgroundColor: '#fff' },
});
// ❌ Bad
import { TouchableOpacity } from 'react-native';
// ✅ Good
import { Pressable } from 'react-native';
<Pressable
onPress={onPress}
style={({ pressed }) => [styles.button, pressed && { opacity: 0.7 }]}
>
// ❌ Bad
const { data } = useUser(userId);
return <Text>{data.name}</Text>; // crash if undefined
// ✅ Good
const { data, isLoading, error } = useUser(userId);
if (isLoading) return <LoadingSpinner />;
if (error) return <ErrorMessage error={error} />;
if (!data) return null;
return <Text>{data.name}</Text>;
| Task | Pattern |
|------|---------|
| New Expo project | npx create-expo-app@latest --template tabs |
| Add NativeWind | npx expo install nativewind tailwindcss |
| Platform check | Platform.OS === 'ios' |
| Safe insets | useSafeAreaInsets() |
| Navigation | router.push('/screen') (Expo Router) |
| Icons | @expo/vector-icons |
| Animations | react-native-reanimated |
| Gestures | react-native-gesture-handler |
useState to simulate navigation stacksclassName prop — never mix NativeWind with inline style objects on the same componentPlatform.select() or .ios.tsx/.android.tsx file extensions; never use Platform.OS checks inline in JSXbusiness
Turns an already-investigated customer issue into a short, non-technical engineering-to-CS brief: one natural message that leads with the finding (root cause, real scope, open question), ready to paste into Slack for the support team. Trigger: /support-brief, support brief, brief for support, resumen soporte.
development
Parks the current Claude Code session before going to sleep. Analyzes the conversation, writes a handoff document to docs/handoffs/ in the current project, mirrors the same summary to engram tagged with the session ID, and prints the exact `claude --resume <id>` command for tomorrow. Zero interaction — runs end-to-end on a single invocation. Trigger: /night-park, night park, park session, me voy a dormir, guardar sesion.
testing
Interactive creator for a project feature: scaffolds the domain knowledge markdown at ai-context/features/<slug>.md AND the antenna skill at .claude/skills/<slug>/SKILL.md, both from the canonical templates. Also registers the antenna in the project's CLAUDE.md. Trigger: /feature-define <name>, define feature, documentar funcionalidad, nueva feature.
data-ai
Generates a short, non-technical, informal English summary of an already-investigated customer issue, ready to paste into Slack/email for support, CX, or ops teammates. Trigger: /customer-summary, customer summary, resumir customer issue, slack summary.