Skip to content

Mobile & iOS Patterns

A UX principle for coding agents. Also covers ios, mobile patterns, tablet, layered composition, drawer, bottom sheet, and 3 more.

Show all 9 aliases

ios, mobile patterns, tablet, layered composition, drawer, bottom sheet, side sheet, sheet dismissal, swipe to dismiss

This doc covers what to build on a phone: sheets, drawers, layered composition, and how they behave. The mobile browser's own defaults, which break these patterns on the web specifically, live in mobile-web.md: viewport units, safe-area insets, tap highlight, and overscroll.

Prefer Layered Compositions Over Full Screens

One of the fastest ways to make your app feel iOS-native almost immediately is to stop designing a full screen for everything.

iOS Human Interface Guidelines highly favor layered compositions: placing stuff-on-stuff while maintaining spatial continuity. The current context stays in view; the new affordance sits on top of it rather than replacing it.

A new full screen mentally takes over the user. Out of sight is out of mind — and that is expensive cognitively. In some domains (checkout, multi-step input, side-by-side comparison, in-progress media), it can outright cost you money: every full-screen push is a chance for the user to lose their place, lose their intent, or abandon.

Don't "teleport" users between screens for every single activity — especially when it's a sub-task of the screen they're already on.

The rule: Before reaching for a push to a new screen on mobile or tablet, ask whether the interaction can live as a layer on the current screen instead. Reserve full-screen transitions for genuinely new contexts (a different object, a different mode), not for sub-tasks of the current one.

Layered alternatives to a full screen

Reach for these before a full-screen push. Each one preserves the current context — the user can still see (and mentally hold) where they came from.

  • Bottom sheets, also called bottom drawers — for sub-tasks that need real estate but should remain anchored to the current screen: filters, share targets, "more options," compact forms, item detail previews. The parent screen stays visible behind the sheet, so dismissal feels like returning rather than navigating back. Support detents (e.g. medium → large) so the sheet only takes the height it needs. On tablet breakpoints, prefer a popover or side sheet over a full-width sheet.
  • Contextual menus — for actions that belong to a specific element (a row, a card, a message). Triggered by long-press on iOS or a ... affordance. Keeps the action right next to its target instead of routing the user to a settings screen and back.
  • Inline expansion — for revealing detail that belongs to a list item or card: expandable rows, "show more" sections, embedded editors. The surrounding content stays in place, so the user keeps their scroll position and their mental model of the list.
  • Popovers — for short, focused content that's tied to a trigger: tooltips, mini-pickers, info bubbles, small forms. Especially useful on tablet/iPad breakpoints where a bottom sheet would feel too heavy. The arrow/tail visually anchors the popover to its source, preserving spatial continuity.
  • Progressive disclosure — for complex flows. Show only what's needed now, reveal the next step inline as the user commits. Multi-step forms can grow downward instead of pushing to a new screen per step. The user sees their progress and previous answers without backtracking.

When a full screen is the right choice

Full-screen transitions still belong in mobile UX — just not as the default. Use them when:

  • The user is entering a genuinely new context (a different object, a different mode — opening a conversation from an inbox, entering an editor from a list).
  • The task needs the entire viewport to be usable (camera, map, video, immersive reading).
  • The previous screen would be misleading if left visible (the parent state no longer reflects reality).

If none of those apply, layer it instead.

Tablet & larger breakpoints

At tablet width and above, the calculus shifts further toward layering. There's enough horizontal room that even patterns which feel "modal" on phone can become side-by-side compositions:

  • Bottom sheets → side sheets or popovers. A ~400-pt-wide side sheet, or trailing-edge drawer, docked to that edge keeps the master list visible. Unlike a bottom drawer, it is usually non-modal: the list stays interactive, so it does not trap focus.
  • Push navigation → split view. Master/detail layouts let the user pick from a list on the left and see the detail on the right without ever leaving the screen.
  • Modal sheets → inspector panes. Settings and details that take over the screen on phone can sit as a persistent inspector on iPad.

The underlying principle is the same at every size: keep context visible whenever you can.

Fluid Sheet Behavior

Layered mobile UI only works if the layer behaves like something the user can manipulate directly. A bottom sheet that ignores velocity or blocks input while it animates feels like a modal wearing mobile clothes.

  • Use detents instead of a single open/closed state. Common resting positions are compact, medium, and full-height. Pick detents that match the content, not arbitrary percentages.
  • Track the sheet one-to-one while dragging. Preserve the grab offset and update the transform on every pointer move. Do not wait until release to animate.
  • Settle from velocity and distance. A short fast flick should be able to advance to the next detent; a slow hesitant drag should not be forced there just because it crossed a crude midpoint.
  • Keep the sheet interruptible. If a sheet is settling and the user grabs it, stop the completion animation and continue from the current visible position.
  • Soften the edges. Past the highest or lowest detent, apply rising resistance and spring back on release. Hard clamps make the sheet feel detached from the finger.
  • Keep the parent context legible. Dim, blur, or scale the background only enough to clarify layering. The parent screen should still explain where the sheet came from and where dismissal returns.
  • Respect reduced motion. Keep the state change and spatial relationship, but drop large travel, bounce, and background scaling when prefers-reduced-motion: reduce is active.

