An Android Jetpack Compose project: a runnable set of samples showing what it takes to actually
own a Compose Dialog: its scrim, its animation, and its back-gesture behavior, instead of
accepting the platform's dim overlay and platform window animation.
Companion article: Own Your Dialog: Taking Control of Compose's Dialog Window
Compose's Dialog opens its own Android Window. That window comes with two things you
didn't ask for and can't easily override from inside the composable: a platform dim scrim
(a flat black overlay, not a themeable color) and a platform window enter/exit animation.
Both happen outside Compose's control, so no Modifier, no animate*AsState, no
Transition you write inside the dialog's content can touch them.
The fix is one function, called first thing inside the dialog's content:
@Composable
internal fun SetDialogWindow() {
val view = LocalView.current
val activityWindow = LocalActivity.current?.window
val dialogWindow = (view.parent as? DialogWindowProvider)?.window
dialogWindow?.setDimAmount(0f)
dialogWindow?.setWindowAnimations(0)
if (dialogWindow != null && activityWindow != null) {
SideEffect {
WindowInsetsControllerCompat(dialogWindow, dialogWindow.decorView).isAppearanceLightStatusBars =
WindowInsetsControllerCompat(activityWindow, activityWindow.decorView).isAppearanceLightStatusBars
}
}
}setDimAmount(0f) kills the platform scrim. setWindowAnimations(0) kills the platform
window animation. The status-bar sync keeps the dialog's own window from flashing a
different icon color than the host Activity's when it opens. Everything after that —
scrim color, fade, scale, slide, how a back gesture reverses it — is Compose code you write
and fully control.
dialog/ the reusable pieces: window neutralization, scrim, dialog shapes, predictive back
samples/ one screen per sample, each demonstrating one dimension of the technique
OwnedDialog.kt—SetDialogWindow(),OwnedDialogProperties, andOwnedDialog: a centered, scale-and-fade card. Two overloads —visible: Booleanfor direct use inside a screen, and a rawscrimAlpha: () -> Float, cardScale: () -> Floatversion driven by whoever hosts it (a nav3Scene, in this project).DialogScrim.kt— the scrim every shape shares: fades with the animation, exposes a real accessibility action for tap-to-dismiss, and theverticalSlidelayout helper that offsets a card by a fraction of its own measured height (no hardcoded pixel travel distances).OwnedDialogScene.kt—OwnedDialogSceneStrategy, a nav3SceneStrategythat renders a back-stack entry as anOwnedDialogoverlay instead of a normal destination.OwnedDialogPredictiveBack.kt— bridges the dialog window's own predictive-back dispatcher (aDialogopens a separateWindowwith its ownOnBackInvokedDispatcher, so the host Activity's back handling never sees the gesture) and exposesonBackProgress/onBackCancelled/onBackCompletedso a caller can reverse its own animation live, mid-drag.OwnedDialogWithPredictiveBack.kt,OwnedFloatingBottomDialog.kt,OwnedBottomSheetDialog.kt— the same window/scrim/back-gesture plumbing, reused across three different card shapes and positions.
Each sample is a screen reachable from the home screen, showing one specific combination.
| # | Sample | What it shows |
|---|---|---|
| 01 | Rendered in place | A screen holds a boolean and calls OwnedDialog(visible, ...) directly. Nav3 never sees it. |
| 02 | In place, with predictive back | Same boolean-driven dialog, but a back gesture drag reverses the scrim fade and card scale live. |
| 03 | Rendered as a destination | A NavKey on the nav3 back stack. OwnedDialogSceneStrategy renders it as a dialog overlay, with the same live predictive-back reversal. |
| 04 | Floating dialog, slide from bottom | A different shape: anchored to the bottom with margin on every side, sliding in by its own height instead of scaling from the center. Predictive-back aware. |
| 05 | Simulated bottom sheet | Edge to edge, flush with the bottom, rounded only on top — no ModalBottomSheet, just OwnedDialogProperties, a bottom-anchored Box, and verticalSlide. Predictive-back aware. |
01 · Rendered in place![]() |
02 · Predictive back![]() |
03 · As a destination![]() |
04 · Floating, slide from bottom![]() |
05 · Simulated bottom sheet![]() |
./gradlew :app:installDebug
Or open the project in Android Studio and run the app configuration. Each sample screen
has a button that shows its dialog; samples 02, 03, and 05 additionally react to a real
system back gesture — drag from the edge and release early to cancel, the dialog snaps back
open instead of dismissing.




