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,
)
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:
There's no need to perform the version check anymore, this works across multiple API levels. I tested API 30 and upwards, but it should also work for API 29.
I used SystemBarStyle.dark so I can consistently get the required color. SystemBarStyle.auto seems to create some light/dark variants of the color depending on the theme.
On API 30 - 34, using SystemBarStyle.dark provided white navigation icons and SystemBarStyle.light gave dark nav icons. On API 35, it had no effect and was only dependent on the theme.