Back to skills
SKILL.md
Mobile Design
ASecurityUse when Mobile-first design for iOS, Android, Foldables, React Native, Flutter. Touch interaction, haptics, 120Hz performance, on-device AI, spatial UI, Reanimated 3. Use when building mobile UI, animations, or cross-platform apps.
- 5 stars
- 0 votes
- 0 copies
- 0 views
- Added September 27, 2026
Works with
Security analysis
100/100Pro scans all 2 files and shows the line behind each finding
npx -y skills add Harmitx7/tribunal-kit --skill mobile-design --agent claude-codeAre you the author of Mobile Design?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/harmitx7-mobile-design-tribunal-kit)---
name: mobile-design
description: "Use when Mobile-first design for iOS, Android, Foldables, React Native, Flutter. Touch interaction, haptics, 120Hz performance, on-device AI, spatial UI, Reanimated 3. Use when building mobile UI, animations, or cross-platform apps."
version: 5.0.0
last-updated: 2026-09-13
skills:
- building-native-ui
- adapt
- 60fps-animation
tools: Read, Grep, Glob, Bash, Edit, Write
scripts-binding:
- .agent/scripts/lint_runner.js
- .agent/scripts/verify_all.js
---
# Mobile Design β Dense Reference
---
## π οΈ Technical Architecture & Reference Recipes
---
## Hallucination Traps (Read First)
- β `Animated.View` for any animation β β
`Reanimated 3` worklets (Animated API is legacy, runs on JS thread)
- β `ScrollView` for lists β β
`FlashList` (Shopify) β ScrollView renders ALL items at once
- β `estimatedItemSize` optional in FlashList β β
**REQUIRED** or you get 0-height items
- β White backgrounds (`#FFFFFF`) β β
OLED: `#000000` true black; off-white: `#FAFAFA`
- β Linear animations (`easing: linear`) β β
Spring physics (`stiffness`, `damping`)
- β Touch targets < 48px β β
Min 48px hitbox (visual size can be smaller via padding)
- β `useAnimatedStyle` in worklet without `'worklet'` directive β crashes on native thread
- β iOS: `useSafeAreaInsets()` optional β β
Required β screen content goes under dynamic island/home indicator
- β Android: hardcoded status bar height (24dp) β β
`StatusBar.currentHeight` (varies per device)
- β Platform-specific code with `if (platform === 'ios')` scattered everywhere β β
centralize in platform/ files
- β `console.log` in production β β
blocks JS thread β remove before release
---
## React Native Performance
### FlashList (Required for Lists)
```tsx
import { FlashList } from '@shopify/flash-list';
<FlashList
data={items}
renderItem={({ item }) => <ItemCard item={item} />}
estimatedItemSize={100} // REQUIRED β measure actual item height first
keyExtractor={item => item.id}
getItemType={item => item.type} // multi-type optimization
/>;
// β NEVER: <ScrollView>{items.map(...)}</ScrollView> for lists
// β NEVER: <FlatList> for perf-critical lists β FlashList is 5-10x faster
```
### Reanimated 3 β Worklet Animations (Required for 120Hz)
```tsx
import {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
runOnJS,
} from 'react-native-reanimated';
// Shared values run on the UI thread β never on JS thread
const scale = useSharedValue(1);
const opacity = useSharedValue(0);
// Animated style β computed on UI thread (no bridge crossing)
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
opacity: opacity.value,
}));
// Triggers
const onPress = () => {
scale.value = withSpring(0.95, { stiffness: 400, damping: 15 });
opacity.value = withTiming(1, { duration: 200 });
};
// Call JS function from worklet
const onComplete = () => runOnJS(setVisible)(true);
scale.value = withSpring(1, {}, onComplete);
// β TRAP: Accessing shared value with .value inside useAnimatedStyle is fine β but inside a gesture handler callback, you need runOnJS to call React setState
```
### 120Hz Animation Rules
- β
Animate ONLY: `transform` (translateX/Y, scale, rotate), `opacity` β all GPU composited
- β Never animate: `width`, `height`, `margin`, `padding`, `top/left/bottom/right` β causes layout recalc at 60 times per second β janky, battery draining
- β
Use `withSpring` for all UI interactions (feel alive) β `withTiming` only for intentional timed animations
- β
`Gesture.Pan()` / `Gesture.Tap()` from `react-native-gesture-handler` v2 (not `PanResponder`)
---
## Haptics
```tsx
import * as Haptics from 'expo-haptics';
// light β switch toggle, tap feedback
// medium β selection change, confirm
// heavy β destructive action, strong confirm
// notificationAsync('success' | 'warning' | 'error') β operation outcomes
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); // tap, toggle
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); // save complete
// β Never overuse β haptics must mean something
// β Haptics not supported on Android emulators β test on device
```
---
## Safe Areas & Platform Layout
```tsx
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Platform, StatusBar } from 'react-native';
function Screen() {
const insets = useSafeAreaInsets();
return (
<View style={{ paddingTop: insets.top, paddingBottom: insets.bottom }}>
{/* Content safe from Dynamic Island, home indicator, status bar */}
</View>
);
}
// Android status bar
const STATUS_BAR_HEIGHT = Platform.OS === 'android' ? (StatusBar.currentHeight ?? 24) : 0;
// Foldable/tablet β dual pane
import { useWindowDimensions } from 'react-native';
function AdaptiveLayout() {
const { width } = useWindowDimensions();
const isTablet = width >= 600;
return isTablet ? <TwoPaneView /> : <SinglePaneView />;
}
```
---
## Touch Psychology & Thumb Zones
- **Thumb zone**: Bottom 40% of screen = primary actions, FABs, CTAs
- **Dead zone**: Top 25% = destructive/rare actions only
- **48px minimum hitbox**: Visual icon can be 24px, padding expands hitbox to 48px
```tsx
// Magnetic padding β visually small, touch-friendly
<TouchableOpacity style={{ padding: 12, margin: -4 }}>
<Icon size={24} />
</TouchableOpacity>
```
- **Coyote time**: Allow 100β150ms buffer after button intent registers before processing β prevents mis-taps
---
## Navigation (Expo Router / React Navigation)
```tsx
// Expo Router v3+ (file-based, recommended)
// app/(tabs)/_layout.tsx β tab navigator
// app/[id].tsx β dynamic segment
// app/(modal)/settings.tsx β modal group
// Stack navigation with gesture
import { Stack } from 'expo-router';
<Stack screenOptions={{ headerShown: false, animation: 'slide_from_right' }}>
<Stack.Screen name="(tabs)" />
<Stack.Screen name="[id]" options={{ presentation: 'modal' }} />
</Stack>;
// Deep linking (Expo Router handles automatically via app.json scheme)
// β TRAP: Don't use react-navigation Link for deep links in Expo Router β use expo-router Link
import { Link, useRouter } from 'expo-router';
const router = useRouter();
router.push('/user/42');
```
---
## On-Device AI UX Patterns
- **Zero-wait illusion**: When model runs β immediately show contextual skeleton/partial tokens
- **Progressive disclosure**: Low confidence β softer UI, soft colors, require confirmation
- **Streaming UI**: `useEffect` + SSE or `StreamingText` component appending tokens
- **Local models** (MediaPipe, Core ML, ONNX): always wrap in try/catch β device capability varies
---
## Color & Typography
```tsx
// OLED-safe dark mode
const colors = {
background: '#000000', // true black β OLED pixel off
surface: '#0A0A0A', // cards
surfaceAlt: '#121212', // elevated surfaces
border: '#1F1F1F',
text: '#FFFFFF',
textMuted: '#8E8E93', // iOS system gray
};
// Dynamic type (iOS) β always use system font with scalesWithContentSizeCategory
import { Text } from 'react-native';
<Text style={{ fontSize: 16, fontFamily: Platform.OS === 'ios' ? 'System' : 'Roboto' }}>
```
---
## Performance Checklist
| Issue | Fix |
| ----------------- | --------------------------------------------------- |
| JS thread jank | Move ALL animations to Reanimated worklets |
| Slow list | Replace ScrollView/FlatList with FlashList |
| Image flicker | `Image` from `expo-image` (faster cache, blurhash) |
| Re-render cascade | `React.memo` + stable callbacks + Zustand selectors |
| Large bundle | Dynamic imports + Metro tree shaking |
| Memory leak | `useEffect` cleanup + cancel animation on unmount |
```tsx
// Cancel animation on unmount
useEffect(() => {
opacity.value = withTiming(1);
return () => cancelAnimation(opacity); // β critical
}, []);
```
---
## iOS-Specific
- **BlurView**: Use `@react-native-community/blur` for frosted glass nav bars/modals
- **SF Symbols**: Use `@expo/vector-icons/Ionicons` for system-native icons
- **Haptics**: `expo-haptics` β rich feedback on iOS, limited on Android
- **Dynamic Island**: Check `insets.top > 50` for Dynamic Island devices
- **Sheet presentations**: `presentation: 'formSheet'` in Expo Router for iOS bottom sheet native feel
## Android-Specific
- **Material You**: Use `react-native-paper` for M3 dynamic color theming
- **Edge-to-edge**: Set `android:windowLayoutInDisplayCutoutMode="shortEdges"` in AndroidManifest
- **Back gesture prediction**: Wrap routes in `GestureHandlerRootView` at root
- **Splash**: Use `expo-splash-screen` β never hardcode a delay
Files in this skill
- SKILL.md
- scripts/mobile_audit.py
Attribution
Comments
Loading commentsβ¦