Agent skill
react-native-component-generator
Generates React Native components with platform-specific code, TypeScript support, and mobile-first patterns. Use when user asks to "create React Native component", "generate RN component", "scaffold mobile component", or "create cross-platform component".
Install this agent skill to your Project
npx add-skill https://github.com/Dexploarer/claudius-skills/tree/main/examples/intermediate/mobile-skills/react-native-component-generator
SKILL.md
React Native Component Generator
Generates React Native components with platform-specific optimizations, TypeScript support, and mobile best practices.
When to Use
- "Create a React Native Button component"
- "Generate mobile component"
- "Scaffold cross-platform component"
- "Create RN component with platform code"
- "Generate native module wrapper"
Instructions
1. Gather Requirements
Ask for:
- Component name (PascalCase)
- Platform support (iOS, Android, or both)
- Props and their types
- Native modules needed
- Styling approach (StyleSheet, styled-components, etc.)
- Animations needed
2. Generate Base Component
Basic Functional Component:
// components/Button/Button.tsx
import React from 'react';
import {
TouchableOpacity,
Text,
StyleSheet,
TouchableOpacityProps,
ViewStyle,
TextStyle,
} from 'react-native';
interface ButtonProps extends TouchableOpacityProps {
title: string;
onPress: () => void;
variant?: 'primary' | 'secondary' | 'outline';
size?: 'small' | 'medium' | 'large';
}
export const Button: React.FC<ButtonProps> = ({
title,
onPress,
variant = 'primary',
size = 'medium',
style,
...props
}) => {
return (
<TouchableOpacity
style={[
styles.button,
styles[variant],
styles[size],
style,
]}
onPress={onPress}
activeOpacity={0.7}
{...props}
>
<Text style={[styles.text, styles[`${variant}Text`]]}>
{title}
</Text>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
button: {
borderRadius: 8,
justifyContent: 'center',
alignItems: 'center',
},
primary: {
backgroundColor: '#007AFF',
},
secondary: {
backgroundColor: '#6c757d',
},
outline: {
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: '#007AFF',
},
small: {
paddingVertical: 8,
paddingHorizontal: 16,
},
medium: {
paddingVertical: 12,
paddingHorizontal: 24,
},
large: {
paddingVertical: 16,
paddingHorizontal: 32,
},
text: {
fontSize: 16,
fontWeight: '600',
},
primaryText: {
color: '#ffffff',
},
secondaryText: {
color: '#ffffff',
},
outlineText: {
color: '#007AFF',
},
});
Component with State and Hooks:
// components/SearchInput/SearchInput.tsx
import React, { useState, useCallback, useEffect } from 'react';
import {
TextInput,
View,
StyleSheet,
TextInputProps,
TouchableOpacity,
ActivityIndicator,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
interface SearchInputProps extends Omit<TextInputProps, 'onChangeText'> {
onSearch: (query: string) => void;
onClear?: () => void;
debounceMs?: number;
loading?: boolean;
}
export const SearchInput: React.FC<SearchInputProps> = ({
onSearch,
onClear,
debounceMs = 300,
loading = false,
placeholder = 'Search...',
...props
}) => {
const [query, setQuery] = useState('');
useEffect(() => {
const timer = setTimeout(() => {
if (query) {
onSearch(query);
}
}, debounceMs);
return () => clearTimeout(timer);
}, [query, debounceMs, onSearch]);
const handleClear = useCallback(() => {
setQuery('');
onClear?.();
}, [onClear]);
return (
<View style={styles.container}>
<Ionicons name="search" size={20} color="#666" style={styles.icon} />
<TextInput
style={styles.input}
value={query}
onChangeText={setQuery}
placeholder={placeholder}
placeholderTextColor="#999"
autoCapitalize="none"
autoCorrect={false}
{...props}
/>
{loading && <ActivityIndicator size="small" color="#007AFF" />}
{query.length > 0 && !loading && (
<TouchableOpacity onPress={handleClear}>
<Ionicons name="close-circle" size={20} color="#666" />
</TouchableOpacity>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#f0f0f0',
borderRadius: 10,
paddingHorizontal: 12,
height: 44,
},
icon: {
marginRight: 8,
},
input: {
flex: 1,
fontSize: 16,
color: '#000',
},
});
3. Platform-Specific Code
Using Platform Module:
// components/Header/Header.tsx
import React from 'react';
import { View, Text, StyleSheet, Platform, StatusBar } from 'react-native';
interface HeaderProps {
title: string;
}
export const Header: React.FC<HeaderProps> = ({ title }) => {
return (
<View style={styles.container}>
<Text style={styles.title}>{title}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
paddingTop: Platform.select({
ios: 50,
android: StatusBar.currentHeight || 0,
default: 0,
}),
paddingBottom: 10,
paddingHorizontal: 16,
backgroundColor: '#007AFF',
...Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
android: {
elevation: 4,
},
}),
},
title: {
fontSize: 20,
fontWeight: 'bold',
color: '#fff',
...Platform.select({
ios: {
fontFamily: 'System',
},
android: {
fontFamily: 'Roboto',
},
}),
},
});
Using Platform-Specific Files:
// components/DatePicker/DatePicker.ios.tsx
import React from 'react';
import { DatePickerIOS } from '@react-native-community/datetimepicker';
interface DatePickerProps {
date: Date;
onDateChange: (date: Date) => void;
}
export const DatePicker: React.FC<DatePickerProps> = ({ date, onDateChange }) => {
return <DatePickerIOS date={date} onDateChange={onDateChange} mode="date" />;
};
// components/DatePicker/DatePicker.android.tsx
import React, { useState } from 'react';
import { TouchableOpacity, Text, StyleSheet } from 'react-native';
import DateTimePicker from '@react-native-community/datetimepicker';
interface DatePickerProps {
date: Date;
onDateChange: (date: Date) => void;
}
export const DatePicker: React.FC<DatePickerProps> = ({ date, onDateChange }) => {
const [show, setShow] = useState(false);
const onChange = (_event: any, selectedDate?: Date) => {
setShow(false);
if (selectedDate) {
onDateChange(selectedDate);
}
};
return (
<>
<TouchableOpacity onPress={() => setShow(true)} style={styles.button}>
<Text>{date.toLocaleDateString()}</Text>
</TouchableOpacity>
{show && (
<DateTimePicker value={date} mode="date" display="default" onChange={onChange} />
)}
</>
);
};
const styles = StyleSheet.create({
button: {
padding: 12,
backgroundColor: '#f0f0f0',
borderRadius: 8,
},
});
// components/DatePicker/index.ts
export { DatePicker } from './DatePicker';
4. Animated Components
Basic Animation:
// components/FadeIn/FadeIn.tsx
import React, { useEffect, useRef } from 'react';
import { Animated, ViewProps } from 'react-native';
interface FadeInProps extends ViewProps {
duration?: number;
delay?: number;
}
export const FadeIn: React.FC<FadeInProps> = ({
children,
duration = 300,
delay = 0,
style,
...props
}) => {
const fadeAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration,
delay,
useNativeDriver: true,
}).start();
}, [fadeAnim, duration, delay]);
return (
<Animated.View style={[style, { opacity: fadeAnim }]} {...props}>
{children}
</Animated.View>
);
};
Complex Animation:
// components/SlideUp/SlideUp.tsx
import React, { useEffect, useRef } from 'react';
import { Animated, Dimensions } from 'react-native';
const { height } = Dimensions.get('window');
interface SlideUpProps {
visible: boolean;
onClose: () => void;
children: React.ReactNode;
}
export const SlideUp: React.FC<SlideUpProps> = ({ visible, onClose, children }) => {
const slideAnim = useRef(new Animated.Value(height)).current;
useEffect(() => {
if (visible) {
Animated.spring(slideAnim, {
toValue: 0,
useNativeDriver: true,
tension: 65,
friction: 11,
}).start();
} else {
Animated.timing(slideAnim, {
toValue: height,
duration: 250,
useNativeDriver: true,
}).start();
}
}, [visible, slideAnim]);
if (!visible) return null;
return (
<Animated.View
style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
backgroundColor: '#fff',
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
padding: 20,
transform: [{ translateY: slideAnim }],
}}
>
{children}
</Animated.View>
);
};
5. List Components
Optimized FlatList:
// components/UserList/UserList.tsx
import React, { useCallback } from 'react';
import {
FlatList,
View,
Text,
StyleSheet,
ActivityIndicator,
RefreshControl,
} from 'react-native';
interface User {
id: string;
name: string;
email: string;
}
interface UserListProps {
users: User[];
loading?: boolean;
onRefresh?: () => void;
onEndReached?: () => void;
refreshing?: boolean;
}
export const UserList: React.FC<UserListProps> = ({
users,
loading,
onRefresh,
onEndReached,
refreshing = false,
}) => {
const renderItem = useCallback(({ item }: { item: User }) => (
<View style={styles.item}>
<Text style={styles.name}>{item.name}</Text>
<Text style={styles.email}>{item.email}</Text>
</View>
), []);
const keyExtractor = useCallback((item: User) => item.id, []);
const renderFooter = useCallback(() => {
if (!loading) return null;
return (
<View style={styles.footer}>
<ActivityIndicator size="small" color="#007AFF" />
</View>
);
}, [loading]);
const renderEmpty = useCallback(() => (
<View style={styles.empty}>
<Text style={styles.emptyText}>No users found</Text>
</View>
), []);
return (
<FlatList
data={users}
renderItem={renderItem}
keyExtractor={keyExtractor}
ListFooterComponent={renderFooter}
ListEmptyComponent={renderEmpty}
onEndReached={onEndReached}
onEndReachedThreshold={0.5}
refreshControl={
onRefresh ? (
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
) : undefined
}
removeClippedSubviews={true}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
initialNumToRender={10}
windowSize={5}
/>
);
};
const styles = StyleSheet.create({
item: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
name: {
fontSize: 16,
fontWeight: '600',
marginBottom: 4,
},
email: {
fontSize: 14,
color: '#666',
},
footer: {
paddingVertical: 20,
alignItems: 'center',
},
empty: {
padding: 40,
alignItems: 'center',
},
emptyText: {
fontSize: 16,
color: '#999',
},
});
6. Form Components
Input with Validation:
// components/TextInputField/TextInputField.tsx
import React, { useState } from 'react';
import { View, TextInput, Text, StyleSheet, TextInputProps } from 'react-native';
interface TextInputFieldProps extends TextInputProps {
label: string;
error?: string;
required?: boolean;
validate?: (value: string) => string | undefined;
}
export const TextInputField: React.FC<TextInputFieldProps> = ({
label,
error,
required,
validate,
onBlur,
onChangeText,
...props
}) => {
const [localError, setLocalError] = useState<string>();
const handleBlur = (e: any) => {
if (validate && props.value) {
setLocalError(validate(props.value));
}
onBlur?.(e);
};
const handleChange = (text: string) => {
setLocalError(undefined);
onChangeText?.(text);
};
const displayError = error || localError;
return (
<View style={styles.container}>
<Text style={styles.label}>
{label}
{required && <Text style={styles.required}> *</Text>}
</Text>
<TextInput
style={[styles.input, displayError && styles.inputError]}
onBlur={handleBlur}
onChangeText={handleChange}
{...props}
/>
{displayError && <Text style={styles.errorText}>{displayError}</Text>}
</View>
);
};
const styles = StyleSheet.create({
container: {
marginBottom: 16,
},
label: {
fontSize: 14,
fontWeight: '600',
marginBottom: 8,
color: '#333',
},
required: {
color: '#ff0000',
},
input: {
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 12,
fontSize: 16,
backgroundColor: '#fff',
},
inputError: {
borderColor: '#ff0000',
},
errorText: {
color: '#ff0000',
fontSize: 12,
marginTop: 4,
},
});
7. Native Module Integration
Using Native Modules:
// components/BiometricAuth/BiometricAuth.tsx
import React from 'react';
import { Alert, Platform } from 'react-native';
import TouchID from 'react-native-touch-id';
import ReactNativeBiometrics from 'react-native-biometrics';
interface BiometricAuthProps {
onSuccess: () => void;
onError: (error: string) => void;
}
export const useBiometricAuth = ({ onSuccess, onError }: BiometricAuthProps) => {
const authenticate = async () => {
try {
if (Platform.OS === 'ios') {
const isSupported = await TouchID.isSupported();
if (!isSupported) {
onError('Biometric authentication not supported');
return;
}
await TouchID.authenticate('Authenticate to continue', {
fallbackLabel: 'Use Passcode',
});
onSuccess();
} else {
const rnBiometrics = new ReactNativeBiometrics();
const { available } = await rnBiometrics.isSensorAvailable();
if (!available) {
onError('Biometric authentication not available');
return;
}
const { success } = await rnBiometrics.simplePrompt({
promptMessage: 'Authenticate to continue',
});
if (success) {
onSuccess();
} else {
onError('Authentication failed');
}
}
} catch (error: any) {
onError(error.message);
}
};
return { authenticate };
};
8. Responsive Components
Using Dimensions:
// components/ResponsiveCard/ResponsiveCard.tsx
import React from 'react';
import { View, Text, StyleSheet, Dimensions, ScaledSize } from 'react-native';
import { useDeviceOrientation } from '@react-native-community/hooks';
const getStyles = (window: ScaledSize) => {
const { width } = window;
const isTablet = width >= 768;
return StyleSheet.create({
card: {
padding: isTablet ? 24 : 16,
margin: isTablet ? 16 : 8,
backgroundColor: '#fff',
borderRadius: isTablet ? 12 : 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3,
},
title: {
fontSize: isTablet ? 24 : 18,
fontWeight: 'bold',
marginBottom: isTablet ? 12 : 8,
},
});
};
export const ResponsiveCard: React.FC<{ title: string; children: React.ReactNode }> = ({
title,
children,
}) => {
const window = Dimensions.get('window');
const styles = getStyles(window);
return (
<View style={styles.card}>
<Text style={styles.title}>{title}</Text>
{children}
</View>
);
};
9. Index File
Create barrel export:
// components/index.ts
export { Button } from './Button/Button';
export { SearchInput } from './SearchInput/SearchInput';
export { Header } from './Header/Header';
export { DatePicker } from './DatePicker';
export { FadeIn } from './FadeIn/FadeIn';
export { SlideUp } from './SlideUp/SlideUp';
export { UserList } from './UserList/UserList';
export { TextInputField } from './TextInputField/TextInputField';
export { ResponsiveCard } from './ResponsiveCard/ResponsiveCard';
10. Tests
Component Test:
// components/Button/__tests__/Button.test.tsx
import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import { Button } from '../Button';
describe('Button', () => {
it('renders correctly', () => {
const { getByText } = render(<Button title="Click me" onPress={() => {}} />);
expect(getByText('Click me')).toBeTruthy();
});
it('calls onPress when pressed', () => {
const onPress = jest.fn();
const { getByText } = render(<Button title="Click me" onPress={onPress} />);
fireEvent.press(getByText('Click me'));
expect(onPress).toHaveBeenCalledTimes(1);
});
it('applies correct variant styles', () => {
const { getByText, rerender } = render(
<Button title="Primary" onPress={() => {}} variant="primary" />
);
let button = getByText('Primary').parent;
expect(button?.props.style).toContainEqual(
expect.objectContaining({ backgroundColor: '#007AFF' })
);
rerender(<Button title="Outline" onPress={() => {}} variant="outline" />);
button = getByText('Outline').parent;
expect(button?.props.style).toContainEqual(
expect.objectContaining({ backgroundColor: 'transparent' })
);
});
});
Best Practices
DO:
- Use TypeScript for type safety
- Optimize FlatList performance
- Use Platform-specific code when needed
- Implement proper keyboard handling
- Use useCallback for list renders
- Test on both platforms
- Follow platform design guidelines
DON'T:
- Use inline styles in render
- Forget to use useNativeDriver for animations
- Ignore safe area insets
- Over-render components
- Use ScrollView for long lists
- Hardcode dimensions
- Ignore accessibility
Checklist
- Component created with TypeScript
- Props interface defined
- Platform-specific code handled
- Styles optimized with StyleSheet
- Accessibility props added
- Tests written
- Index file updated
- Performance optimized
- Documentation added
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
smart-contract-generator
Generates Solidity smart contracts with security best practices (ERC-20, ERC-721, ERC-1155, custom). Use when user asks to "create smart contract", "solidity contract", "erc20 token", "nft contract", or "web3 contract".
threejs-scene-builder
Comprehensive Three.js and React Three Fiber skill for creating 3D scenes, characters, NPCs, procedural generation, animation retargeting, and interactive experiences. Use when user asks to "create Three.js scene", "setup React Three Fiber", "add 3D character", "create NPC AI", "procedural 3D generation", "retarget animation", "setup avatar system", or "create 3D game".
wcag-compliance-checker
Checks websites for WCAG 2.1 Level AA compliance, identifies accessibility violations, and provides remediation guidance. Use when user asks to "check accessibility", "wcag compliance", "a11y audit", "accessibility violations", or "screen reader testing".
kubernetes-manifest-generator
Generates Kubernetes manifests (Deployments, Services, Ingress, ConfigMaps, Secrets) with best practices for production workloads. Use when user asks to "create k8s manifest", "generate Kubernetes deployment", "setup k8s service", or "create Kubernetes resources".
terraform-module-builder
Generates reusable Terraform modules with best practices for AWS, Azure, GCP infrastructure as code. Use when user asks to "create Terraform module", "generate IaC module", "setup Terraform", or "create infrastructure module".
translation-key-extractor
Extracts hardcoded strings from code and converts them to translation keys for i18n. Use when user asks to "extract translations", "find hardcoded strings", "internationalize code", "setup i18n", or "create translation files".
Didn't find tool you were looking for?