androidandroid-activityandroid-jetpack-composematerial3

How do I change color of Android system navigation bar API 35+


I am unable to change color of system navigation bar. In dark mode it ineed appears as transparent and its fine (ideally I would want it to be the same color as bottom navigation bar), but for light mode, its just straight white. I know that since Android 35, there is some kind of protection layer on it, but when set as transparent, it should allow me to change its color, or at least be transparent. Has anyone managed to make it work?

I haven't checked how it behaves on API >35.

When inspecting in layout inspector, indeed the box appears where system navigation bars are, but doesn't apply any color.

My background fills entire screen including system/navigation bars, when I set this color for example for Yellow, it is correctly drawn over navigation bars and status bars, but only when in dark mode, in light mode it is still white.

Below is my code for MainActivity:

class MainActivity : AppCompatActivity() {
    private val startupViewModel: StartupViewModel by viewModel()
    private val inAppUpdateManager: InAppUpdateManager by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        installSplashScreen()
        super.onCreate(savedInstanceState)

        var themeSettings by mutableStateOf(
            ThemeSettings(
                darkTheme = false,
                dynamicColor = false
            )
        )

        inAppUpdateManager.check(this)

        lifecycleScope.launch {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                combine(
                    isSystemInDarkTheme(),
                    startupViewModel.state
                ) { systemDark, uiState ->
                    ThemeSettings(
                        darkTheme = when (uiState.theme) {
                            Theme.SYSTEM -> systemDark
                            Theme.LIGHT -> false
                            Theme.DARK -> true
                        },
                        dynamicColor = uiState.dynamicColors
                    )
                }.onEach { themeSettings = it }
                    .map { it.darkTheme }
                    .distinctUntilChanged()
                    .collect { darkTheme ->
                        enableEdgeToEdge(
                            statusBarStyle = SystemBarStyle.auto(
                                lightScrim = Color.TRANSPARENT,
                                darkScrim = Color.TRANSPARENT,
                            ) { darkTheme },
                            navigationBarStyle = SystemBarStyle.auto(
                                lightScrim = Color.TRANSPARENT,
                                darkScrim = Color.TRANSPARENT,
                            ) { darkTheme }
                        )
                    }
            }
        }

        setContent {
            Theme(
                darkTheme = themeSettings.darkTheme,
                dynamicColor = themeSettings.dynamicColor
            ) {
                val state = startupViewModel.state.collectAsStateWithLifecycle()
                Background {
                    if (state.value.isLoading) {
                        StartupScreen(
                            showSkip = { state.value.showSkip },
                            onSkip = { startupViewModel.skipFetching() }
                        )
                    } else {
                        NavigationRoot(startDestination = state.value.startDestination)
                    }
                }
            }
        }
    }

    override fun onResume() {
        super.onResume()
        inAppUpdateManager.onResume(this)
    }
}

private val lightScrim = Color.argb(0xe6, 0xFF, 0xFF, 0x00)
private val darkScrim = Color.argb(0xe6, 0xFF, 0xFF, 0x00)

@Immutable
private data class ThemeSettings(
    val darkTheme: Boolean,
    val dynamicColor: Boolean,
)

And here is where i set my background:

@Composable
fun Background(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit,
) {
    val primary = MaterialTheme.colorScheme.surface
    val secondary = MaterialTheme.colorScheme.surfaceContainer

    val gradient = remember(primary, secondary) {
        Brush.verticalGradient(colors = listOf(primary, secondary))
    }

    Box(
        modifier = modifier.fillMaxSize()
    ) {
        Surface(
            color = Color.Transparent,
            modifier = Modifier
                .matchParentSize()
                .background(gradient)
        ) {
            CompositionLocalProvider(LocalAbsoluteTonalElevation provides 0.dp) {
                content()
            }
        }
      
        if (Build.VERSION.SDK_INT >= 35) {
            Box(
                modifier = Modifier
                    .align(Alignment.BottomCenter)
                    .fillMaxWidth()
                    .zIndex(Float.MAX_VALUE)
                    .windowInsetsBottomHeight(WindowInsets.navigationBars)
                    .background(MaterialTheme.colorScheme.surfaceContainer)
            )
        }
    }
}

@Edit 29.07.2025

Below is my working solution (thanks Mofe Ejegi), which gives brush background while also changing color of system navigation bar together with icons in them.

@Composable
fun Background(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit,
) {
    val primary = MaterialTheme.colorScheme.surface
    val secondary = MaterialTheme.colorScheme.surfaceContainer

    val gradient = remember(primary, secondary) {
        Brush.verticalGradient(colors = listOf(primary, secondary))
    }

    Box(
        modifier = modifier
            .fillMaxSize()
    ) {
        Surface(
            color = Color.Transparent,
            modifier = modifier
                .fillMaxSize()
                .background(gradient)
        ) {
            CompositionLocalProvider(LocalAbsoluteTonalElevation provides 0.dp) {
                content()
            }
        }
        Box(
            modifier = Modifier
                .align(Alignment.BottomCenter)
                .fillMaxWidth()
                .windowInsetsBottomHeight(WindowInsets.navigationBars)
                .background(MaterialTheme.colorScheme.surfaceContainer)
        )
    }
}
class MainActivity : AppCompatActivity() {
    private val startupViewModel: StartupViewModel by viewModel()
    private val inAppUpdateManager: InAppUpdateManager by inject()

