๐ŸŽจ Jetpack Compose

Adaptive UI for Tablets and Foldable Devices

Android runs on phones, tablets, foldables, Chromebooks and desktop windows, and the window can change size at any time. Adaptive UI means designing for window size rather than device type. Here's how to do it in Compose with Google's adaptive libraries.

โœ๏ธ Yunok Tech ๐Ÿ“… Updated โฑ 3 min read ๐ŸŽฏ Intermediate
On this page
  1. Core principles
  2. Window size classes
  3. Adaptive navigation with NavigationSuiteScaffold
  4. Canonical layout: list-detail
  5. Adapting content, not just structure
  6. Testing adaptive UI
  7. Real-world examples
  8. Common mistakes
  9. Frequently asked questions
  10. Conclusion

A phone layout stretched across a 12-inch tablet shows lines of text 80 characters wide, a lonely list, and a bottom bar far from the user's thumbs. Many Android users are on tablets, foldables and Chromebooks, and multi-window means even phone apps can be resized. Adaptive layouts fix this by responding to available window space.

Core principles

  • Design for window size, not device. A foldable's inner screen, a tablet in split-screen and a Chromebook window are all just "a window of size X".
  • Don't lock orientation or resizability unless absolutely necessary. Recent Android versions increasingly ignore such restrictions on large screens.
  • Keep state across resizes. Unfolding or rotating triggers a configuration change, and the user's place must survive it.

Window size classes

Window size classes are breakpoints that bucket the window into compact, medium and expanded (with further large and extra-large buckets in recent versions) for width and height:

Width size classes and typical layouts Compact width, under 600dp, typical of phones in portrait: single pane with bottom navigation bar. Medium width, 600 to 840dp, such as foldables or small tablets: navigation rail, optional second pane. Expanded width, 840dp and above, tablets and desktops: navigation rail or drawer with list-detail two panes. Compact< 600dp1 pane + bottom bar Medium600โ€“840dprail + 1 (or 2) panes Expandedโ‰ฅ 840dprail + list-detail
Breakpoints describe the window, not the hardware. The same device can move between classes as it folds or enters split-screen.
Reading the window size classKotlin
@Composable
fun AppRoot() {
    val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
    val showTwoPanes = windowSizeClass.isWidthAtLeastBreakpoint(WindowSizeClass.WIDTH_DP_EXPANDED_LOWER_BOUND)
    InboxScreen(twoPane = showTwoPanes)
}

The exact helper names in the Material 3 adaptive library have changed between versions, so check the current API reference when you add the dependency.

AppScaffold.ktKotlin
enum class TopLevel(val label: String, val icon: ImageVector) {
    HOME("Home", Icons.Default.Home), SEARCH("Search", Icons.Default.Search), PROFILE("Profile", Icons.Default.Person)
}

@Composable
fun AppScaffold() {
    var current by rememberSaveable { mutableStateOf(TopLevel.HOME) }
    // Bottom bar on compact, navigation rail on medium/expanded: chosen automatically.
    NavigationSuiteScaffold(
        navigationSuiteItems = {
            TopLevel.entries.forEach { dest ->
                item(
                    selected = dest == current,
                    onClick = { current = dest },
                    icon = { Icon(dest.icon, contentDescription = null) },
                    label = { Text(dest.label) },
                )
            }
        },
    ) {
        when (current) {
            TopLevel.HOME -> HomeScreen()
            TopLevel.SEARCH -> SearchScreen()
            TopLevel.PROFILE -> ProfileScreen()
        }
    }
}

Canonical layout: list-detail

On a phone, tapping an email opens it full screen. On a tablet, the list stays on the left and the email shows on the right. Material 3 adaptive provides ListDetailPaneScaffold and a navigator that handles single-pane versus two-pane behaviour and back navigation for you:

InboxListDetail.ktKotlin
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun InboxListDetail(messages: List<Message>) {
    val navigator = rememberListDetailPaneScaffoldNavigator<String>()   // content key: message id
    val scope = rememberCoroutineScope()

    BackHandler(navigator.canNavigateBack()) { scope.launch { navigator.navigateBack() } }

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            AnimatedPane {
                MessageList(messages, onOpen = { id ->
                    scope.launch { navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, id) }
                })
            }
        },
        detailPane = {
            AnimatedPane {
                navigator.currentDestination?.contentKey?.let { id -> MessageDetail(id) }
                    ?: EmptyDetailPlaceholder()
            }
        },
    )
}

Some navigator functions are suspend in recent versions, hence the coroutine scope. Follow the API reference for your version.

Adapting content, not just structure

  • Constrain reading width: limit text columns, for example Modifier.widthIn(max = 640.dp), so long lines stay readable.
  • Grids: LazyVerticalGrid(GridCells.Adaptive(minSize = 180.dp)) adds columns as width grows.
  • Input: support keyboard, mouse hover and stylus on tablets and Chromebooks, such as keyboard shortcuts and hover states.
  • Foldables: Jetpack WindowManager reports fold position and posture (for example tabletop), so you can avoid placing content across the hinge.

Testing adaptive UI

  • Use resizable emulators and the foldable and tablet emulator profiles.
  • Add previews for different device sizes (@Preview(device = Devices.TABLET)) and screenshot-test them.
  • Test fold and unfold and multi-window transitions to confirm state is preserved.

Real-world examples

Email or chat

List-detail on expanded widths, single pane on phones, with the selection preserved when the device unfolds.

Shopping

An adaptive product grid with filters in a side pane on tablets and a bottom sheet on phones.

Media

Tabletop posture on foldables: video on the top half, controls on the bottom.

Common mistakes

  • Checking "is tablet" from device characteristics instead of window size.
  • Locking portrait orientation for the whole app.
  • Losing selection or scroll state on fold or unfold.
  • Stretching phone layouts edge to edge on wide screens.

Frequently asked questions

Do I need separate layouts for each device?

No. Use a few window-size-based layouts (one, two or three panes) and adaptive components. They cover phones, foldables, tablets and desktops.

Does Play reward large-screen support?

Google publishes large-screen quality guidelines, and Play can surface well-optimised apps to large-screen users. Review the guidelines for current tiers.

Does Navigation 3 help with adaptive layouts?

Yes. Its scene strategies can display multiple back stack entries side by side, which suits list-detail. See Navigation in Compose.

Conclusion

Adaptive UI is about window size, not device type: read the window size class, switch navigation with NavigationSuiteScaffold, use canonical layouts like list-detail, constrain content width, and preserve state through resizes and folds.

Next step: structure the app behind those screens with MVVM Architecture in Android.

Official references

๐Ÿ“ฑ Apps built by Yunok Tech

Real Android apps on Google Play, free to download.

See all our apps โ†’

Ready to build real Android applications?

Jetpack Compose clicks once you build real screens with real state. Practise modern Android UI development on projects that go beyond toy examples.

Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.

Explore the Android course and take your next step as an Android developer.

Share: LinkedIn X / Twitter WhatsApp