# Mobile Interface Guidelines - Complete Ruleset
**Purpose:** Touch, gesture, animation, layout, accessibility, and performance rules for React Native/Expo.
---
## How to Use This Document
When generating mobile applications, Claude MUST follow these rules. During Ralph QA, compliance with these rules contributes to the Mobile UI Skills category (5% of total score).
**Priority Levels:**
- **CRITICAL** - Must pass or build fails
- **HIGH** - Should pass; failures reduce score significantly
- **MEDIUM** - Should pass; failures reduce score moderately
- **LOW** - Nice to have; minor score impact
---
## 1. Touch & Gestures
### TOU-1: Touch Targets (CRITICAL)
Interactive elements must be at least 44pt (iOS) / 48dp (Android).
```tsx
// GOOD: Adequate touch target
// GOOD: Using Pressable with hitSlop
// BAD: Too small
```
### TOU-2: Touch Feedback (HIGH)
Provide visual feedback on touch.
```tsx
// GOOD: Opacity feedback
Press me
// GOOD: Highlight feedback
Press me
// GOOD: Pressable with style function
[
styles.button,
pressed && styles.buttonPressed,
]}
>
Press me
```
### TOU-3: Haptic Feedback (MEDIUM)
Use haptic feedback for important interactions.
```tsx
import * as Haptics from 'expo-haptics';
// GOOD: Haptic on success
const handlePurchase = async () => {
await completePurchase();
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
};
// GOOD: Haptic on selection
const handleSelect = (item) => {
Haptics.selectionAsync();
setSelected(item);
};
// GOOD: Impact feedback on button press
{
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
handleAction();
}}
>
```
### TOU-4: Swipe Gestures (MEDIUM)
Implement standard swipe gestures where appropriate.
```tsx
import { Swipeable } from 'react-native-gesture-handler';
// GOOD: Swipe to delete
(
)}
onSwipeableRightOpen={handleDelete}
>
;
```
### TOU-5: Pull to Refresh (HIGH)
Refreshable content should support pull-to-refresh.
```tsx
// GOOD: Pull to refresh
}
/>
// BAD: Manual refresh button only
```
### TOU-6: Scroll Indicators (LOW)
Show scroll indicators for scrollable content.
```tsx
// GOOD: Scroll indicator visible
{content}
// In long lists, indicator helps orientation
```
---
## 2. Animation
### ANI-1: Reduced Motion (CRITICAL)
Respect user's reduced motion preferences.
```tsx
import { useReducedMotion } from 'react-native-reanimated';
// GOOD: Check motion preference
function AnimatedCard({ children }) {
const reducedMotion = useReducedMotion();
const animatedStyle = useAnimatedStyle(() => ({
transform: reducedMotion ? [] : [{ scale: withSpring(isPressed ? 0.95 : 1) }],
}));
return {children};
}
// GOOD: AccessibilityInfo API
import { AccessibilityInfo } from 'react-native';
const [reduceMotionEnabled, setReduceMotionEnabled] = useState(false);
useEffect(() => {
AccessibilityInfo.isReduceMotionEnabled().then(setReduceMotionEnabled);
const subscription = AccessibilityInfo.addEventListener('reduceMotionChanged', setReduceMotionEnabled);
return () => subscription.remove();
}, []);
```
### ANI-2: Native Driver (HIGH)
Use native driver for animations when possible.
```tsx
import Animated, { useNativeDriver } from 'react-native-reanimated';
// GOOD: Native driver animation
const fadeAnim = useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => ({
opacity: fadeAnim.value,
transform: [{ translateY: (1 - fadeAnim.value) * 20 }],
}));
// GOOD: Animated API with native driver
Animated.timing(fadeAnim, {
toValue: 1,
duration: 300,
useNativeDriver: true, // Only for transform and opacity
}).start();
// BAD: Animating layout properties
Animated.timing(heightAnim, {
toValue: 100,
useNativeDriver: true, // Will crash - height not supported
}).start();
```
### ANI-3: Screen Transitions (MEDIUM)
Use smooth transitions between screens.
```tsx
// GOOD: Custom transition in Expo Router
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function Layout() {
return (
);
}
// GOOD: Modal presentation
;
```
### ANI-4: Loading Animations (MEDIUM)
Use subtle loading animations.
```tsx
// GOOD: Skeleton pulse animation
function Skeleton({ width, height }) {
const opacity = useSharedValue(0.3);
useEffect(() => {
opacity.value = withRepeat(
withSequence(withTiming(0.7, { duration: 800 }), withTiming(0.3, { duration: 800 })),
-1,
false
);
}, []);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
backgroundColor: '#E5E7EB',
width,
height,
borderRadius: 4,
}));
return ;
}
```
### ANI-5: Gesture-Driven Animation (LOW)
Animations should respond to gesture input.
```tsx
// GOOD: Gesture-driven card
function SwipeableCard() {
const translateX = useSharedValue(0);
const gesture = Gesture.Pan()
.onUpdate((e) => {
translateX.value = e.translationX;
})
.onEnd((e) => {
if (Math.abs(e.translationX) > 100) {
translateX.value = withTiming(e.translationX > 0 ? 300 : -300);
} else {
translateX.value = withSpring(0);
}
});
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: translateX.value }],
}));
return (
);
}
```
---
## 3. Layout
### LAY-1: Safe Areas (CRITICAL)
Respect device safe areas (notch, home indicator, status bar).
```tsx
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';
// GOOD: SafeAreaView wrapper
function Screen({ children }) {
return (
{children}
);
}
// GOOD: Manual inset handling
function CustomHeader() {
const insets = useSafeAreaInsets();
return (
Header
);
}
// GOOD: Bottom tab bar
function TabBar() {
const insets = useSafeAreaInsets();
return {/* Tab items */};
}
// BAD: Ignoring safe areas
{/* Content blocked by notch */};
```
### LAY-2: Platform Conventions (HIGH)
Follow platform-specific UI conventions.
```tsx
import { Platform } from 'react-native';
// GOOD: Platform-specific back button
function Header({ title, onBack }) {
return (
{Platform.OS === 'ios' ? (
Back
) : (
)}
{title}
);
}
// GOOD: Platform-specific styling
const styles = StyleSheet.create({
shadow: Platform.select({
ios: {
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
android: {
elevation: 4,
},
}),
});
```
### LAY-3: Keyboard Avoidance (HIGH)
Handle keyboard appearance properly.
```tsx
import { KeyboardAvoidingView, Platform } from 'react-native';
// GOOD: Keyboard avoiding form
function LoginForm() {
return (
);
}
// GOOD: Dismiss keyboard on tap outside
import { Keyboard, TouchableWithoutFeedback } from 'react-native';
{/* Form content */}
;
```
### LAY-4: Responsive Layout (MEDIUM)
Adapt layout to different screen sizes.
```tsx
import { useWindowDimensions } from 'react-native';
// GOOD: Responsive grid
function Grid({ items }) {
const { width } = useWindowDimensions();
const numColumns = width > 600 ? 3 : 2;
return (
(
)}
/>
);
}
// GOOD: Orientation handling
function useOrientation() {
const { width, height } = useWindowDimensions();
return width > height ? 'landscape' : 'portrait';
}
```
### LAY-5: StyleSheet Usage (MEDIUM)
Use StyleSheet.create for styles.
```tsx
// GOOD: StyleSheet.create
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
},
});
// BAD: Inline styles (causes re-renders)
Title
;
```
---
## 4. Content
### CON-1: Empty States (HIGH)
Design empty states with icon, message, and action.
```tsx
// GOOD: Designed empty state
function EmptyInbox() {
return (
No messages yet
Start a conversation to see messages here
Send Message
);
}
// BAD: Plain text
No messages;
```
### CON-2: Error States (HIGH)
Error states should explain and offer recovery.
```tsx
// GOOD: Helpful error state
function ErrorState({ error, onRetry }) {
return (
Something went wrong
{error.message}
Try Again
);
}
// BAD: Generic error
Error;
```
### CON-3: Loading States (HIGH)
Use skeleton loaders, not spinners.
```tsx
// GOOD: Skeleton loader
function CardSkeleton() {
return (
);
}
// GOOD: List with skeletons
function ListLoading() {
return (
{[1, 2, 3, 4, 5].map((i) => (
))}
);
}
// BAD: Centered spinner
;
```
### CON-4: Confirmation Dialogs (MEDIUM)
Destructive actions require confirmation.
```tsx
import { Alert } from 'react-native';
// GOOD: Confirmation before delete
const handleDelete = () => {
Alert.alert('Delete Item', 'Are you sure? This cannot be undone.', [
{ text: 'Cancel', style: 'cancel' },
{
text: 'Delete',
style: 'destructive',
onPress: confirmDelete,
},
]);
};
```
### CON-5: Text Truncation (LOW)
Handle long text gracefully.
```tsx
// GOOD: Truncated text with numberOfLines
{longDescription}
;
// GOOD: Expandable text
function ExpandableText({ text, maxLines = 3 }) {
const [expanded, setExpanded] = useState(false);
return (
{text}
setExpanded(!expanded)}>
{expanded ? 'Show less' : 'Read more'}
);
}
```
---
## 5. Accessibility
### ACC-1: Accessibility Labels (CRITICAL)
All interactive elements must have accessibility labels.
```tsx
// GOOD: Proper accessibility
// GOOD: Image with description
// BAD: No accessibility
```
### ACC-2: Accessibility Roles (HIGH)
Use correct accessibility roles.
```tsx
// GOOD: Proper roles
Submit
Learn more
Section Title
```
### ACC-3: Screen Reader Announcements (HIGH)
Announce important changes to screen readers.
```tsx
import { AccessibilityInfo } from 'react-native';
// GOOD: Announce success
const handlePurchase = async () => {
await completePurchase();
AccessibilityInfo.announceForAccessibility('Purchase complete');
};
// GOOD: Announce errors
const handleError = (error) => {
setError(error);
AccessibilityInfo.announceForAccessibility(`Error: ${error.message}`);
};
```
### ACC-4: Focus Management (MEDIUM)
Manage focus for modals and navigation.
```tsx
// GOOD: Auto-focus first input
function SearchScreen() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return ;
}
```
### ACC-5: Color Contrast (HIGH)
Maintain 4.5:1 contrast ratio for text.
```tsx
// GOOD: High contrast
const styles = StyleSheet.create({
text: {
color: '#1F2937', // Dark gray on white
},
secondaryText: {
color: '#6B7280', // Medium gray, still accessible
},
});
// BAD: Low contrast
const badStyles = StyleSheet.create({
text: {
color: '#D1D5DB', // Light gray on white - hard to read
},
});
```
### ACC-6: Dynamic Type (MEDIUM)
Support user's preferred text size.
```tsx
import { PixelRatio } from 'react-native';
// GOOD: Scaled font sizes
const scaledFontSize = (size) => {
const scale = PixelRatio.getFontScale();
return size * scale;
};
// GOOD: Using Text's allowFontScaling
This text will scale with system settings
// For critical UI where scaling might break layout:
Tab Label
```
### ACC-7: Touch Accessibility (MEDIUM)
Group related elements for accessibility.
```tsx
// GOOD: Accessible list item
{item.title}
{item.subtitle}
{item.price}
// BAD: Each element separately focusable
{item.title}
{item.subtitle}
{item.price}
```
### ACC-8: Accessibility Testing (HIGH)
Test with VoiceOver (iOS) and TalkBack (Android).
**Checklist:**
- [ ] All buttons/links announced with clear labels
- [ ] Screen reader can navigate between all elements
- [ ] Form inputs have labels read correctly
- [ ] Headings properly identified
- [ ] Images have meaningful alt text
- [ ] Dynamic changes announced
---
## 6. Performance
### PER-1: FlatList for Lists (CRITICAL)
Lists with more than 20 items must use FlatList.
```tsx
// GOOD: FlatList for performance
}
keyExtractor={(item) => item.id}
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={5}
/>
// GOOD: SectionList for grouped data
}
renderSectionHeader={({ section }) => }
keyExtractor={(item) => item.id}
/>
// BAD: ScrollView with many items
{items.map(item => )}
```
### PER-2: Memory Cleanup (CRITICAL)
Clean up subscriptions and listeners in useEffect.
```tsx
// GOOD: Cleanup in useEffect
useEffect(() => {
const subscription = eventEmitter.addListener('event', handler);
return () => {
subscription.remove();
};
}, []);
// GOOD: Abort controller for fetch
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal }).then(handleResponse).catch(handleError);
return () => controller.abort();
}, [url]);
// BAD: No cleanup
useEffect(() => {
eventEmitter.addListener('event', handler);
}, []);
```
### PER-3: Image Optimization (HIGH)
Optimize images for mobile.
```tsx
import { Image } from 'expo-image';
// GOOD: Optimized image loading
// GOOD: Cached images
```
### PER-4: Avoid Main Thread Work (HIGH)
Keep heavy computation off the main thread.
```tsx
// GOOD: Defer expensive work
import { InteractionManager } from 'react-native';
useEffect(() => {
InteractionManager.runAfterInteractions(() => {
// Heavy computation after animations complete
processData(data);
});
}, [data]);
// GOOD: Use worklets for animation calculations
const animatedStyle = useAnimatedStyle(() => {
// Runs on UI thread, not JS thread
return {
transform: [{ rotate: `${rotation.value}deg` }],
};
});
// BAD: Heavy computation in render
function Component({ data }) {
const processed = expensiveOperation(data); // Blocks render
return {/* ... */};
}
```
### PER-5: Memoization (MEDIUM)
Use memo and useCallback appropriately.
```tsx
// GOOD: Memoized list item
const ListItem = memo(function ListItem({ item, onPress }) {
return (
onPress(item.id)}>
{item.title}
);
});
// GOOD: Stable callback
const handlePress = useCallback(
(id) => {
navigation.navigate('Detail', { id });
},
[navigation]
);
// GOOD: Memoized computation
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => a.title.localeCompare(b.title));
}, [items]);
```
### PER-6: Lazy Loading (MEDIUM)
Lazy load screens and heavy components.
```tsx
// GOOD: Lazy loaded screen in Expo Router
// This happens automatically with file-based routing
// GOOD: Deferred component loading
const HeavyChart = lazy(() => import('./HeavyChart'));
function Dashboard() {
return (
}>
);
}
```
---
## Compliance Scoring
During Ralph QA, these rules are checked and scored:
| Category | Weight | Items |
| ---------------- | ------ | ------- |
| Touch & Gestures | 20% | 6 rules |
| Animation | 15% | 5 rules |
| Layout | 20% | 5 rules |
| Content | 15% | 5 rules |
| Accessibility | 20% | 8 rules |
| Performance | 20% | 6 rules |
**Pass Threshold:** 95% of HIGH/CRITICAL rules, 80% of MEDIUM rules
**Automatic Failure:** Any CRITICAL rule violation (touch targets, safe areas, FlatList, memory cleanup, accessibility labels, reduced motion)