Edge To Edge

by android42dc2270e960Complete terms in LICENSE.txt7.7K starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 13 days ago

Use this skill to migrate your Jetpack Compose app to add adaptive edge-to-edge support and troubleshoot common issues. Use this skill to fix UI components (like buttons or lists) that are obscured by or overlapping with the navigation bar or status bar, fix IME insets, and fix system bar legibility.

Instructions onlySoftware Development
AI-generated overview

Guides migrating an Android Jetpack Compose app to adaptive edge-to-edge display and fixing system bar, IME and inset issues.

What it does
Provides a step-by-step procedure for adding edge-to-edge support to Android Jetpack Compose apps: planning per-Activity changes, calling enableEdgeToEdge, setting adjustResize, and applying window insets. It covers Scaffold and Material component inset handling, adaptive scaffolds, IME padding patterns with right and wrong code samples, navigation bar contrast and system bar icon legibility, lists, and full-screen dialogs. It ends with a verification checklist including running the Gradle build.
When to use it
Use it when migrating a Jetpack Compose app to edge-to-edge display, or when UI components such as buttons, lists or text fields are obscured by or overlap the navigation bar, status bar or IME. It also applies when system bar icons are hard to read against app content.
Requirements
An Android project using Jetpack Compose and targeting SDK 35 or later; no scripts are shipped, only instructions. Verification mentions running ./gradlew build.

Prerequisites

  • Project MUST use Android Jetpack Compose.
  • Project MUST target SDK 35 or later. If the SDK is lower than 35, increase the SDK to 35.

Step 1: plan

  1. Locate and analyze all Activity classes to detect which have existing edge-to-edge support. For every Activity without edge-to-edge, plan to make each Activity edge-to-edge.
  2. In each Activity, Locate and analyze all lists and FAB components to detect which have existing edge-to-edge support. For every component without edge-to-edge support, plan to make each of these components edge-to-edge.
  3. In each Activity, scan for TextField, OutlinedTextField, or BasicTextField. If found, then you MUST verify the IME doesn't hide the input field by following the IME section of this skill.

Step 2: add edge-to-edge support

  1. Add enableEdgeToEdge before setContent in onCreate in each Activity that does not already call enableEdgeToEdge.
  2. Add android:windowSoftInputMode="adjustResize" in the AndroidManifest.xml for all Activities that use a soft keyboard.