    private lateinit var updateLauncher: ActivityResultLauncher<IntentSenderRequest>

    override fun onCreate(savedInstanceState: Bundle?) {
        installSplashScreen()
        var themeSettings by mutableStateOf(
            ThemeSettings(
                darkTheme = false,
                dynamicColor = false
            )
        )

        lifecycleScope.launch {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                combine(
                    isSystemInDarkTheme(),
                    startupViewModel.state
                ) { systemDark, uiState ->
                    ThemeSettings(
                        darkTheme = when (uiState.theme) {
                            Theme.SYSTEM -> systemDark
                            Theme.LIGHT -> false
                            Theme.DARK -> true
                        },
                        dynamicColor = uiState.dynamicColors
                    )
                }.onEach { themeSettings = it }
                    .map { it.darkTheme }
                    .distinctUntilChanged()
                    .collect { darkTheme ->
                        enableEdgeToEdge(
                            navigationBarStyle = if (darkTheme) {
                                SystemBarStyle.dark(
                                    scrim = Color.TRANSPARENT
                                )
                            } else {
                                SystemBarStyle.light(
                                    scrim = Color.TRANSPARENT,
                                    darkScrim = Color.TRANSPARENT
                                )
                            },
                            statusBarStyle = if (darkTheme) {
                                SystemBarStyle.dark(
                                    scrim = Color.TRANSPARENT
                                )
                            } else {
                                SystemBarStyle.light(
                                    scrim = Color.TRANSPARENT,
                                    darkScrim = Color.TRANSPARENT
                                )
                            }
                        )
                    }
            }
        }

        super.onCreate(savedInstanceState)

        updateLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
            inAppUpdateManager.onUpdateResult(result, this)
        }
        inAppUpdateManager.setLauncher(updateLauncher, this)

        inAppUpdateManager.check(this)


        setContent {
            Theme(
                darkTheme = themeSettings.darkTheme,
                dynamicColor = themeSettings.dynamicColor
            ) {
                val state = startupViewModel.state.collectAsStateWithLifecycle()
                Background {
                    if (state.value.isLoading) {
                        StartupScreen(
                            showSkip = { state.value.showSkip },
                            onSkip = { startupViewModel.skipFetching() }
                        )
                    } else {
                        NavigationRoot(startDestination = state.value.startDestination)
                    }
                }
            }
        }
    }

    override fun onResume() {
        super.onResume()
        inAppUpdateManager.onResume(this)
    }
}

@Immutable
private data class ThemeSettings(
    val darkTheme: Boolean,
    val dynamicColor: Boolean,
)

Solution

  • So prior to Android API 35, the way to do this was to call

    window.navigationBarColor = yourNavBarColor.toArgb()
    

    However, window.setNavigationBarColor() is deprecated from API 35+.
    Since your approach doesn't reference that method, I assume you already know this.

    So you're already on the right track to achieving this in apps compiled with API 35 or higher.
    All that's left is just a few minor tweaks.

    In your MainActivity.kt, add the enableEdgeToEdge function to create a transparent scrim for the System navigation bar:

    class MainActivity : AppCompatActivity() {
        private val startupViewModel: StartupViewModel by viewModel()
        private val inAppUpdateManager: InAppUpdateManager by inject()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            installSplashScreen()
            super.onCreate(savedInstanceState)
    
            // Force the exact color by disabling automatic theming
            enableEdgeToEdge(
                navigationBarStyle = SystemBarStyle.dark(
                    scrim = android.graphics.Color.TRANSPARENT
                )
            )
            ...
        }
        
        ...
    }
    

    And in your Background Composable, you set the system navigation bar background with the appropriate height and color.

    @Composable
    fun Background(
        modifier: Modifier = Modifier,
        content: @Composable () -> Unit,
    ) {
        val navigationBarColor = Color.Yellow
    
        Box(
            modifier = modifier.fillMaxSize()
        ) {
            Surface(
                color = Color.White,
                modifier = Modifier.matchParentSize()
            ) {
                CompositionLocalProvider(LocalAbsoluteTonalElevation provides 0.dp) {
                    content()
                }
            }
    
            // Draw yellow background behind WindowInsets.Type.navigationBars()
            Box(
                modifier = Modifier
                    .align(Alignment.BottomCenter)
                    .fillMaxWidth()
                    .windowInsetsBottomHeight(WindowInsets.navigationBars)
                    .background(navigationBarColor) // Your exact yellow color
            )
        }
    }
    

    NOTE: