Adaptive Layouts for Phones, Tablets, and Foldables

Quick answer: Design for the space your app window has now, not for a label such as “phone” or “tablet.” Let individual components reflow when their own constraints change, and make a larger content-level layout change only when the extra room helps people complete a task. Keep selection, drafts, scroll state, and navigation independent from the visual arrangement so a resize, rotation, or fold does not reset the user’s work.
An Android app can be full-screen on a phone, narrow in split-screen on a tablet, wide in a resizable desktop window, or change shape when a foldable opens. The physical device is therefore a poor input for layout logic. Android’s adaptive-layout guidance recommends basing screen-level decisions on the current app window rather than physical hardware.
That one shift produces a simpler rule: a wide phone window may deserve a wider layout, and a compact tablet window may not. Compose is especially suited to this because the UI is recomposed from the state and constraints that are true at that moment.
Responsive versus adaptive: two useful levels of change
Responsive and adaptive design solve related but different problems.
| Change | Best for | Example |
|---|---|---|
| Responsive | A component needs to use its current bounds more efficiently. | A card changes from stacked to side-by-side content; a grid gains another column. |
| Adaptive | A whole destination has enough room for a different information architecture. | A navigation bar becomes a rail; a list can keep a detail pane visible. |
Responsive changes should be the default. They preserve the same task and hierarchy while reducing awkward empty space, cramped labels, or oversized controls. Adaptive changes are more consequential: they can expose complementary information, tools, or navigation that help the user work faster.
Avoid expanding a single narrow column just because a window is wide. If the reading measure becomes uncomfortable or the task remains sequential, constrain the content width and keep the single-pane flow. Add a second pane only when showing more information at once has a clear product benefit.
Start from the window, not the device category
These questions are more useful than “is this a tablet?”
- What width and height does this destination actually receive after system bars, app chrome, and multi-window sizing?
- Is the extra space useful for another task-relevant surface, or would it simply stretch the existing one?
- Can the user change the window, rotate, or fold the device while this screen is open?
- Which state must survive when the arrangement changes?
Screen-level layout policy belongs near the app or destination boundary, where the full window is meaningful. Individual reusable components should instead respond to the constraints their parent supplies. A card in a tablet grid might be narrower than the same card on a phone, so using a global “tablet mode” inside the card would be wrong.
For that local case, BoxWithConstraints exposes the component’s available space. Use it to make a small, content-driven change—not to distribute app-wide device checks through every leaf composable.
@Composable
fun ProjectSummary(
project: ProjectUi,
modifier: Modifier = Modifier,
) {
Card(modifier = modifier) {
BoxWithConstraints(Modifier.padding(16.dp)) {
if (maxWidth < 360.dp) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
ProjectIcon(project)
ProjectText(project)
}
} else {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
ProjectIcon(project)
ProjectText(project)
}
}
}
}
}The value is not the exact 360.dp number. Choose a threshold by checking the icon, translated text, large font sizes, and tap targets. A threshold is successful when the layout on either side remains intentional, not when it matches a generic device category.
Let collections absorb width naturally
Grids are an excellent responsive tool because their repeated content can gain columns without changing the screen’s task. GridCells.Adaptive sets the smallest viable cell width; Compose then calculates how many columns fit and shares remaining room between them.
@Composable
fun SavedProjects(
projects: List<ProjectUi>,
onOpen: (String) -> Unit,
modifier: Modifier = Modifier,
) {
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 168.dp),
modifier = modifier,
contentPadding = PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
) {
items(
items = projects,
key = { it.id },
) { project ->
ProjectTile(
project = project,
onClick = { onOpen(project.id) },
)
}
}
}Choose the minimum from real content: image proportions, readable line length, badge widths, and the minimum interactive area. Do not pick a value that makes every card tiny merely to show more columns. The lazy-grid guide explains adaptive columns, stable item keys, and full-width spans in more depth; Android’s lazy lists and grids documentation also notes that maxLineSpan is valuable when an adaptive grid’s column count is not fixed.
Make structural changes earn their complexity
At a content level, a larger window can support a genuinely different arrangement. Good candidates include:
- A top-level navigation control that changes presentation while keeping the same destinations.
- A master-detail workflow where retaining both the current item and its details saves repeated navigation.
- A primary task with tools, filters, or metadata that are useful beside it rather than hidden behind another step.
The adaptive-navigation pattern is a good example. A compact window can use a bottom navigation bar, while a wider one can use a rail with the same selected destination and click behavior. NavigationSuiteScaffold is designed for that app-shell decision.
The content itself still needs its own evaluation. Changing a bar to a rail does not automatically make a single-pane feed, editor, or settings screen adaptive. Conversely, a two-pane design is not a reward for having a large display: it adds focus order, Back behavior, selection state, and more test cases. Use it where simultaneous context is clearly better than a focused single-pane sequence.
Keep state independent from the arrangement
A window can change while the user is reading, editing, or selecting an item. Treat that as a layout event, not as a new session.
Keep durable state in a scope that survives the visual switch:
- Put screen data and business state in the screen state holder or
ViewModel. - Keep the selected item ID, not a reference to a particular “left pane” or “detail pane.”
- Use
rememberSaveablefor small UI state that should survive recreation, such as a query, selected filter, or expanded section. - Hoist lazy-list or grid state when a parent must coordinate it; do not silently discard a reading position because columns changed.
- Derive the visual policy from the latest window information. Do not persist an
isTabletflag as user state.
For example, a user can select projectId = "42" in a compact list, then unfold the device. On a wider layout, that same ID may appear in a persistent detail pane. The selection has not changed; only its presentation has. The Android guidance specifically calls out hoisting information that might not be used at every size as a way to preserve state across a layout-size change.
Treat foldables as changing windows, not oversized phones
Foldables are a strong reason to avoid device-specific branches. The same device can be narrow while folded, broad while open, and may change posture without rotating. The foldables guide notes that a responsive adjustment alone may not be enough between folded and unfolded states; a different layout can be appropriate when the available size and aspect ratio change substantially.
Start with a layout that works on any rectangle. Then add fold-aware behavior only when folding features materially affect the task:
- Do not place essential content across an occluding hinge.
- In tabletop posture, consider whether controls belong in the lower region and content in the upper region.
- In book posture, check that a split layout does not put a continuous control or readable line directly under the fold.
- Keep a graceful single-pane fallback for devices without a reported folding feature.
This approach also covers resizable ChromeOS and desktop windows. Android 16 strengthens the expectation: on displays with a smallest width of at least 600dp, apps targeting API 36 have orientation, aspect-ratio, and resizability restrictions overridden to make room for adaptive layouts. See the Android orientation, aspect-ratio, and resizability guidance for the current rules.
Keep edge-to-edge responsibilities clear
More panes and changed navigation controls can alter the space your content receives, but they do not change the rules for system bars, cutouts, gestures, or the IME. Let background surfaces extend behind system UI when appropriate, and ensure the first and last actionable content remains reachable.
When using a Material 3 Scaffold, pass its innerPadding through once to the screen’s scrollable content. Do not fix a resize bug by stacking arbitrary inset padding on every nested pane. The edge-to-edge Compose guide shows how to assign each inset a clear owner.
Test the transitions, not just the endpoints
An adaptive screen can look correct in a static compact preview and a static wide preview yet fail while moving between them. Test the path a real user can take:
- Start in a compact window, select content, type a draft, scroll a collection, or open a sheet.
- Rotate, resize, enter split-screen, or fold/unfold while that state is active.
- Verify the selection, draft, scroll intent, focus, and Back behavior make sense in the new arrangement.
- Repeat with large font scaling, keyboard navigation where relevant, and system bars visible.
Use previews to spot hierarchy and spacing early, but also run on emulators or physical devices at representative sizes and postures. Screenshots or screenshot tests at those sizes are particularly useful for catching clipped panes, oversized content, and controls obscured by insets.
Common mistakes
Branching on “phone” and “tablet” everywhere
A tablet in split-screen and a wide phone in desktop windowing make those labels misleading. Derive app-level policy from current window information, and let components use their own constraints.
Stretching instead of reorganizing
Wider text columns, giant cards, and large empty margins rarely improve a task. Keep comfortable content widths, allow responsive grids, or introduce a useful adjacent surface.
Recreating UI state on a layout switch
Layout policy can change; the user’s work should not. Separate selected IDs, draft values, and navigation state from the composable branch that renders compact or wide UI.
Making a fold feature a required dependency
Hinges and postures are enhancements, not a replacement for a sound layout. A rectangular, responsive baseline remains necessary on every device and window.
FAQ
Should every screen have a two-pane layout on a tablet?
No. Use two panes when seeing two related pieces of information at once reduces navigation or improves the task. A focused reading page, form, or linear workflow may be better constrained to one pane on every window size.
Is BoxWithConstraints the right place to decide an app-wide layout?
Usually no. It is best for a component reacting to the space its parent offers. Use current window information for app- and destination-level layout policy so the decision is made once at the correct scope.
Do adaptive layouts replace accessibility work?
No. Every layout branch needs readable type, adequate contrast, logical focus order, reachable controls, and equivalent access to actions. Test these when the layout reflows; a second pane can introduce a new focus path even when the content is unchanged.