# Mobile UI Patterns

> Canonical mobile UI patterns (list, detail, tab, sheet, master-detail, search, form) with platform-aware guidance across SwiftUI, Compose, Flutter, and React Native. Use when designing or reviewing screens.

- Skill: `almasumdev/mobile-ui-patterns` (Agent Skill)
- Install (CLI): `npx skillmds@latest add almasumdev/mobile-ui-patterns`
- Raw SKILL.md: https://api.skillmd.com/api/skills/almasumdev/mobile-ui-patterns/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: almasumdev (https://skillmd.com/u/almasumdev)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/almasumdev/mobile-ui-patterns

---


# Mobile UI Patterns

## Instructions

Mobile apps are built from a small set of recurring UI patterns. Using canonical patterns reduces design debt, makes navigation predictable, and keeps the codebase learnable. This skill names the patterns and shows the cross-platform equivalents.

### 1. The Core Pattern Set

| Pattern | When to use | Platform convention cue |
| --- | --- | --- |
| List | Browsing a homogeneous collection | Plain list, grouped list (iOS), LazyColumn (Android) |
| Detail | Focused view of a single item | Pushed onto the stack from a list |
| Tabs | 2-5 peer destinations always available | Bottom tab bar (both platforms) |
| Sheet | Secondary task or disclosure | Modal/half sheet (iOS 15+), BottomSheet (Android) |
| Master-detail | Large screens: list on left, detail on right | iPad, foldable, tablet layouts |
| Search | Filtering or discovery | `searchable` (SwiftUI), SearchBar (Compose) |
| Form | Structured data entry | Grouped list style; keyboard-aware |
| Feed | Vertical infinite content | Pull-to-refresh + pagination |
| Onboarding | First-run explanation | Horizontal paged, skippable |
| Empty / error / loading | Non-happy path of any screen | Tri-state render contract |

### 2. The Tri-State Screen Contract

Every data-driven screen must handle **loading**, **empty**, and **error** as first-class states alongside **content**. Agents should reject "success-only" designs.

```swift
// SwiftUI
switch viewModel.state {
case .loading: ProgressView()
case .empty:   EmptyStateView(onAction: viewModel.createNew)
case .error(let e): ErrorStateView(message: e.localizedDescription, retry: viewModel.load)
case .content(let items): List(items) { ItemRow(item: $0) }
}
```

```kotlin
// Jetpack Compose
when (val s = state) {
    UiState.Loading -> CircularProgressIndicator()
    UiState.Empty   -> EmptyState(onAction = vm::createNew)
    is UiState.Error -> ErrorState(s.message, retry = vm::load)
    is UiState.Content -> LazyColumn { items(s.items) { ItemRow(it) } }
}
```

```dart
// Flutter
return switch (state) {
  Loading()            => const Center(child: CircularProgressIndicator()),
  Empty()              => EmptyState(onAction: vm.createNew),
  Error(:final message)=> ErrorState(message: message, retry: vm.load),
  Content(:final items)=> ListView(children: items.map(ItemRow.new).toList()),
};
```

```tsx
// React Native
if (state.kind === 'loading') return <ActivityIndicator />;
if (state.kind === 'empty')   return <EmptyState onAction={vm.createNew} />;
if (state.kind === 'error')   return <ErrorState message={state.message} retry={vm.load} />;
return <FlatList data={state.items} renderItem={({item}) => <ItemRow item={item} />} />;
```

### 3. List Pattern Details

- Use lazy containers: `List`, `LazyColumn`, `ListView.builder`, `FlatList`. Never map over an array of 1000 items into a non-virtualized container.
- Row height should be stable. If not, supply a key/id to aid diffing.
- Pull-to-refresh is expected on any user-owned list.
- Pagination is either cursor-based (preferred) or page-index-based, never both.
- Separator style follows platform: iOS inset separators, Android divider or none with spacing.

### 4. Detail Pattern Details

- Hero item is above the fold on phones.
- Primary action is a single, visually dominant button.
- Destructive actions go under an overflow menu or a confirmation sheet, never a primary button.
- On iOS, large navigation title collapses on scroll; on Android, use collapsing top app bar sparingly.

### 5. Tabs Pattern Details

- 2-5 tabs. Six is a code smell; replace with "More".
- Each tab owns its own navigation stack. Deep links restore into the right tab.
- Tab icons use filled/outlined variants for selected/unselected on both platforms.
- Do not hide tabs on scroll unless the screen is immersive (video, map).

### 6. Sheet Pattern Details

- Use sheets for tasks that could return the user to the previous context (composing, filtering).
- iOS 15+ supports `presentationDetents([.medium, .large])`; Android uses `ModalBottomSheet`.
- Avoid stacking sheets more than two deep.
- Sheets are dismissable with a swipe on both platforms; always persist draft state on dismiss.

### 7. Master-Detail Pattern Details

- Trigger breakpoint at 600-700 dp width (roughly iPad portrait, large foldable unfolded).
- On phones, this collapses to list -> detail push navigation.
- Preserve selection across orientation changes.
- Detail pane must function standalone (bookmarks, deep links land there directly).

### 8. Search Pattern Details

- Debounce input by 250-400 ms before querying.
- Cancel the previous request when a new one starts.
- Empty state shows recent searches or a helpful prompt.
- Respect the platform: search bar at top on Android (Material), inside navigation on iOS.

### 9. Form Pattern Details

- Keyboard type matches the field (`.emailAddress`, `.numberPad`, `KeyboardType.Password`, `inputType`, `keyboardType="email-address"`).
- Focus advances on return; submit on the final field.
- Validate on blur, not on every keystroke.
- Show a single inline error per field, not a top-of-form summary alone.
- Forms must survive backgrounding and rotation without data loss.

### 10. Platform Convention Respect

Do not build a Material-styled app on iOS or vice versa. Conventions cost little at build time and dominate perceived quality. When using Flutter or React Native, use adaptive components (`CupertinoSwitch` / `Switch`, `Platform.select`) for controls that have strong platform semantics.

## Checklist

- [ ] Every screen handles loading, empty, error, and content states explicitly.
- [ ] Lists use platform-appropriate lazy containers and keyed items.
- [ ] Tab count is 2-5; each tab has its own stack and deep-link entry.
- [ ] Sheets are used for sub-tasks, not primary navigation.
- [ ] Master-detail triggers on tablet-width; selection survives rotation.
- [ ] Search is debounced, cancellable, and has recent/empty states.
- [ ] Forms handle keyboard types, focus order, validation timing, and rotation.
- [ ] Platform controls are adaptive in cross-platform stacks.