Focus and Dismissal in a Sheet or Drawer

A modal sheet is a dialog that happens to be draggable, so it owes the user the same focus and dismissal contract as any other dialog (see dialogs.md for the full rules).

First decide whether the surface is modal. A bottom drawer that dims the screen behind it is modal: the parent is inert until the user deals with it. A side sheet or inspector pane that deliberately keeps the master list usable is not, and the rules below split on that. Treating a persistent inspector as modal is its own bug, since it locks the user out of the content the pane exists to edit.

For modal sheets and drawers:

  • Move focus into the sheet when it opens, and trap it there. While it is open, Tab must cycle inside it and never reach the parent screen behind it. A modal drawer that leaks focus to the content it covers is unusable with a keyboard or a screen reader.
  • Offer more than one way out, and make them agree. Escape, a swipe down past the lowest detent, and a tap on the dimmed background should all dismiss, and all run the same close logic. A sheet that can be swiped away but not dismissed with Escape is keyboard-hostile. The exception is a flow that must not be dismissed by accident (a required step, a destructive confirmation), which drops the implicit paths and asks for an explicit choice.

For non-modal side sheets and inspector panes: no focus trap, no aria-modal, no inert background, and no backdrop to tap, because there is no backdrop. Move focus in on open if the user opened it to work in it, and let Tab leave naturally.

Either way:

  • Return focus to the trigger on close. Whatever opened the sheet gets focus back, so the user resumes where they left off instead of at the top of the page.
  • Guard dismissal when work would be lost. If the sheet holds unsaved input, confirm before discarding it, and apply that guard to the swipe as much as to the close button. An accidental swipe should not be more destructive than a deliberate Cancel.

Route every exit through one function so the drag gesture cannot bypass the guard the button respects:

function onDismiss(reason: "escape" | "swipe" | "backdrop" | "close") {
  // The swipe is a dismissal like any other, so it answers to the same guard.
  if (isDirty && !confirmDiscard()) {
    settleToDetent("current"); // spring the sheet back; do not close
    return;
  }
  close();
  triggerRef.current?.focus(); // focus returns to whatever opened the sheet
}
  • Announce a modal sheet as a dialog, and label it. A drag-driven sheet is usually hand-rolled rather than a native <dialog>, so a modal one needs role="dialog", aria-modal="true", an inert background, and a label naming what it is. If you did build on a native <dialog> opened with showModal(), do not add those on top: it already carries the role, modality, and inertness, and doubling up can suppress correct announcement (see dialogs.md).

Critique the Emotional Contract, Not Only the Screen

The product coordinates a divorce process, so its interface must support people during a stressful and emotionally charged period. A generic task-manager treatment misses that context.

The critique identifies arbitrary color, muddy shadows, an undeveloped type scale, weak emphasis on the current task, completed work that remains too dominant, missing time expectations, unclear chat hierarchy, and status information detached from the actions it explains.

The central lesson is to critique the emotional contract as well as the screen. Here, trust, calm, focus, and support matter more than novelty. Every visual and verbal decision should reinforce those qualities.

Mobile Information Architecture and Progressive Disclosure

The redesign begins with mobile information architecture and familiar platform behavior. Tasks, chat, and settings become stable top-level destinations rather than competing regions on one dense screen.

The journey is organized as a sequence of phases. The current phase opens into detail; past and future phases remain visible but quiet. Progressive disclosure gives people enough context to understand where they are without asking them to process the entire process at once.

Cards are formed through subtle layers and spacing rather than excessive borders and shadows. Time estimates sit near the work they describe. Warmer, direct language replaces administrative phrasing. These choices translate the desired qualities into structure, material, and copy.

One Task-Detail Pattern for Simple and Complex Steps

The task-detail pattern must support both a single action and a complex set of subtasks without becoming two unrelated systems. A simple repeated structure does the work: explain the step, expose one primary action, and retain a secondary way to mark it complete when the real-world work happens elsewhere.

Consistency reduces the learning cost, while flexible content inside the pattern handles variation. The design becomes more useful by accommodating reality without adding new interface grammar for every exception.

Checklist

  • Audit every push / full-screen transition: does this interaction need a new screen, or is it a sub-task of the current one?
  • Sub-tasks use bottom sheets, popovers, contextual menus, inline expansion, or progressive disclosure — not new screens.
  • Bottom sheets use detents so they only take the height they need.
  • Bottom sheets track the pointer one-to-one, settle from velocity, and remain interruptible while animating.
  • Drag boundaries use resistance and spring-back rather than abrupt clamping.
  • Each sheet or drawer is explicitly modal or non-modal; only modal ones trap focus, set aria-modal, and make the background inert.
  • Focus returns to the trigger on close, modal or not.
  • For modal sheets, Escape, swipe-down, and background tap all dismiss, and all run the same close logic.
  • Dismissal is guarded when the sheet holds unsaved work, including dismissal by swipe.
  • On tablet breakpoints, bottom sheets become side sheets / popovers; push navigation becomes split view where appropriate.
  • Full-screen pushes are reserved for new contexts, full-viewport tasks, or cases where the previous screen would be misleading.

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-ux-principle({ topic: "mobile" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems