ui-mobile

安装量: 369
排名: #2559

安装

npx skills add https://github.com/alinaqi/claude-bootstrap --skill ui-mobile

Mobile UI Design Skill (React Native)

Load with: base.md + react-native.md

MANDATORY: Mobile Accessibility Standards

These rules are NON-NEGOTIABLE. Every UI element must pass these checks.

  1. Touch Targets (CRITICAL) // MINIMUM 44x44 points for ALL interactive elements const MINIMUM_TOUCH_SIZE = 44;

// EVERY button, link, icon button must meet this const styles = StyleSheet.create({ button: { minHeight: MINIMUM_TOUCH_SIZE, minWidth: MINIMUM_TOUCH_SIZE, paddingVertical: 12, paddingHorizontal: 16, }, iconButton: { width: MINIMUM_TOUCH_SIZE, height: MINIMUM_TOUCH_SIZE, justifyContent: 'center', alignItems: 'center', }, });

// NEVER DO THIS: style={{ height: 30 }} // ✗ TOO SMALL style={{ padding: 4 }} // ✗ RESULTS IN TINY TARGET

  1. Color Contrast (CRITICAL) // WCAG 2.1 AA: 4.5:1 for text, 3:1 for large text/UI

// SAFE COMBINATIONS: const colors = { // Light mode textPrimary: '#000000', // on white = 21:1 ✓ textSecondary: '#374151', // gray-700 on white = 9.2:1 ✓

// Dark mode textPrimaryDark: '#FFFFFF', // on gray-900 = 16:1 ✓ textSecondaryDark: '#E5E7EB', // gray-200 on gray-900 = 11:1 ✓ };

// FORBIDDEN - FAILS CONTRAST: // ✗ '#9CA3AF' (gray-400) on white = 2.6:1 // ✗ '#6B7280' (gray-500) on '#111827' = 4.0:1 // ✗ Any text below 4.5:1 ratio

  1. Visibility Rules // ALL BUTTONS MUST HAVE visible boundaries

// PRIMARY: Solid background with contrasting text Submit

const styles = StyleSheet.create({ primaryButton: { backgroundColor: '#1F2937', // gray-800 paddingVertical: 16, paddingHorizontal: 24, borderRadius: 12, minHeight: 44, }, });

// SECONDARY: Visible background Cancel

const styles = StyleSheet.create({ secondaryButton: { backgroundColor: '#F3F4F6', // gray-100 minHeight: 44, }, });

// GHOST: MUST have visible border Skip

const styles = StyleSheet.create({ ghostButton: { borderWidth: 1, borderColor: '#D1D5DB', // gray-300 minHeight: 44, }, });

// NEVER CREATE invisible buttons: // ✗ backgroundColor: 'transparent' without border // ✗ Text color matching background

  1. Accessibility Labels (REQUIRED) // EVERY interactive element needs accessibility props

// Buttons <Pressable accessible={true} accessibilityRole="button" accessibilityLabel="Submit form" accessibilityHint="Double tap to submit your information"

Submit

// Icon buttons (NO visible text = MUST have label) <Pressable accessible={true} accessibilityRole="button" accessibilityLabel="Close menu"

// Images

  1. Focus/Selection States // EVERY Pressable needs visible pressed state [ styles.button, pressed && styles.buttonPressed, ]}

    {children}

const styles = StyleSheet.create({ button: { backgroundColor: '#1F2937', }, buttonPressed: { opacity: 0.7, // OR backgroundColor: '#374151', }, });

Core Philosophy

Mobile UI is about touch, speed, and focus. No hover states, smaller screens, thumb-friendly targets. Design for one-handed use and interruption recovery.

Platform Differences iOS vs Android import { Platform } from 'react-native';

// Platform-specific values const styles = StyleSheet.create({ shadow: Platform.select({ ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 8, }, android: { elevation: 4, }, }),

// iOS uses SF Pro, Android uses Roboto text: { fontFamily: Platform.OS === 'ios' ? 'System' : 'Roboto', }, });

Design Language iOS (Human Interface Guidelines) ───────────────────────────────── - Flat design with subtle depth - SF Symbols for icons - Large titles (34pt) - Rounded corners (10-14pt) - Blue as default tint

Android (Material Design 3) ───────────────────────────────── - Material You dynamic color - Outlined/filled icons - Medium titles (22pt) - Rounded corners (12-28pt) - Primary color from theme

Spacing System 4px Base Grid // React Native spacing - consistent scale const spacing = { xs: 4, sm: 8, md: 16, lg: 24, xl: 32, '2xl': 48, } as const;

// Usage const styles = StyleSheet.create({ container: { padding: spacing.md, gap: spacing.sm, }, });

Safe Areas import { useSafeAreaInsets } from 'react-native-safe-area-context';

const Screen = ({ children }) => { const insets = useSafeAreaInsets();

return ( {children} ); };

Typography Type Scale const typography = { // Large titles (iOS style) largeTitle: { fontSize: 34, fontWeight: '700' as const, letterSpacing: 0.37, },

// Section headers title: { fontSize: 22, fontWeight: '700' as const, letterSpacing: 0.35, },

// Card titles headline: { fontSize: 17, fontWeight: '600' as const, letterSpacing: -0.41, },

// Body text body: { fontSize: 17, fontWeight: '400' as const, letterSpacing: -0.41, lineHeight: 22, },

// Secondary text callout: { fontSize: 16, fontWeight: '400' as const, letterSpacing: -0.32, },

// Small labels caption: { fontSize: 12, fontWeight: '400' as const, letterSpacing: 0, }, };

Color System Semantic Colors // Use semantic names, not literal colors const colors = { // Backgrounds background: '#FFFFFF', backgroundSecondary: '#F2F2F7', backgroundTertiary: '#FFFFFF',

// Surfaces surface: '#FFFFFF', surfaceElevated: '#FFFFFF',

// Text label: '#000000', labelSecondary: '#3C3C43', // 60% opacity labelTertiary: '#3C3C43', // 30% opacity

// Actions primary: '#007AFF', destructive: '#FF3B30', success: '#34C759', warning: '#FF9500',

// Separators separator: '#3C3C43', // 29% opacity opaqueSeparator: '#C6C6C8', };

// Dark mode variants const darkColors = { background: '#000000', backgroundSecondary: '#1C1C1E', label: '#FFFFFF', labelSecondary: '#EBEBF5', // 60% opacity separator: '#545458', };

Dynamic Colors (React Native) import { useColorScheme } from 'react-native';

const useColors = () => { const scheme = useColorScheme(); return scheme === 'dark' ? darkColors : colors; };

// Usage const MyComponent = () => { const colors = useColors(); return ( Hello ); };

Touch Targets Minimum Sizes // CRITICAL: Minimum 44pt touch targets const touchable = { minHeight: 44, minWidth: 44, };

// Button with proper sizing const styles = StyleSheet.create({ button: { minHeight: 44, paddingHorizontal: 16, paddingVertical: 12, justifyContent: 'center', alignItems: 'center', },

// Icon button (square) iconButton: { width: 44, height: 44, justifyContent: 'center', alignItems: 'center', },

// List row listRow: { minHeight: 44, paddingVertical: 12, paddingHorizontal: 16, }, });

Touch Feedback import { Pressable } from 'react-native';

// iOS-style opacity feedback const Button = ({ children, onPress }) => ( [ styles.button, pressed && { opacity: 0.7 }, ]}

{children}

);

// Android-style ripple const AndroidButton = ({ children, onPress }) => ( <Pressable onPress={onPress} android_ripple={{ color: 'rgba(0, 0, 0, 0.1)', borderless: false, }} style={styles.button}

{children}

);

Component Patterns Cards const Card = ({ children, style }) => ( {children} );

const styles = StyleSheet.create({ card: { backgroundColor: '#FFFFFF', borderRadius: 12, padding: 16, ...Platform.select({ ios: { shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.08, shadowRadius: 8, }, android: { elevation: 2, }, }), }, });

Buttons // Primary button const PrimaryButton = ({ title, onPress, disabled }) => ( [ styles.primaryButton, pressed && styles.primaryButtonPressed, disabled && styles.buttonDisabled, ]}

<Text style={styles.primaryButtonText}>{title}</Text>

);

const styles = StyleSheet.create({ primaryButton: { backgroundColor: '#007AFF', borderRadius: 12, paddingVertical: 16, paddingHorizontal: 24, alignItems: 'center', }, primaryButtonPressed: { backgroundColor: '#0056B3', }, primaryButtonText: { color: '#FFFFFF', fontSize: 17, fontWeight: '600', }, buttonDisabled: { opacity: 0.5, }, });

// Secondary button const SecondaryButton = ({ title, onPress }) => ( [ styles.secondaryButton, pressed && { opacity: 0.7 }, ]}

<Text style={styles.secondaryButtonText}>{title}</Text>

);

Input Fields const TextField = ({ label, value, onChangeText, error }) => { const [focused, setFocused] = useState(false);

return ( {label && ( {label} )} setFocused(true)} onBlur={() => setFocused(false)} style={[ styles.textField, focused && styles.textFieldFocused, error && styles.textFieldError, ]} placeholderTextColor="#8E8E93" /> {error && ( {error} )} ); };

const styles = StyleSheet.create({ textFieldContainer: { gap: 8, }, textFieldLabel: { fontSize: 15, fontWeight: '500', color: '#3C3C43', }, textField: { backgroundColor: '#F2F2F7', borderRadius: 10, paddingHorizontal: 16, paddingVertical: 14, fontSize: 17, color: '#000000', borderWidth: 2, borderColor: 'transparent', }, textFieldFocused: { borderColor: '#007AFF', backgroundColor: '#FFFFFF', }, textFieldError: { borderColor: '#FF3B30', }, errorText: { fontSize: 13, color: '#FF3B30', }, });

Lists // Grouped list (iOS Settings style) const GroupedList = ({ sections }) => ( {sections.map((section, i) => ( {section.title && ( {section.title} )} {section.items.map((item, j) => ( {j > 0 && } [ styles.listRow, pressed && { backgroundColor: '#E5E5EA' }, ]} onPress={item.onPress} > {item.title} ))} ))} );

const styles = StyleSheet.create({ groupedList: { flex: 1, backgroundColor: '#F2F2F7', }, section: { marginTop: 35, }, sectionHeader: { fontSize: 13, fontWeight: '400', color: '#6D6D72', textTransform: 'uppercase', marginLeft: 16, marginBottom: 8, }, sectionContent: { backgroundColor: '#FFFFFF', borderRadius: 10, marginHorizontal: 16, overflow: 'hidden', }, listRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingVertical: 12, paddingHorizontal: 16, minHeight: 44, }, separator: { height: StyleSheet.hairlineWidth, backgroundColor: '#C6C6C8', marginLeft: 16, }, });

Navigation Patterns Bottom Tab Bar // Proper bottom tab sizing const tabBarStyle = { height: Platform.OS === 'ios' ? 83 : 65, // Account for home indicator paddingBottom: Platform.OS === 'ios' ? 34 : 10, paddingTop: 10, backgroundColor: '#F8F8F8', borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: '#C6C6C8', };

// Tab item const TabItem = ({ icon, label, active }) => ( {label} );

Header // Large title header (iOS) const LargeTitleHeader = ({ title, rightAction }) => { const insets = useSafeAreaInsets();

return ( {title} {rightAction} ); };

const styles = StyleSheet.create({ header: { backgroundColor: '#F8F8F8', borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: '#C6C6C8', }, headerContent: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: 16, paddingBottom: 8, }, largeTitle: { fontSize: 34, fontWeight: '700', letterSpacing: 0.37, }, });

Animations Native Driver Animations import { Animated } from 'react-native';

// Always use native driver when possible const fadeIn = (value: Animated.Value) => { Animated.timing(value, { toValue: 1, duration: 200, useNativeDriver: true, // CRITICAL for performance }).start(); };

// Spring for natural feel const bounce = (value: Animated.Value) => { Animated.spring(value, { toValue: 1, damping: 15, stiffness: 150, useNativeDriver: true, }).start(); };

Reanimated for Complex Animations import Animated, { useSharedValue, useAnimatedStyle, withSpring, } from 'react-native-reanimated';

const AnimatedCard = ({ children }) => { const scale = useSharedValue(1);

const animatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: scale.value }], }));

const onPressIn = () => { scale.value = withSpring(0.95); };

const onPressOut = () => { scale.value = withSpring(1); };

return ( {children} ); };