Step 3: apply insets

  • The app MUST apply system insets, or align content to rulers, so critical UI remains tappable. Choose only one method to avoid double padding:

    1. PREFERRED: When available, use Scaffolds and pass PaddingValues to the content lambda.
    kotlin
    Scaffold { innerPadding ->    // innerPadding accounts for system bars and any Scaffold components    LazyColumn(        modifier = Modifier            .fillMaxSize()            .consumeWindowInsets(innerPadding),        contentPadding = innerPadding    ) { /* Content */ }}
    <br />
    1. PREFERRED: When available, use the automatic inset handling or padding modifiers in material components.

      • Material 3 Components manages safe areas for its own components, including:
        • TopAppBar
        • SmallTopAppBar
        • CenterAlignedTopAppBar
        • MediumTopAppBar
        • LargeTopAppBar
        • BottomAppBar
        • ModalDrawerSheet
        • DismissibleDrawerSheet
        • PermanentDrawerSheet
        • ModalBottomSheet
        • NavigationBar
        • NavigationRail
      • For Material 2 Components, use the windowInsetsparameter to apply insets manually for BottomAppBar, TopAppBar and BottomNavigation. DO NOT apply padding to the parent container; instead, pass insets directly to the App Bar component. Applying padding to the parent container prevents the App Bar background from drawing into the system bar area. For example, for TopAppBar, choose only one of the following options:
        1. PREFERRED: TopAppBar(windowInsets = AppBarDefaults.topAppBarWindowInsets)
        2. TopAppBar(windowInsets = WindowInsets.systemBars.exclude(WindowInsets.navigationBars))
        3. TopAppBar(windowInsets = WindowInsets.systemBars.add(WindowInsets.captionBar))
    2. For components outside a Scaffold, use padding modifiers, such as Modifier.safeDrawingPadding() or Modifier.windowInsetsPadding(WindowInsets.safeDrawing).

      kotlin
      Box(    modifier = Modifier        .fillMaxSize()        .safeDrawingPadding()) {    Button(        onClick = {},        modifier = Modifier.align(Alignment.BottomCenter)    ) {        Text("Login")    }}
      <br />
    3. For deeply nested components with excessive padding, use WindowInsetsRulers (e.g. Modifier.fitInside(WindowInsetsRulers.SafeDrawing.current)). See the IME section for a code sample.

    4. When you need an element (e.g. a custom header or decorative scrim) to equal the dimensions of a system bar, use inset size modifiers (e.g. Modifier.windowInsetsTopHeight(WindowInsets.systemBars)). See the Lists section for a code sample.

Adaptive Scaffolds

  • NavigationSuiteScaffold manages safe areas for its own components, like the NavigationRail or NavigationBar. However, the adaptive scaffolds (e.g. NavigationSuiteScaffold, ListDetailPaneScaffold) don't propagate PaddingValues to their inner contents. You MUST apply insets to individual screens or components (e.g., list contentPadding or FAB padding) as described in Step 3 . DO NOT apply safeDrawingPadding or similar modifiers to the NavigationSuiteScaffold parent. This clips and prevents an edge-to-edge screen.

IME

  • For each Activity with a soft keyboard, check that android:windowSoftInputMode="adjustResize" is set in the AndroidManifest.xml. DO NOT use SOFT_INPUT_ADJUST_RESIZE because it is deprecated. Then, maintain focus on the input field. Choose one:
      1. PREFERRED: Add Modifier.fitInside(WindowInsetsRulers.Ime.current) to the content container. This is preferred over imePadding() because it reduces jank and extra padding caused by forgetting to consume insets upstream in the hierarchy.
      1. Add imePadding to the content container. The padding modifier MUST be placed before Modifier.verticalScroll(). Do NOT use Modifier.imePadding() if the parent already accounts for the IME with contentWindowInsets (e.g. contentWindowInsets = WindowInsets.safeDrawing). Doing so will cause double padding.

IMEs with Scaffolds code patterns

RIGHT

RIGHT because contentWindowInsets contains IME insets, which are passed to the content lambda as innerPadding.

kotlin
// RIGHTScaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->    Column(        modifier = Modifier            .padding(innerPadding)            .consumeWindowInsets(innerPadding)            .verticalScroll(rememberScrollState())    ) { /* Content */ }}
<br />

RIGHT because fitInside fits the content to the IME insets regardless of contentWindowInsets.

kotlin
// RIGHTScaffold() { innerPadding ->    Column(        modifier = Modifier            .padding(innerPadding)            .consumeWindowInsets(innerPadding)            .fitInside(WindowInsetsRulers.Ime.current)            .verticalScroll(rememberScrollState())    ) { /* Content */ }}
<br />

RIGHT because the default contentWindowInsets does not contain IME insets, and imePadding() applies IME insets:

kotlin
// RIGHTScaffold() { innerPadding ->    Column(        modifier = Modifier            .padding(innerPadding)            .consumeWindowInsets(innerPadding)            .imePadding()            .verticalScroll(rememberScrollState())    ) { /* Content */ }}
<br />
WRONG

WRONG because there will be excess padding when the IME opens. IME insets are applied twice, once with innerPadding, which contains IME insets from the passed contentWindowInsets values, and once with imePadding:

kotlin
// WRONGScaffold(contentWindowInsets = WindowInsets.safeDrawing) { innerPadding ->    Column(        modifier = Modifier            .padding(innerPadding)            .imePadding()            .verticalScroll(rememberScrollState())    ) { /* Content */ }}
<br />

WRONG because the IME will cover up the content. Scaffold's default contentWindowInsets does NOT contain IME insets.

kotlin
// WRONGScaffold() { innerPadding ->    Column(        modifier = Modifier            .padding(innerPadding)            .verticalScroll(rememberScrollState())    ) { /* Content */ }}
<br />

IMEs without Scaffolds code patterns

RIGHT

The following code samples WILL NOT cause excessive padding.

kotlin
// RIGHTBox(    // Insets consumed    modifier = Modifier.safeDrawingPadding() // or imePadding(), safeContentPadding(), safeGesturesPadding()) {    Column(        modifier = Modifier.imePadding()    ) { /* Content */ }}
<br />
kotlin
// RIGHTBox(    // Insets consumed    modifier = Modifier.windowInsetsPadding(WindowInsets.safeDrawing) // or WindowInsets.ime, WindowInsets.safeContent, WindowInsets.safeGestures) {    Column(        modifier = Modifier.imePadding()    ) { /* Content */ }}
<br />
kotlin
// RIGHTBox(    // Insets not consumed, but irrelevant due to fitInside    modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // or WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()) {    Column(        modifier = Modifier            .fillMaxSize()            .fitInside(WindowInsetsRulers.Ime.current)    ) { /* Content */ }}
<br />
WRONG

The following code sample WILL cause excessive padding because IME insets are applied twice:

kotlin
// WRONGBox(    // Insets not consumed    modifier = Modifier.padding(WindowInsets.safeDrawing.asPaddingValues()) // or WindowInsets.ime.asPaddingValues(), WindowInsets.safeContent.asPaddingValues(), WindowInsets.safeGestures.asPaddingValues()) {    Column(        modifier = Modifier.imePadding()    ) { /* Content */ }}
<br />

Navigation Bar Contrast & System Bar Icons

  • If the Activity uses enableEdgeToEdge from WindowCompat, you MUST set isAppearanceLightNavigationBars and isAppearanceLightStatusBars to the inverse of the device theme for apps that support light and dark theme so the system bar icons are legible. It's recommended to do this in your theme file. DO NOT do this if the Activities use enableEdgeToEdge from ComponentActivity because it handles the icon colors automatically.

    kotlin
    // Only use if calling `enableEdgeToEdge` from `WindowCompat`.// Apply to your theme file.@Composablefun MyTheme(    darkTheme: Boolean = isSystemInDarkTheme(),    content: @Composable () -> Unit) {    val view = LocalView.current    if (!view.isInEditMode) {        SideEffect {            val window = (view.context as? Activity)?.window ?: return@SideEffect            val controller = WindowCompat.getInsetsController(window, view)
                // Dark icons for Light Mode (!darkTheme), Light icons for Dark Mode            controller.isAppearanceLightStatusBars = !darkTheme            controller.isAppearanceLightNavigationBars = !darkTheme        }    }
        MaterialTheme(content = content)}
    <br />
  • If any screen uses a Scaffold or a NavigationSuiteScaffold with a bottom bar (e.g., BottomAppBar, NavigationBar), set window.isNavigationBarContrastEnforced = false in the corresponding Activity for SDK 29+. This prevents the system from adding a translucent background to the navigation bar, verifying your bottom bar colors extend to the bottom of the screen.

