appllama-app-design-skill
Build native-feeling, benchmark-quality mobile app screens (Expo / React Native). Use when designing or implementing any mobile UI — screens, flows, onboarding, paywalls, tab bars, sheets, settings, empty states — or when polishing motion, navigation, typography, dark mode, or perceived performance. Enforces Apple HIG fidelity, semantic colors, native controls, anti-slop discipline, navigation semantics, purposeful Reanimated motion, a full-motion simulator-verified iteration loop, and a study-real-apps-first workflow.
Install
npx skills add https://github.com/Appllama/appllama-skills --skill appllama-app-design-skillPerformance — measure, fix, re-measure
Perceived quality is half design, half frame rate. This reference is the method; never optimize on vibes.
The loop
- Measure a baseline for the exact interaction that feels bad (FPS during the transition, TTI from cold start, commit counts during typing).
- Optimize the one thing the measurement indicts.
- Re-measure the same way.
- Validate with a number: 45 → 60 fps, TTI 3.2 s → 1.8 s. No number, no claim.
Do not recommend useMemo/useCallback/React.memo without profiler
evidence of wasted renders. Do not flag "stale closure risk" without a repro.
Measure the target interaction, not tree depth.
FPS & re-renders (highest impact)
- Long or growable list on ScrollView → replace with FlashList. This single swap fixes more RN jank than everything else combined.
- Re-render storms: profile with React DevTools; the classic causes are a broad context/store consumed by leaves, inline object/array props into memoized children, and parent state that should be local.
- Prefer atomic state (Zustand selectors, Jotai atoms) so a change re-renders only its consumers.
- React Compiler: enable once profiling shows cascading re-renders; it replaces most manual memoization. Watch for bailouts (mutations, non-plain patterns) — a bailed-out hot component silently loses the win.
useDeferredValuefor expensive derived UI (filter results, search highlighting) behind fast-changing inputs.
Typing performance
Controlled TextInputs re-render the tree per keystroke. For search bars and
forms: uncontrolled inputs (defaultValue + onChangeText into a ref or
store), commit to state on submit/debounce.
Startup / TTI
- Measure only cold starts, with
react-native-performancemarkers. - Native navigation (
react-native-screens) enabled. - Defer everything not needed for first paint: heavy SDK init, analytics, below-the-fold data.
- Hermes: check bundle compression guidance for your RN version (mmap).
- Inspect the bundle when it grows:
npx react-native bundle … --dev falsethensource-map-explorer. Barrel imports (import { x } from '@/components') are the classic silent bloat — import from the source file.
Memory
- Symptoms: growing RSS while navigating back and forth. Hunt JS leaks with heap snapshots (retained listeners, intervals, subscriptions in effects missing cleanup) before blaming native.
- Lists:
recyclingKeyonexpo-imageitems, no inline closures capturing huge parent scopes inrenderItem.
Animations
- Anything janky mid-gesture: confirm the animation runs as a worklet on the
UI thread; a single
runOnJSinonChangeis enough to ruin it. - Heavy screens committed during a transition stall the JS thread and hitch
even UI-thread animations — defer the destination screen's expensive work
until
InteractionManager.runAfterInteractions/ after the transition ends.