Loading States Skeleton Loader const SkeletonLoader = ({ width, height, borderRadius = 4 }) => { const opacity = useSharedValue(0.3);

useEffect(() => { opacity.value = withRepeat( withSequence( withTiming(1, { duration: 500 }), withTiming(0.3, { duration: 500 }) ), -1, false ); }, []);

const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value, }));

return ( ); };

Activity Indicator import { ActivityIndicator } from 'react-native';

// Use platform-native indicator

// Button with loading state const LoadingButton = ({ loading, title, onPress }) => ( <Pressable onPress={onPress} disabled={loading} style={styles.button}

{loading ? (
  <ActivityIndicator color="#FFFFFF" />
) : (
  <Text style={styles.buttonText}>{title}</Text>
)}

);

Accessibility VoiceOver / TalkBack // Accessible button <Pressable onPress={onPress} accessible={true} accessibilityRole="button" accessibilityLabel="Submit form" accessibilityHint="Double tap to submit your information"

Submit

// Accessible image

// Group related elements <View accessible={true} accessibilityRole="summary" accessibilityLabel={${name}, ${role}, ${status}}

{name} {role} {status}

Dynamic Type (iOS) import { PixelRatio } from 'react-native';

// Scale fonts with system settings const fontScale = PixelRatio.getFontScale(); const scaledFontSize = (size: number) => size * fontScale;