Lists

  • Apply inset padding (like Scaffold's innerPadding) to the contentPadding parameter of scrollable components (e.g. LazyColumn, LazyRow). DO NOT apply it as a Modifier.padding() to the list's parent container, as this clips the content and prevents it from scrolling behind the system bars.
  • Create a translucent composable covering the system bar so that the icons are still legible.
kotlin
class SystemBarProtectionSnippets : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)
        // enableEdgeToEdge sets window.isNavigationBarContrastEnforced = true        // which is used to add a translucent scrim to three-button navigation        enableEdgeToEdge()
        setContent {            MyTheme {                // Main content                MyContent()
                // After drawing main content, draw status bar protection                StatusBarProtection()            }        }    }}
@Composableprivate fun StatusBarProtection(    color: Color = MaterialTheme.colorScheme.surfaceContainer,) {    Spacer(        modifier = Modifier            .fillMaxWidth()            .height(                with(LocalDensity.current) {                    (WindowInsets.statusBars.getTop(this) * 1.2f).toDp()                }            )            .background(                brush = Brush.verticalGradient(                    colors = listOf(                        color.copy(alpha = 1f),                        color.copy(alpha = 0.8f),                        Color.Transparent                    )                )            )    )}
<br />

Dialogs

If both the following conditions are true, then the Dialog is full screen and must be made edge-to-edge:

  1. The DialogProperties contains usePlatformDefaultWidth = false.
  2. The Dialog calls Modifier.fillMaxSize().

To make a full screen Dialog edge-to-edge, set decorFitsSystemWindows = false in the DialogProperties.

kotlin
Dialog(    onDismissRequest = { /* Handle dismiss */ },    properties = DialogProperties(        // 1. Allows the dialog to span the full width of the screen        usePlatformDefaultWidth = false,        // 2. Allows the dialog to draw behind status and navigation bars        decorFitsSystemWindows = false    )) { /* Content */ }
<br />

Checklist

  • [ ] Does every Activity call enableEdgeToEdge()?
  • [ ] Is adjustResize set in the AndroidManifest.xml?
  • [ ] Does every TextField, OutlinedTextField, or BasicTextField have a parent with imePadding(), fitInside, Modifier.safeDrawingPadding(), Modifier.safeContentPadding(), Modifier.safeGesturesPadding(), or contentWindowInsets set to WindowInsets.safeDrawing or WindowInsets.ime?
  • [] Does the first and last list item draw away from the system bars by passing insets to contentPadding?
  • [] Do FABs draw above the navigation bars by either being inside a Scaffold or by applying Modifier.safeDrawingPadding()?
  • [] Does the project build? Run ./gradlew build to be sure.

Source and attribution

Source:android/skillsinsystem/edge-to-edgeat commit42dc227

License: Complete terms in LICENSE.txt

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal

More from android/skills

Wear Compose M3

android

Guides building, updating and migrating Wear OS Compose Material3 apps, with canonical component samples and version rules.

Software Development7.7Kupdated 13 days ago

Display Glasses With Jetpack Compose Glimmer

android

Provides guidelines for developing projected Android XR apps for display glasses using the Jetpack Compose Glimmer UI toolkit. This skill covers foundational Glimmer design principles, workflows for implementing Jetpack Compose Glimmer, and interaction models for the glasses form factor. Use this skill to build an Android XR Augmented Experience app with Jetpack Compose Glimmer that adheres to the Glimmer design system for optimized glasses styling.

Awaiting classification7.7Kupdated 13 days ago

Play Billing Library Version Upgrade

android

Guides upgrading an Android project from a legacy Google Play Billing Library version to the latest stable release.

Software Development7.7Kupdated 13 days ago

Leanback To Compose Tv Migration

android

Provides instructions and architectural patterns for migrating Android TV applications from legacy Leanback UI Toolkit, Android Views, or Support Fragments to Jetpack Compose for TV (androidx.tv). Use this skill for Leanback to Compose migrations, including browse screen, settings screen, authentication screen, login screen, or video playback screen migrations, or when replacing BrowseSupportFragment, LeanbackSettingsFragment, PreferenceFragment, BaseLeanbackPreferenceFragmentCompat, VideoSupportFragment, GuidedStepSupportFragment, SearchSupportFragment, VerticalGridSupportFragment, Presenter, ArrayObjectAdapter, or CursorMapper with modern Compose equivalents, implementing immersive carousels with focus memory, Media3 video playback with PlayerSurface, or custom 10-foot hero layouts.

Awaiting classification7.7Kupdated 13 days ago

Testing Setup

android

Analyzes an Android project's testing setup and installs testing frameworks, harnesses and tests for unit, UI, screenshot and end-to-end coverage.

Software Development7.7Kupdated 13 days ago

Android Profiler

android

Guides Android performance profiling: recording system traces, heap dumps and method recordings, and analyzing them with Perfetto SQL.

Software Development7.7Kupdated 13 days ago