Skip to content
Back to skills

Mobile Design

ASecurity

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.

  • 5 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 27, 2026
ai-agentsgobashreactspringapiperformance

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 27, 2026

npx -y skills add Harmitx7/tribunal-kit --skill mobile-design --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Mobile Design?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Mobile Design
[![Security: A β€” Skills Directory](https://www.skillsdirectory.com/api/skills/harmitx7-mobile-design-tribunal-kit/badge)](https://www.skillsdirectory.com/skills/harmitx7-mobile-design-tribunal-kit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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.md8.7 KB
  • scripts/mobile_audit.py35.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…