// Or use allowFontScaling This text scales with system settings

Anti-Patterns Never Do ✗ Touch targets smaller than 44pt ✗ Text smaller than 12pt ✗ Hover states (no hover on mobile) ✗ Fixed heights that break with large text ✗ Ignoring safe areas ✗ Heavy shadows on Android (use elevation) ✗ White text on light backgrounds without checking contrast ✗ Non-native animations (JS-driven transforms) ✗ Ignoring platform conventions (iOS vs Android) ✗ Inline styles everywhere (use StyleSheet.create)

Common Mistakes // ✗ Hardcoded dimensions that break accessibility style={{ height: 40 }} // Text might be larger

// ✓ Minimum height with padding style={{ minHeight: 44, paddingVertical: 12 }}

// ✗ Shadow on Android shadowColor: '#000' // Won't work

// ✓ Platform-specific ...Platform.select({ ios: { shadowColor: '#000', ... }, android: { elevation: 4 }, })

// ✗ Fixed status bar height paddingTop: 44

// ✓ Use safe area paddingTop: insets.top

Quick Reference Mobile Defaults Touch targets: 44pt minimum Font sizes: 12pt min, 17pt body, 34pt large title Border radius: 10-14pt (iOS), 12-28pt (Android) Spacing: 4/8/16/24/32 grid Animations: 200-300ms, native driver Shadow: iOS shadowOpacity 0.08-0.15, Android elevation 2-8

Premium Feel Checklist □ All touch targets 44pt+ □ Consistent spacing (4pt grid) □ Platform-appropriate styling □ Safe area handling □ Native animations (60fps) □ Proper loading states □ Dark mode support □ Accessibility labels □ Haptic feedback on actions □ Pull-to-refresh where appropriate

返回排行榜