Skip to main content
Version: 2.x

Styles and Animations

Compose Destinations allows you to define different styles for your Composable screens.
These "styles" describe the way the Composable enters and leaves the screen or how it is shown.

Each destination can have one of these styles:

The way you can choose this is by passing the style argument to the @Destination annotation, example:

@Destination(style = DestinationStyle.Dialog::class)
@Composable
fun SomeScreen() { /*...*/ }
hint

If you have an animation that you need to apply at runtime, for example, if the animation depends on some state that is only known in the DestinationsNavHost level, you can override whatever style is set in the annotation. Read more in the Override the annotation style at runtime section.

Default Style

As you probably have guessed, this is the style that's going to be applied to all Destinations that don't explicitly use another style. Internally, it is similar to DestinationsStyle.Animated, except it takes animations from the destination's parent nav graph.

Animated Style

The animated style enables you to define custom animations for your screen transitions. You can subclass DestinationStyle.Animated class with an object class and define the enter and exit transitions with normal animation APIs.

Since compose 1.7, you'll also be able to override a sizeTransform as part of this class.

object ProfileTransitions : DestinationStyle.Animated() {

override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition? = {
//...
(expand to see the full ProfileTransitions.kt)
object ProfileTransitions : DestinationStyle.Animated() {

override val enterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition? = {
when (initialState.destination()) {
GreetingScreenDestination ->
slideInHorizontally(
initialOffsetX = { 1000 },
animationSpec = tween(700)
)
else -> null
}
}

override val exitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition? = {
when (targetState.destination()) {
GreetingScreenDestination ->
slideOutHorizontally(
targetOffsetX = { -1000 },
animationSpec = tween(700)
)
else -> null
}
}

override val popEnterTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition? = {
when (initialState.destination()) {
GreetingScreenDestination ->
slideInHorizontally(
initialOffsetX = { -1000 },
animationSpec = tween(700)
)
else -> null
}
}

override val popExitTransition: AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition? = {
when (targetState.destination()) {
GreetingScreenDestination ->
slideOutHorizontally(
targetOffsetX = { 1000 },
animationSpec = tween(700)
)
else -> null
}
}
}
@Destination(style = ProfileTransitions::class)
@Composable
fun AnimatedVisibilityScope.ProfileScreen() { /*...*/ }

Notice the AnimatedVisibilityScope receiver. This scope is available to all "non dialog" and "non bottom sheet" Composables in the nav graph.

Dialog Style

This style will make your Composable be displayed as a dialog above the previous screen.
DestinationStyle.Dialog is also a class you can extend to specify a DialogProperties field. This enables you to create specific configurations of Dialogs subclassing it with an object. Then you can pass that object's class to the style argument, for example:

object NonDismissableDialog : DestinationStyle.Dialog() {
override val properties = DialogProperties(
dismissOnClickOutside = false,
dismissOnBackPress = false,
)
}
@Destination(style = NonDismissableDialog::class)
@Composable
fun SomeDialogScreen() { /*...*/ }

If you declare the style as style = DestinationStyle.Dialog::class, then the default DialogProperties will be used.

BottomSheet Style

This style requires you to add io.github.raamcosta.compose-destinations:bottom-sheet dependency. The DestinationStyleBottomSheet is a simple object that you can use to set this style.

@Destination(style = DestinationStyleBottomSheet::class)
@Composable
fun ColumnScope.SomeBottomSheetScreen() { /*...*/ }

Notice the ColumnScope receiver. This is optional if you're using the bottom sheet style and the reason is that the bottom sheet is internally placed inside a Column, so you can potentially do things that are only possible within that type of scope without needing another Column.

Just as if you were working with Navigation-Material directly, you will need to wrap your top-most Composable with a ModalBottomSheetLayout.

val navController = rememberNavController()

val bottomSheetNavigator = rememberBottomSheetNavigator()
navController.navigatorProvider += bottomSheetNavigator

ModalBottomSheetLayout(
bottomSheetNavigator = bottomSheetNavigator,
// other configuration for you bottom sheet screens, like:
sheetShape = RoundedCornerShape(16.dp),
) {
// ...
DestinationsNavHost(
navController = navController
// ...
)
}

Override the annotation style at runtime

You might have some situations where animations at compile time, set on a different place is not ideal. For example, if your animation depends on some state that is available at the DestinationsNavHost call.

In that case, you can override whatever is set (or not set, if you didn't specify, which makes sense if you're going to override it anyway), like this:

DestinationsNavHost(
//...
) {
// same place you can manually call composables
MyDestination animateWith SomeAnimatedStyleObject

// OR more likely what you're looking for:
MyDestination.animateWith(
enterTransition = { /*...*/ },
exitTransition = { /*...*/ },
popEnterTransition = { /*...*/ },
popExitTransition = { /*...*/ },
sizeTransform = { /*...*/ },
)
}