@buivietphi/skill-mobile-mt 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,667 @@
1
+ # UI/UX Mobile — Screen Design & Implementation
2
+
3
+ > Use when: "create screen X", "build UI for Y", "design this layout", "demo screen", "mockup".
4
+ > Covers: design system, screen templates, navigation, touch, color, typography, animation, accessibility.
5
+
6
+ ---
7
+
8
+ ## Design Decision Framework
9
+
10
+ **Before any screen, answer these:**
11
+
12
+ ```
13
+ SCREEN: [name / purpose]
14
+ PLATFORM: [iOS / Android / Cross-platform]
15
+ TYPE: [form / list / detail / dashboard / chat / auth / onboarding]
16
+ DATA: [static / API / real-time / offline-first]
17
+ PRIORITY: [speed-to-build / pixel-perfect / accessibility-first]
18
+ ```
19
+
20
+ **Then auto-think:**
21
+
22
+ ```
23
+ <think>
24
+ SCREEN: [description]
25
+ TEMPLATE: [closest match from templates below]
26
+ TOKENS: [which design tokens to use]
27
+ STATES: loading / error / empty / success
28
+ NAVIGATION: [how user arrives + leaves]
29
+ PLATFORM RULES: [iOS HIG / Material Design 3 specifics]
30
+ TOUCH: [primary CTA in thumb zone?]
31
+ DARK MODE: [semantic colors, no hardcoded]
32
+ A11Y: [labels, contrast, Dynamic Type]
33
+ </think>
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Design Token Architecture (3 Layers)
39
+
40
+ ```
41
+ Primitive tokens: blue-500, spacing-4 (raw values)
42
+
43
+ Semantic tokens: color-primary, text-error (purpose-based)
44
+
45
+ Component tokens: button-bg, card-radius (component-specific)
46
+ ```
47
+
48
+ **Rule: NEVER hardcode values. Always use design tokens.**
49
+
50
+ ### React Native
51
+
52
+ ```typescript
53
+ // theme/tokens.ts
54
+ export const colors = {
55
+ primary: '#007AFF',
56
+ secondary: '#5856D6',
57
+ background: '#FFFFFF',
58
+ surface: '#F2F2F7',
59
+ text: '#000000',
60
+ textSecondary: '#8E8E93',
61
+ error: '#FF3B30',
62
+ success: '#34C759',
63
+ warning: '#FF9500',
64
+ border: '#E5E5EA',
65
+ dark: {
66
+ background: '#000000', // OLED: true black = 0% battery
67
+ surface: '#1C1C1E', // elevation level 1
68
+ surfaceElevated: '#2C2C2E', // elevation level 2
69
+ text: '#E0E0E0', // NOT pure white (causes eye strain)
70
+ textSecondary: '#8E8E93',
71
+ border: '#38383A',
72
+ },
73
+ };
74
+
75
+ export const spacing = {
76
+ xs: 4, sm: 8, md: 16, lg: 24, xl: 32, xxl: 48,
77
+ };
78
+
79
+ export const radius = {
80
+ sm: 8, md: 12, lg: 16, xl: 24, full: 9999,
81
+ };
82
+
83
+ export const fontSize = {
84
+ caption: 12, body: 16, title: 20, heading: 28, hero: 34,
85
+ };
86
+
87
+ export const shadow = {
88
+ sm: { shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.1, shadowRadius: 2, elevation: 2 },
89
+ md: { shadowOffset: { width: 0, height: 4 }, shadowOpacity: 0.15, shadowRadius: 8, elevation: 4 },
90
+ lg: { shadowOffset: { width: 0, height: 8 }, shadowOpacity: 0.2, shadowRadius: 16, elevation: 8 },
91
+ };
92
+ ```
93
+
94
+ ### Flutter
95
+
96
+ ```dart
97
+ // theme/app_theme.dart
98
+ class AppTheme {
99
+ static ThemeData light() => ThemeData(
100
+ colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF007AFF)),
101
+ useMaterial3: true,
102
+ textTheme: const TextTheme(
103
+ headlineLarge: TextStyle(fontSize: 34, fontWeight: FontWeight.bold),
104
+ titleLarge: TextStyle(fontSize: 20, fontWeight: FontWeight.w600),
105
+ bodyLarge: TextStyle(fontSize: 16),
106
+ bodySmall: TextStyle(fontSize: 12, color: Color(0xFF8E8E93)),
107
+ ),
108
+ );
109
+
110
+ static ThemeData dark() => ThemeData(
111
+ colorScheme: ColorScheme.fromSeed(
112
+ seedColor: const Color(0xFF007AFF),
113
+ brightness: Brightness.dark,
114
+ ),
115
+ useMaterial3: true,
116
+ );
117
+ }
118
+ ```
119
+
120
+ ---
121
+
122
+ ## Platform Color Rules
123
+
124
+ ### iOS Semantic Colors (auto light/dark)
125
+
126
+ ```
127
+ Label: .label → primary text
128
+ SecondaryLabel: → secondary text
129
+ SystemBackground: → screen bg
130
+ SecondarySystemBackground: → card/section bg
131
+ Separator: → thin dividers
132
+ ```
133
+
134
+ ### Android Material You (Dynamic Color)
135
+
136
+ ```
137
+ API 31+: Colors extracted from wallpaper automatically
138
+ Primary / Secondary / Tertiary + On variants
139
+ Surface: elevation = slightly lighter overlay
140
+ 0dp = 0% overlay | 4dp = 9% | 8dp = 12% | 12dp = 14%
141
+ ```
142
+
143
+ ### OLED Battery Impact
144
+
145
+ | Color | Battery Usage |
146
+ |-------|--------------|
147
+ | #000000 (true black) | 0% |
148
+ | #1A1A1A (near black) | ~15% |
149
+ | #333333 (dark gray) | ~30% |
150
+ | #FFFFFF (white) | 100% |
151
+
152
+ **Rule:** Dark mode backgrounds = `#000000`. Surfaces = `#0D0D0D` to `#1C1C1E`.
153
+
154
+ ---
155
+
156
+ ## Typography System
157
+
158
+ ### iOS (SF Pro)
159
+
160
+ | Style | Size | Weight | Usage |
161
+ |-------|------|--------|-------|
162
+ | Large Title | 34pt | Bold | Screen titles |
163
+ | Title 1 | 28pt | Bold | Section titles |
164
+ | Title 3 | 20pt | Semibold | Subtitles |
165
+ | Body | 17pt | Regular | Main content |
166
+ | Callout | 16pt | Regular | Secondary content |
167
+ | Caption 1 | 12pt | Regular | Labels, timestamps |
168
+
169
+ ### Android (Roboto / Material 3)
170
+
171
+ | Style | Size | Weight | Usage |
172
+ |-------|------|--------|-------|
173
+ | Display Large | 57sp | Regular | Hero text |
174
+ | Headline Medium | 28sp | Regular | Section titles |
175
+ | Title Large | 22sp | Regular | Card titles |
176
+ | Body Large | 16sp | Regular | Main content |
177
+ | Body Medium | 14sp | Regular | Supporting text |
178
+ | Label Small | 11sp | Medium | Captions, chips |
179
+
180
+ **Critical rules:**
181
+ - iOS: Dynamic Type is MANDATORY, not optional
182
+ - Android: always use `sp` for text, `dp` for everything else
183
+ - Test at 200% font scale — UI must not break
184
+ - Dark mode: text appears thinner (halation) — consider medium weight
185
+
186
+ ---
187
+
188
+ ## Touch & Thumb Zone
189
+
190
+ ### Touch Targets
191
+
192
+ | Platform | Minimum | Recommended | Critical Actions |
193
+ |----------|---------|-------------|-----------------|
194
+ | iOS (HIG) | 44×44 pt | 48×48 pt | 56+ pt |
195
+ | Android (Material) | 48×48 dp | 48×48 dp | 56+ dp |
196
+ | Between targets | 8 dp gap | 12 dp gap | |
197
+ | WCAG 2.2 | 44×44 px | — | — |
198
+
199
+ **Rule:** Use `hitSlop` (RN) or `MaterialTapTargetSize.padded` (Flutter) if visual size is smaller.
200
+
201
+ ### Thumb Zone (One-Handed Use)
202
+
203
+ ```
204
+ ┌─────────────────────────┐
205
+ │ HARD HARD │ ← Top corners (status, settings)
206
+ │ │
207
+ │ OK OK │ ← Middle area
208
+ │ │
209
+ │ EASY ██ EASY │ ← Bottom = PRIMARY CTAs go here
210
+ │ THUMB │
211
+ │ [Tab1] [Tab2] [Tab3] │ ← Tab bar in easy zone
212
+ └─────────────────────────┘
213
+
214
+ 49% of users hold phone one-handed.
215
+ Primary CTA → bottom.
216
+ Destructive actions → top-left (hardest to reach = safer).
217
+ ```
218
+
219
+ ### Haptic Feedback
220
+
221
+ | Action | iOS | Android |
222
+ |--------|-----|---------|
223
+ | Toggle/selection | `selection` (light) | `TICK` |
224
+ | Button tap | `medium` | `CLICK` |
225
+ | Important confirm | `heavy` | `HEAVY_CLICK` |
226
+ | Success | `success` | `DOUBLE_CLICK` |
227
+ | Error | `error` | `REJECT` |
228
+
229
+ ---
230
+
231
+ ## Navigation Patterns
232
+
233
+ ### Decision Tree
234
+
235
+ ```
236
+ 3-5 top-level sections → Tab Bar / Bottom Navigation
237
+ Deep hierarchy (>2 levels) → Stack Navigation
238
+ Many destinations (>5) → Drawer Navigation
239
+ Single linear flow → Stack only (wizard/onboarding)
240
+ Tablet / Foldable → Navigation Rail + List-Detail
241
+ ```
242
+
243
+ ### Tab Bar Rules
244
+
245
+ | Rule | iOS | Android |
246
+ |------|-----|---------|
247
+ | Max items | 5 | 5 |
248
+ | Height | 49pt | 80dp |
249
+ | Icon size | 25×25pt (SF Symbols) | 24dp (Material Symbols) |
250
+ | Labels | Always visible | Always visible |
251
+ | Active state | Filled icon + tint | Filled icon + indicator pill |
252
+ | On tab switch | Preserve each tab's navigation stack |
253
+ | Deep link | Construct full back stack |
254
+
255
+ ### Critical Navigation Rules
256
+
257
+ ```
258
+ ✅ Each tab maintains its own navigation stack
259
+ ✅ System back button works predictably
260
+ ✅ Deep link URLs match navigation path
261
+ ⛔ NEVER use modals for everything — use push navigation
262
+ ⛔ NEVER reset tab stack on tab switch
263
+ ⛔ NEVER override platform back gesture
264
+ ```
265
+
266
+ ---
267
+
268
+ ## Screen Templates
269
+
270
+ ### 1. Login Screen
271
+
272
+ ```
273
+ ┌─────────────────────────┐
274
+ │ │
275
+ │ [Logo] │
276
+ │ App Name / Tagline │
277
+ │ │
278
+ │ ┌───────────────────┐ │
279
+ │ │ Email │ │
280
+ │ └───────────────────┘ │
281
+ │ ┌───────────────────┐ │
282
+ │ │ Password 👁 │ │
283
+ │ └───────────────────┘ │
284
+ │ │
285
+ │ [ Forgot password? ] │
286
+ │ │
287
+ │ ┌───────────────────┐ │
288
+ │ │ Sign In │ │ ← Primary CTA (full width)
289
+ │ └───────────────────┘ │
290
+ │ │
291
+ │ ── or continue with ── │
292
+ │ [Google] [Apple] [FB] │ ← Social login row
293
+ │ │
294
+ │ Don't have account? │
295
+ │ [Sign Up] │
296
+ └─────────────────────────┘
297
+ ```
298
+
299
+ **Rules:** `KeyboardAvoidingView` (RN) / `SingleChildScrollView` (Flutter). Password toggle. Disable button during loading. Show inline error below input, not toast.
300
+
301
+ ### 2. Home / Feed
302
+
303
+ ```
304
+ ┌─────────────────────────┐
305
+ │ [☰] Home [🔔][👤]│ ← Header: menu, title, actions
306
+ ├─────────────────────────┤
307
+ │ ┌───────────────────┐ │
308
+ │ │ 🔍 Search... │ │
309
+ │ └───────────────────┘ │
310
+ │ [Filter chips scrollH] │ ← Horizontal scroll filter
311
+ │ ┌───────────────────┐ │
312
+ │ │ Card 1 │ │
313
+ │ │ Image + Title │ │ ← FlatList / ListView.builder
314
+ │ │ Subtitle + Meta │ │
315
+ │ └───────────────────┘ │
316
+ │ ┌───────────────────┐ │
317
+ │ │ Card 2 │ │
318
+ │ └───────────────────┘ │
319
+ │ ... │
320
+ ├─────────────────────────┤
321
+ │ [🏠] [🔍] [➕] [💬] [👤]│ ← Bottom tab bar
322
+ └─────────────────────────┘
323
+ ```
324
+
325
+ **Rules:** `FlatList` / `ListView.builder` — NEVER `ScrollView` for dynamic lists. Pull-to-refresh. Pagination. Loading skeleton. Empty state. `key`/`Key` on each item.
326
+
327
+ ### 3. Detail Screen
328
+
329
+ ```
330
+ ┌─────────────────────────┐
331
+ │ [←] Detail [⋮] │
332
+ ├─────────────────────────┤
333
+ │ ┌───────────────────┐ │
334
+ │ │ Hero Image │ │ ← Full width, 16:9
335
+ │ └───────────────────┘ │
336
+ │ Title (heading) │
337
+ │ Subtitle / category │
338
+ │ ★★★★☆ 4.2 (128) │
339
+ │ │
340
+ │ Description text... │
341
+ │ │
342
+ │ ── Related ────────── │
343
+ │ [Card] [Card] [Card]→ │ ← Horizontal scroll
344
+ ├─────────────────────────┤
345
+ │ [$29.99] [Add to Cart]│ ← Sticky bottom CTA
346
+ └─────────────────────────┘
347
+ ```
348
+
349
+ **Rules:** `ScrollView` OK (single item). Sticky bottom bar outside scroll. Safe area at bottom. Share/bookmark in header.
350
+
351
+ ### 4. Profile / Settings
352
+
353
+ ```
354
+ ┌─────────────────────────┐
355
+ │ [←] Profile [Edit] │
356
+ ├─────────────────────────┤
357
+ │ ┌─────┐ │
358
+ │ │ 👤 │ │ ← Avatar (circular)
359
+ │ └─────┘ │
360
+ │ John Doe │
361
+ │ john@email.com │
362
+ │ ┌───────────────────┐ │
363
+ │ │ Account > │ │
364
+ │ ├───────────────────┤ │
365
+ │ │ Notifications > │ │ ← Grouped sections with chevrons
366
+ │ ├───────────────────┤ │
367
+ │ │ Appearance > │ │
368
+ │ ├───────────────────┤ │
369
+ │ │ Privacy > │ │
370
+ │ └───────────────────┘ │
371
+ │ ┌───────────────────┐ │
372
+ │ │ Terms > │ │
373
+ │ ├───────────────────┤ │
374
+ │ │ Help > │ │
375
+ │ └───────────────────┘ │
376
+ │ [ Sign Out ] │ ← Red/destructive
377
+ │ App v2.1.0 │
378
+ └─────────────────────────┘
379
+ ```
380
+
381
+ ### 5. Onboarding (Swipeable)
382
+
383
+ ```
384
+ ┌─────────────────────────┐
385
+ │ [Skip] │
386
+ │ ┌───────────┐ │
387
+ │ │ Illustration │ │
388
+ │ └───────────┘ │
389
+ │ Welcome to AppName │
390
+ │ Short description │
391
+ │ ● ○ ○ │ ← Page indicator dots
392
+ │ ┌───────────────────┐ │
393
+ │ │ Get Started │ │ ← Last page: primary CTA
394
+ │ └───────────────────┘ │
395
+ └─────────────────────────┘
396
+ ```
397
+
398
+ **Rules:** 3-5 pages max. Skip always visible. Last page = CTA. Store `hasSeenOnboarding`.
399
+
400
+ ### 6. Form / Multi-Step
401
+
402
+ ```
403
+ ┌─────────────────────────┐
404
+ │ [←] Create [Save] │
405
+ ├─────────────────────────┤
406
+ │ Step 1 of 3 ●●○ │ ← Progress indicator
407
+ │ │
408
+ │ Title * │
409
+ │ ┌───────────────────┐ │
410
+ │ │ │ │
411
+ │ └───────────────────┘ │
412
+ │ ⚠ Title is required │ ← Inline error (red, below input)
413
+ │ │
414
+ │ Category │
415
+ │ ┌───────────────────┐ │
416
+ │ │ Select... ▼│ │
417
+ │ └───────────────────┘ │
418
+ │ │
419
+ │ Description │
420
+ │ ┌───────────────────┐ │
421
+ │ │ │ │
422
+ │ │ │ │
423
+ │ └───────────────────┘ │
424
+ │ 0/500 │ ← Character count
425
+ │ │
426
+ ├─────────────────────────┤
427
+ │ [Back] [Continue] │ ← Sticky bottom
428
+ └─────────────────────────┘
429
+ ```
430
+
431
+ **Rules:** Validate on blur. Show errors inline below input. Disable submit while loading. `KeyboardAvoidingView`. Mark required fields with `*`. Unsaved changes warning on back.
432
+
433
+ ### 7. Chat / Messages
434
+
435
+ ```
436
+ ┌─────────────────────────┐
437
+ │ [←] John Doe [📞][⋮]│
438
+ ├─────────────────────────┤
439
+ │ Today │
440
+ │ │
441
+ │ ┌─────────────┐ │
442
+ │ │ Hey, how are │ │ ← Received (left, gray bg)
443
+ │ │ you? │ │
444
+ │ └─────────────┘ 10:30 │
445
+ │ │
446
+ │ ┌─────────────┐ │
447
+ │ │ I'm good! │ │ ← Sent (right, primary color)
448
+ │ │ Thanks │ │
449
+ │ └─────────────┘ │
450
+ │ 10:31 │
451
+ │ │
452
+ │ ┌─────────────┐ │
453
+ │ │ ··· │ │ ← Typing indicator
454
+ │ └─────────────┘ │
455
+ ├─────────────────────────┤
456
+ │ [+] [Message... ] [→]│ ← Input bar + send
457
+ └─────────────────────────┘
458
+ ```
459
+
460
+ **Rules:** `FlatList inverted` (RN) / `ListView reverse` (Flutter). Auto-scroll on new message. Typing indicator. Timestamp grouping (Today/Yesterday/date). Input bar above keyboard.
461
+
462
+ ### 8. Search Results
463
+
464
+ ```
465
+ ┌─────────────────────────┐
466
+ │ [←] ┌───────────────┐ │
467
+ │ │ pizza nearby ✕ │ │ ← Search with clear button
468
+ │ └───────────────┘ │
469
+ ├─────────────────────────┤
470
+ │ Recent: [sushi] [tacos]│ ← Search history chips
471
+ │ │
472
+ │ 3 results for "pizza" │
473
+ │ ┌───────────────────┐ │
474
+ │ │ 🍕 Pizza Palace │ │
475
+ │ │ ★4.5 · 0.3mi · $$ │ │
476
+ │ └───────────────────┘ │
477
+ │ ┌───────────────────┐ │
478
+ │ │ 🍕 Mario's │ │
479
+ │ └───────────────────┘ │
480
+ └─────────────────────────┘
481
+ ```
482
+
483
+ **Rules:** Debounce search input (300ms+). Show recent searches. Show "No results" with suggestions, never blank. Autocomplete dropdown as user types.
484
+
485
+ ---
486
+
487
+ ## Component Patterns
488
+
489
+ ### Bottom Sheet
490
+
491
+ ```typescript
492
+ // RN: @gorhom/bottom-sheet
493
+ <BottomSheet snapPoints={['25%', '50%', '90%']}>
494
+ <BottomSheetView>{/* content */}</BottomSheetView>
495
+ </BottomSheet>
496
+
497
+ // Flutter: showModalBottomSheet
498
+ showModalBottomSheet(
499
+ context: context,
500
+ isScrollControlled: true,
501
+ builder: (context) => DraggableScrollableSheet(...),
502
+ );
503
+ ```
504
+
505
+ ### Empty State
506
+
507
+ ```
508
+ ┌─────────────────────────┐
509
+ │ [Illustration] │
510
+ │ No items yet │ ← Clear title
511
+ │ Add your first item │ ← Helpful subtitle
512
+ │ to get started. │
513
+ │ [ + Add Item ] │ ← Action button
514
+ └─────────────────────────┘
515
+ ```
516
+
517
+ **Rule:** Every list screen MUST have an empty state. Never show blank screen.
518
+
519
+ ### Loading Skeleton
520
+
521
+ ```
522
+ Show skeleton ONLY when there is no data to display.
523
+ If cached data exists → show stale data + refresh indicator.
524
+ Skeleton shape MUST match final UI layout.
525
+ Show 3-5 skeleton items.
526
+ ```
527
+
528
+ ### Toast / Snackbar
529
+
530
+ ```
531
+ Position: bottom (Android) or top (iOS)
532
+ Duration: 3s (info), 5s (error), persistent (action required)
533
+ Actions: max 1 button ("Undo", "Retry")
534
+ Never: block UI or require dismiss for non-critical info
535
+ ```
536
+
537
+ ### Confirmation Dialog
538
+
539
+ ```
540
+ Destructive actions (delete, sign out) → ALWAYS confirm first.
541
+ Title: "Delete item?"
542
+ Message: "This action cannot be undone."
543
+ Actions: [Cancel] [Delete] ← destructive button in red
544
+ ```
545
+
546
+ ---
547
+
548
+ ## Dark Mode
549
+
550
+ ```typescript
551
+ // RN: useColorScheme()
552
+ const scheme = useColorScheme(); // 'light' | 'dark'
553
+ const bg = scheme === 'dark' ? colors.dark.background : colors.background;
554
+
555
+ // Flutter: Theme.of(context).brightness
556
+ final isDark = Theme.of(context).brightness == Brightness.dark;
557
+ ```
558
+
559
+ **Rules:**
560
+ - NEVER hardcode colors — always semantic tokens
561
+ - Dark text on dark bg: use `#E0E0E0`, NOT pure `#FFFFFF` (eye strain)
562
+ - Shadows: reduce or remove (invisible on dark backgrounds)
563
+ - Borders: use lighter shade (`#38383A`, not `#E5E5EA`)
564
+ - Images: add dark variant or semi-transparent overlay
565
+ - Elevation in dark = slightly lighter surface (Material guideline)
566
+
567
+ ---
568
+
569
+ ## Animation Guidelines
570
+
571
+ | Animation | Duration | Easing |
572
+ |-----------|----------|--------|
573
+ | Button press | 100ms | ease-out |
574
+ | Screen transition | 300ms | ease-in-out |
575
+ | Bottom sheet open | 250ms | spring (damping 0.8) |
576
+ | Fade in content | 200ms | ease-in |
577
+ | List item appear | 150ms stagger | ease-out |
578
+
579
+ **Rules:**
580
+ - `useNativeDriver: true` (RN) — always for transforms/opacity
581
+ - 60 FPS target — no layout animations on main thread
582
+ - Animate ONLY `transform` and `opacity` (never `width`/`height`/`top`/`left`)
583
+ - Reduce motion: `AccessibilityInfo.isReduceMotionEnabled` (RN) / `MediaQuery.disableAnimations` (Flutter)
584
+ - No animation > 500ms (feels sluggish)
585
+ - Never apply universal `transition: all` — specify properties
586
+
587
+ ---
588
+
589
+ ## Accessibility Checklist
590
+
591
+ | Check | RN | Flutter |
592
+ |-------|-----|---------|
593
+ | Screen reader label | `accessibilityLabel` | `Semantics(label:)` |
594
+ | Button role | `accessibilityRole="button"` | `Semantics(button: true)` |
595
+ | Image alt text | `accessible={true} accessibilityLabel` | `Semantics(image: true, label:)` |
596
+ | Focus order | `accessibilityElementsHidden` | `ExcludeSemantics` |
597
+ | Color contrast | 4.5:1 (text), 3:1 (large text) | Same ratios |
598
+ | Font scaling | Support Dynamic Type | `MediaQuery.textScaleFactor` |
599
+ | State changes | `accessibilityLiveRegion` | `Semantics(liveRegion: true)` |
600
+
601
+ **Rules:**
602
+ - Every interactive element needs a label
603
+ - Every image needs alt text (or `decorative` flag)
604
+ - Never rely on color alone to convey meaning (add icons/text)
605
+ - Test with VoiceOver (iOS) and TalkBack (Android)
606
+ - Test at 200% font scale
607
+ - Visible focus indicators on all interactive elements
608
+
609
+ ---
610
+
611
+ ## UX Anti-Patterns (Never Do These)
612
+
613
+ | Anti-Pattern | Why | Fix |
614
+ |-------------|------|-----|
615
+ | `ScrollView` for long lists | Memory explosion | `FlatList` / `ListView.builder` |
616
+ | Hardcoded colors | Breaks dark mode | Semantic design tokens |
617
+ | Touch target < 44pt | Frustrating taps | Min 44pt iOS / 48dp Android |
618
+ | No empty state | Confusing blank screen | Illustration + message + CTA |
619
+ | Error only at form top | User can't find which field | Inline error below each input |
620
+ | Silent failures | User doesn't know what happened | Toast/banner with retry option |
621
+ | Modal for everything | Feels trapped | Stack navigation for content flow |
622
+ | Auto-play video | Drains battery + data | Click-to-play or pause off-screen |
623
+ | Placeholder as only label | Disappears on focus | Persistent label above input |
624
+ | Layout shift on load | Jarring CLS | Reserve space / skeleton placeholders |
625
+ | `100vh` on mobile | Address bar overlap | `dvh` or platform safe area |
626
+ | No loading indicator | UI feels frozen | Skeleton or spinner after 300ms |
627
+
628
+ ---
629
+
630
+ ## Self-Critique Protocol
631
+
632
+ **After generating any screen, ask:**
633
+
634
+ ```
635
+ 1. "Does this look like a default template?" → If yes, customize tokens/spacing
636
+ 2. "Would a user know what to do first?" → If unclear, strengthen visual hierarchy
637
+ 3. "What happens on error/empty/loading?" → If any missing, add them
638
+ 4. "Does primary CTA sit in thumb zone?" → If not, move to bottom
639
+ 5. "Can I use this in dark mode?" → If hardcoded colors, fix
640
+ 6. "Does it pass 44pt touch targets?" → If buttons are small, enlarge
641
+ ```
642
+
643
+ **If the screen could be mistaken for a tutorial demo, it needs more design work.**
644
+
645
+ ---
646
+
647
+ ## Screen Sizing Reference
648
+
649
+ | Device | Width (pt/dp) | Safe Area Top | Safe Area Bottom |
650
+ |--------|--------------|---------------|-----------------|
651
+ | iPhone SE | 375 | 20 | 0 |
652
+ | iPhone 15 | 393 | 59 | 34 |
653
+ | iPhone 15 Pro Max | 430 | 59 | 34 |
654
+ | Android small | 360 | 24 (status) | 48 (nav bar) |
655
+ | Android large | 412 | 24 | 48 |
656
+ | iPad | 768-1024 | 24 | 20 |
657
+
658
+ **Rules:**
659
+ - Always use `SafeAreaView` (RN) / `SafeArea` (Flutter)
660
+ - Design for 375pt width (smallest common), scale up
661
+ - Test on smallest AND largest device
662
+ - Scrollable content — never assume fixed heights
663
+
664
+ ---
665
+
666
+ > Mobile is NOT a small desktop. Touch-first, thumb-zone aware, battery conscious.
667
+ > Tokens first, 4 states always, platform rules respected, accessibility mandatory.