Design mobile screens as a system, not as loose screens. The same hierarchy, rhythm, and visual meaning should work in Compose and SwiftUI — adapting native components, without blindly cloning pixels.
Framework implementation: jetpack-compose and swiftui-development. Architecture: android-development / mvvm-architecture. For React Native/Expo: better-mobile-interface-react-native.
Expected result
- Contemporary interface, legible and with obvious hierarchy in 3 seconds.
- Light and dark with the same semantic structure (not just “inverting colors”).
- Consistent spacing, type, and radius across the entire flow.
- It feels native on each OS, but belonging to the same product.
- Accessible: contrast, Dynamic Type / fontScale, touch targets.
Before designing
Define in a few lines:
- Product and audience — what the screen sells or solves.
- A visual signature — the memorable detail (type, accent color, media treatment, shape).
- Tone — calm / energetic / editorial / utilitarian.
- Tokens — semantic colors, type, space, radius (light + dark together).
If the app already has a design system or brand guidelines, they win. Complete only what is missing.
Semantic tokens (required)
Never scatter literal hex/Color values across screens. Always roles:
| Role | Function |
|---|---|
background |
Screen background |
surface |
Cards, sheets, inputs |
surfaceElevated |
Modals, menus, FABs |
textPrimary |
Title and main body |
textSecondary |
Support, metadata |
textTertiary |
Hints, placeholders |
border / separator |
Dividers and outlines |
accent / primary |
CTA and brand focus |
onAccent |
Text/icon over accent |
success / warning / danger |
Feedback |
overlay |
Modal scrim |
Rules:
- Light and dark define pairs for each role in the same token table.
- Single accent (or controlled family). Do not invent a new color per screen.
- Neutrals carry the UI; the accent appears in CTAs, selection, and links.
- Elevation in dark: prefer lighter surfaces per layer, not just an invisible black shadow.
- Text/background contrast ≥ WCAG AA (4.5:1 body, 3:1 large text/essential icons).
Compose
- Extend
MaterialTheme/ColorScheme(Material 3) or your ownAppThemewith composition locals. isSystemInDarkTheme()+ user override if the product has one.- Dynamic Color only if the brand allows it; otherwise, a fixed product palette.
SwiftUI
Colorassets with Any/Dark variants, or semanticColorin the asset catalog.preferredColorSchemeonly when the screen requires it; the default follows the system.- Prefer semantic styles (
.primary,.secondary) mapped to the app's tokens.
Typography
- Limited and named scale:
display,title,body,label,caption. - At most 2 families (or the system stack: SF / Roboto / brand font).
- Hierarchy by size and weight — not only by color.
- Comfortable tracking and line-height on mobile; titles without truncating without a strategy (
minScale/lineLimitaware). - Respect Dynamic Type (iOS) and fontScale (Android); layouts must not break at 200%.
- Avoid long uppercase; short labels ok.
Space, grid, and density
- Base 4 or 8 pt/dp. Scale: 4, 8, 12, 16, 20, 24, 32, 40, 48…
- Consistent screen margins (typically 16–20).
- One screen: one vertical rhythm — don't alternate 10, 14, 22 without a system.
- Group what is related; separate sections with space, not with too many lines.
- Density: utilities can be more compact; marketing/onboarding more airy — choose and keep.
Shape, elevation, and depth
- Named radii:
sm,md,lg,full— use the same ones on cards, inputs, and chips. - Subtle borders; in dark, border + elevated surface usually reads better than a strong shadow.
- Avoid generic multi-shadow "glow" and cards with blur on everything.
- Dividers: 1 pt/dp in the color
separator, never 100% black.
Mobile layout
- Safe areas / system bars / home indicator / cutouts respected.
- Touch targets ≥ 44–48 pt/dp.
- Screen primary: one obvious main action (FAB/filled button).
- Lists: hierarchy title → subtitle → meta; discreet trailing actions.
- Empty / loading / error are design states, not blank screens.
- Keyboard: avoid hidden fields; correct scroll insets.
- Don't copy cross-platform chrome (iOS bottom nav identical to Material without adaptation).
Light and dark — parity
Checklist per screen:
- Same semantic tokens; no hex only in light or only in dark
- Images/icons legible in both (templates, tint, adaptive versions)
- Elevation/surface distinguishable in dark
- Accent with contrast on both backgrounds
- Dividers and borders visible without "disappearing" in dark
- Status bar / nav bar icons light or dark according to the background
- Placeholders and skeletons with adequate contrast
- Splash/onboarding also have a dark pair
Forbidden:
- Dark mode =
#000+ weak gray text - Light mode = pure white with gray
#999in long body text - Neon accent in dark and illegible pastel in light (or the inverse) without pair adjustment
Motion
- Short and physical: 200–350 ms in most UI transitions.
- System default easing (
spring/ Material motion) instead of theatrical bounce. - Motion reinforces hierarchy (content appearing, touch feedback), it doesn't decorate.
- Respect
Reduce Motion/prefers-reduced-motion/ animator duration scale. - Loading: skeleton or native progress; avoid too many decorative spinners.
Icons and media
- One icon family (SF Symbols / Material Symbols / the app's set).
- Weights aligned with type (regular with body; filled for selection).
- Avatars and thumbs with a fixed size from the scale; corner radius from the shape system.
- Photos: overlay/scrim when there is text over an image — test light and dark.
Components — cross-platform consistency
Map roles, not widgets 1:1:
| Role | Compose (guidance) | SwiftUI (guidance) |
|---|---|---|
| Primary CTA | Button filled / M3 |
.borderedProminent |
| Secondary CTA | outlined / text | .bordered / plain |
| Field | OutlinedTextField or design system |
TextField + app style |
| List | LazyColumn + list item tokens |
List / styled LazyVStack |
| Main nav | NavigationBar / Tab | TabView / toolbar |
| Sheet | Modal bottom sheet | .sheet / detents |
Same labels, action order, and button hierarchy on both platforms.
Anti-patterns (“generic AI” UI)
Avoid clichéd defaults when the brief does not call for them:
- Purple/indigo gradient + glass cards on everything
- Black + acid accent as the only “premium” look
- Too many pills, badges, and layered shadows
- Onboarding with 3 identical cards and random colorful icons
- System-only typography with no deliberate scale + generic accent
- Dark mode tacked on later, with leftover hardcoded colors
Implementation flow
- Documented light/dark tokens (or in the theme code).
- Theme applied at the root (
MaterialTheme/AppTheme/.environment). - Screen using only system tokens and components.
- Preview /
@Preview/#Previewin light and dark, normal and large font. - Consistency pass: spacing, radii, accent, empty states.
Definition of done
- No loose color literals in the screens touched
- Light and dark reviewed side by side
- Typography and spacing from the app scale
- Contrast and touch targets ok
- Looks native on the target OS and aligned with the product
- Reduce Motion / font scaling do not break the layout
- Compose and SwiftUI (if both are in scope) share the same semantic roles