# 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 (