Shared element transitions are a seamless way to transition between composables that have content that is consistent between them. They are often used for navigation, allowing you to visually connect different screens as a user navigates between them.

For example, in the following video, you can see the image and title of the snack are shared from the listing page, to the detail page.
**Figure 1.**Jetsnack shared element demo.

In Compose, there are a few high level APIs that help you create shared elements:

- [`SharedTransitionLayout`](https://developer.android.com/reference/kotlin/androidx/compose/animation/package-summary#SharedTransitionLayout(androidx.compose.ui.Modifier,kotlin.Function1)): The outermost layout required to implement shared element transitions. It provides a`SharedTransitionScope`. Composables need to be in a`SharedTransitionScope`to use the shared element modifiers.
- [`Modifier.sharedElement()`](https://developer.android.com/develop/ui/compose/animation/shared-elements#basic-usage): The modifier that flags to the`SharedTransitionScope`the composable that should be matched with another composable.
- [`Modifier.sharedBounds()`](https://developer.android.com/develop/ui/compose/animation/shared-elements#shared-bounds): The modifier that flags to the`SharedTransitionScope`that this composable's bounds should be used as the container bounds for where the transition should take place. In contrast to`sharedElement()`,`sharedBounds()`is designed for visually different content.

An important concept when creating shared elements in Compose is how they work with overlays and clipping. See[the clipping and overlays](https://developer.android.com/develop/ui/compose/animation/shared-elements/customize#clip-overlays)section to learn more about this important topic.

## Basic usage

The following transition will be built in this section, transitioning from the smaller "list" item, to the larger detailed item:
![](https://developer.android.com/static/develop/ui/compose/images/animations/shared-element/basic_shared_element_jetsnack.gif)**Figure 2.**Basic example of a shared element transition between two composables.

The best way to use`Modifier.sharedElement()`is in conjunction with`AnimatedContent`,[`AnimatedVisibility`](https://developer.android.com/develop/ui/compose/animation/shared-elements#animated-visibility), or[`NavHost`](https://developer.android.com/develop/ui/compose/animation/shared-elements/navigation), as this manages the transition between composables automatically for you.

The starting point is an existing basic`AnimatedContent`that has a`MainContent`, and`DetailsContent`composable before adding shared elements:
![](https://developer.android.com/static/develop/ui/compose/images/animations/shared-element/basic_no_animation_jetsnack.gif)**Figure 3.** Starting`AnimatedContent`without any shared element transitions.

<br />

1. To make the shared elements animate between the two layouts, surround the`AnimatedContent`composable with`SharedTransitionLayout`. The scopes from[`SharedTransitionLayout`](https://developer.android.com/reference/kotlin/androidx/compose/animation/package-summary#SharedTransitionLayout(androidx.compose.ui.Modifier,kotlin.Function1))and`AnimatedContent`are passed to the`MainContent`and`DetailsContent`:

   <br />

   ```kotlin
   var showDetails by remember {
       mutableStateOf(false)
   }
   SharedTransitionLayout {
       AnimatedContent(
           showDetails,
           label = "basic_transition"
       ) { targetState ->
           if (!targetState) {
               MainContent(
                   onShowDetails = {
                       showDetails = true
                   },
                   animatedVisibilityScope = this@AnimatedContent,
                   sharedTransitionScope = this@SharedTransitionLayout
               )
           } else {
               DetailsContent(
                   onBack = {
                       showDetails = false
                   },
                   animatedVisibilityScope = this@AnimatedContent,
                   sharedTransitionScope = this@SharedTransitionLayout
               )
           }
       }
   }https://github.com/android/snippets/blob/95aeebd507b29719a9e7d5a839f101bbbe42ea72/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/BasicSharedElementSnippets.kt#L66-L92
   ```

   <br />

2. Add[`Modifier.sharedElement()`](https://developer.android.com/reference/kotlin/androidx/compose/animation/SharedTransitionScope#(androidx.compose.ui.Modifier).sharedElement(androidx.compose.animation.SharedTransitionScope.SharedContentState,androidx.compose.animation.AnimatedVisibilityScope,androidx.compose.animation.BoundsTransform,androidx.compose.animation.SharedTransitionScope.PlaceHolderSize,kotlin.Boolean,kotlin.Float,androidx.compose.animation.SharedTransitionScope.OverlayClip))to your composable modifier chain on the two composables that match. Create a`SharedContentState`object and remember it with[`rememberSharedContentState()`](https://developer.android.com/reference/kotlin/androidx/compose/animation/SharedTransitionScope#rememberSharedContentState(kotlin.Any)). The`SharedContentState`object is storing the unique key which determines the elements that are shared. Provide a unique key to identify the content, and use`rememberSharedContentState()`for the item to be remembered. The`AnimatedContentScope`is passed into the modifier, which is used to coordinate the animation.

   <br />

   ```kotlin
   @Composable
   private fun MainContent(
       onShowDetails: () -> Unit,
       modifier: Modifier = Modifier,
       sharedTransitionScope: SharedTransitionScope,
       animatedVisibilityScope: AnimatedVisibilityScope
   ) {
       Row(
           // ...
       ) {
           with(sharedTransitionScope) {
               Image(
                   painter = painterResource(id = R.drawable.cupcake),
                   contentDescription = "Cupcake",
                   modifier = Modifier
                       .sharedElement(
                           rememberSharedContentState(key = "image"),
                           animatedVisibilityScope = animatedVisibilityScope
                       )
                       .size(100.dp)
                       .clip(CircleShape),
                   contentScale = ContentScale.Crop
               )
               // ...
           }
       }
   }

   @Composable
   private fun DetailsContent(
       modifier: Modifier = Modifier,
       onBack: () -> Unit,
       sharedTransitionScope: SharedTransitionScope,
       animatedVisibilityScope: AnimatedVisibilityScope
   ) {
       Column(
           // ...
       ) {
           with(sharedTransitionScope) {
               Image(
                   painter = painterResource(id = R.drawable.cupcake),
                   contentDescription = "Cupcake",
                   modifier = Modifier
                       .sharedElement(
                           rememberSharedContentState(key = "image"),
                           animatedVisibilityScope = animatedVisibilityScope
                       )
                       .size(200.dp)
                       .clip(CircleShape),
                   contentScale = ContentScale.Crop
               )
               // ...
           }
       }
   }https://github.com/android/snippets/blob/95aeebd507b29719a9e7d5a839f101bbbe42ea72/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/BasicSharedElementSnippets.kt#L204-L299
   ```

   <br />

To get information on if a shared element match has occurred, extract`rememberSharedContentState()`into a variable, and query`isMatchFound`.
| **Important:** The[order of where this modifier](https://developer.android.com/develop/ui/compose/animation/shared-elements#modifier-ordering)is placed in the modifier chain is important. Put anything**you don't want to be shared before** the**`sharedElement()`**in the modifier chain.

This results in the following automatic animation:
![](https://developer.android.com/static/develop/ui/compose/images/animations/shared-element/basic_shared_element_jetsnack.gif)**Figure 4.**Basic example of a shared element transition between two composables.

You may notice that the background color and size of the whole container still uses the default`AnimatedContent`settings.

## Shared bounds versus shared element

[`Modifier.sharedBounds()`](https://developer.android.com/reference/kotlin/androidx/compose/animation/SharedTransitionScope#(androidx.compose.ui.Modifier).sharedBounds(androidx.compose.animation.SharedTransitionScope.SharedContentState,androidx.compose.animation.AnimatedVisibilityScope,androidx.compose.animation.EnterTransition,androidx.compose.animation.ExitTransition,androidx.compose.animation.BoundsTransform,androidx.compose.animation.SharedTransitionScope.PlaceHolderSize,kotlin.Boolean,kotlin.Float,androidx.compose.animation.SharedTransitionScope.OverlayClip))is similar to`Modifier.sharedElement()`. However, the modifiers are different in the following ways:

- `sharedBounds()`is for content that is visually different but should share the same area between states, whereas`sharedElement()`expects the content to be the same.
- With`sharedBounds()`, the content entering and exiting the screen is visible during the transition between the two states, whereas with`sharedElement()`only the target content is rendered in the transforming bounds.`Modifier.sharedBounds()`has`enter`and`exit`parameters for specifying how the content should transition, similar to how`AnimatedContent`works.
- The most common use case for`sharedBounds()`is the[container transform pattern](https://m3.material.io/styles/motion/transitions/transition-patterns#b67cba74-6240-4663-a423-d537b6d21187), whereas for`sharedElement()`the example use case is a hero transition.
- When using`Text`composables,`sharedBounds()`is preferred to support font changes such as transitioning between italic and bold or color changes.

From the previous example, adding`Modifier.sharedBounds()`onto the`Row`and`Column`in the two different scenarios will allow us to share the bounds of the two and perform the transition animation, allowing them to grow between each other:

<br />

```kotlin
@Composable
private fun MainContent(
    onShowDetails: () -> Unit,
    modifier: Modifier = Modifier,
    sharedTransitionScope: SharedTransitionScope,
    animatedVisibilityScope: AnimatedVisibilityScope
) {
    with(sharedTransitionScope) {
        Row(
            modifier = Modifier
                .padding(8.dp)
                .sharedBounds(
                    rememberSharedContentState(key = "bounds"),
                    animatedVisibilityScope = animatedVisibilityScope,
                    enter = fadeIn(),
                    exit = fadeOut(),
                    resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds()
                )
                // ...
        ) {
            // ...
        }
    }
}

@Composable
private fun DetailsContent(
    modifier: Modifier = Modifier,
    onBack: () -> Unit,
    sharedTransitionScope: SharedTransitionScope,
    animatedVisibilityScope: AnimatedVisibilityScope
) {
    with(sharedTransitionScope) {
        Column(
            modifier = Modifier
                .padding(top = 200.dp, start = 16.dp, end = 16.dp)
                .sharedBounds(
                    rememberSharedContentState(key = "bounds"),
                    animatedVisibilityScope = animatedVisibilityScope,
                    enter = fadeIn(),
                    exit = fadeOut(),
                    resizeMode = SharedTransitionScope.ResizeMode.ScaleToBounds()
                )
                // ...

        ) {
            // ...
        }
    }
}https://github.com/android/snippets/blob/95aeebd507b29719a9e7d5a839f101bbbe42ea72/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/SharedBoundsSnippets.kt#L89-L199
```

<br />

**Figure 5.**Shared bounds between two composables.

## Understand scopes

To use`Modifier.sharedElement()`, the composable needs to be in a`SharedTransitionScope`. The`SharedTransitionLayout`composable provides the`SharedTransitionScope`. Make sure to place at the same top-level point in your UI hierarchy that contains the elements you want to share.

Generally, the composables should also be placed inside an`AnimatedVisibilityScope`. This is typically provided by using`AnimatedContent`to switch between composables or when using`AnimatedVisibility`directly, or by the[`NavHost`](https://developer.android.com/develop/ui/compose/animation/shared-elements/navigation)composable function, unless you[manage the visibility manually](https://developer.android.com/develop/ui/compose/animation/shared-elements#managing-visibility). In order to use multiple scopes, save your required scopes in a[`CompositionLocal`](https://developer.android.com/develop/ui/compose/compositionlocal), use[context receivers in Kotlin](https://github.com/Kotlin/KEEP/blob/master/proposals/context-receivers.md), or pass the scopes as parameters to your functions.

Use`CompositionLocals`in the scenario where you have multiple scopes to keep track of, or a deeply nested hierarchy. A`CompositionLocal`lets you choose the exact scopes to save and use. On the other hand, when you use context receivers, other layouts in your hierarchy might accidentally override the provided scopes. For example, if you have multiple nested`AnimatedContent`, the scopes could be overridden.

<br />

```kotlin
val LocalNavAnimatedVisibilityScope = compositionLocalOf<AnimatedVisibilityScope?> { null }
val LocalSharedTransitionScope = compositionLocalOf<SharedTransitionScope?> { null }

@Composable
private fun SharedElementScope_CompositionLocal() {
    // An example of how to use composition locals to pass around the shared transition scope, far down your UI tree.
    // ...
    SharedTransitionLayout {
        CompositionLocalProvider(
            LocalSharedTransitionScope provides this
        ) {
            // This could also be your top-level NavHost as this provides an AnimatedContentScope
            AnimatedContent(state, label = "Top level AnimatedContent") { targetState ->
                CompositionLocalProvider(LocalNavAnimatedVisibilityScope provides this) {
                    // Now we can access the scopes in any nested composables as follows:
                    val sharedTransitionScope = LocalSharedTransitionScope.current
                        ?: throw IllegalStateException("No SharedElementScope found")
                    val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current
                        ?: throw IllegalStateException("No AnimatedVisibility found")
                }
                // ...
            }
        }
    }
}https://github.com/android/snippets/blob/95aeebd507b29719a9e7d5a839f101bbbe42ea72/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/BasicSharedElementSnippets.kt#L448-L480
```

<br />

Alternatively, if your hierarchy isn't deeply nested you can pass the scopes down as parameters:

<br />

```kotlin
@Composable
fun MainContent(
    animatedVisibilityScope: AnimatedVisibilityScope,
    sharedTransitionScope: SharedTransitionScope
) {
}

@Composable
fun Details(
    animatedVisibilityScope: AnimatedVisibilityScope,
    sharedTransitionScope: SharedTransitionScope
) {
}https://github.com/android/snippets/blob/95aeebd507b29719a9e7d5a839f101bbbe42ea72/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/BasicSharedElementSnippets.kt#L485-L497
```

<br />

## Shared elements with`AnimatedVisibility`

Previous examples showed how to use shared elements with`AnimatedContent`, but shared elements work with`AnimatedVisibility`too.

For example, in this lazy grid example, each element is wrapped in`AnimatedVisibility`. When the item is clicked on, the content has the visual effect of being pulled out of the UI into a dialog-like component.

<br />

```kotlin
var selectedSnack by remember { mutableStateOf<Snack?>(null) }

SharedTransitionLayout(modifier = Modifier.fillMaxSize()) {
    LazyColumn(
        // ...
    ) {
        items(listSnacks) { snack ->
            AnimatedVisibility(
                visible = snack != selectedSnack,
                enter = fadeIn() + scaleIn(),
                exit = fadeOut() + scaleOut(),
                modifier = Modifier.animateItem()
            ) {
                Box(
                    modifier = Modifier
                        .sharedBounds(
                            sharedContentState = rememberSharedContentState(key = "${snack.name}-bounds"),
                            // Using the scope provided by AnimatedVisibility
                            animatedVisibilityScope = this,
                            clipInOverlayDuringTransition = OverlayClip(shapeForSharedElement)
                        )
                        .background(Color.White, shapeForSharedElement)
                        .clip(shapeForSharedElement)
                ) {
                    SnackContents(
                        snack = snack,
                        modifier = Modifier.sharedElement(
                            sharedContentState = rememberSharedContentState(key = snack.name),
                            animatedVisibilityScope = this@AnimatedVisibility
                        ),
                        onClick = {
                            selectedSnack = snack
                        }
                    )
                }
            }
        }
    }
    // Contains matching AnimatedContent with sharedBounds modifiers.
    SnackEditDetails(
        snack = selectedSnack,
        onConfirmClick = {
            selectedSnack = null
        }
    )
}https://github.com/android/snippets/blob/95aeebd507b29719a9e7d5a839f101bbbe42ea72/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/AnimatedVisibilitySharedElementSnippets.kt#L81-L132
```

<br />

**Figure 6.** Shared elements with`AnimatedVisibility`.

## Modifier ordering

With`Modifier.sharedElement()`and`Modifier.sharedBounds()`, the[order of your modifier](https://developer.android.com/develop/ui/compose/modifiers#order-modifier-matters)chain matters, as with the rest of Compose. The incorrect placement of size-affecting modifiers can cause unexpected visual jumps during shared element matching.

For example, if you place a padding modifier in a different position on two shared elements, there is a visual difference in the animation.

<br />

```kotlin
var selectFirst by remember { mutableStateOf(true) }
val key = remember { Any() }
SharedTransitionLayout(
    Modifier
        .fillMaxSize()
        .padding(10.dp)
        .clickable {
            selectFirst = !selectFirst
        }
) {
    AnimatedContent(targetState = selectFirst, label = "AnimatedContent") { targetState ->
        if (targetState) {
            Box(
                Modifier
                    .padding(12.dp)
                    .sharedBounds(
                        rememberSharedContentState(key = key),
                        animatedVisibilityScope = this@AnimatedContent
                    )
                    .border(2.dp, Color.Red)
            ) {
                Text(
                    "Hello",
                    fontSize = 20.sp
                )
            }
        } else {
            Box(
                Modifier
                    .offset(180.dp, 180.dp)
                    .sharedBounds(
                        rememberSharedContentState(
                            key = key,
                        ),
                        animatedVisibilityScope = this@AnimatedContent
                    )
                    .border(2.dp, Color.Red)
                    // This padding is placed after sharedBounds, but it doesn't match the
                    // other shared elements modifier order, resulting in visual jumps
                    .padding(12.dp)

            ) {
                Text(
                    "Hello",
                    fontSize = 36.sp
                )
            }
        }
    }
}https://github.com/android/snippets/blob/95aeebd507b29719a9e7d5a839f101bbbe42ea72/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/BasicSharedElementSnippets.kt#L351-L400
```

<br />

| Matched bounds | Unmatched bounds: Notice how the shared element animation appears a bit off as it needs to resize to the incorrect bounds |
|----------------|---------------------------------------------------------------------------------------------------------------------------|
|                |                                                                                                                           |

The modifiers used***before***the shared element modifiers provide constraints to the shared element modifiers, which are then used to derive the initial and target bounds, and subsequently the bounds animation.

The modifiers used***after***the shared element modifiers use the constraints from before to measure and calculate the child's target size. The shared element modifiers create a series of animated constraints to gradually transform the child from the initial size to the target size.

The exception to this is if you use`resizeMode = ScaleToBounds()`for the animation, or`Modifier.skipToLookaheadSize()`on a composable. In this case, Compose lays out the child using the target constraints, and instead uses a scale factor to perform the animation instead of changing the layout size itself.
| **Important:** Be consistent with the order of modifiers on the matching items. Place size modifiers after the shared element modifiers, except when you use`requiredSize()`. If you use`requiredSize()`after shared element modifiers, there will be no relayout of children during the transform, even if you use`ScaleToBounds()`. On the other hand, if`requiredSize()`is before shared element modifiers, the parent of`requiredSize()`can never observe the shared elements`animatedSize`.

## Unique keys

When working with complex shared elements, it is a good practice to create a key that is not a string, because strings can be error prone to match. Each key must be unique for matches to occur. For example, in Jetsnack we have the following shared elements:
![](https://developer.android.com/static/develop/ui/compose/images/animations/shared-element/unique_keys_shared_elements.jpeg)**Figure 7.**Image showing Jetsnack with annotations for each part of the UI.

You could create an enum to represent the shared element type. In this example the whole snack card can also appear from multiple different places on the home screen, for example in a "Popular" and a "Recommended" section. You can create a key that has the`snackId`, the`origin`("Popular" / "Recommended"), and the`type`of the shared element that will be shared:

<br />

```kotlin
data class SnackSharedElementKey(
    val snackId: Long,
    val origin: String,
    val type: SnackSharedElementType
)

enum class SnackSharedElementType {
    Bounds,
    Image,
    Title,
    Tagline,
    Background
}

@Composable
fun SharedElementUniqueKey() {
    // ...
            Box(
                modifier = Modifier
                    .sharedElement(
                        rememberSharedContentState(
                            key = SnackSharedElementKey(
                                snackId = 1,
                                origin = "latest",
                                type = SnackSharedElementType.Image
                            )
                        ),
                        animatedVisibilityScope = this@AnimatedVisibility
                    )
            )
            // ...
}https://github.com/android/snippets/blob/95aeebd507b29719a9e7d5a839f101bbbe42ea72/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/BasicSharedElementSnippets.kt#L406-L443
```

<br />

Data classes are recommended for keys since they implement`hashCode()`and`isEquals()`.
| **Important:** When working with lists of content, you need to either append the item number to the key, or use a different unique identifier.

## Manage the visibility of shared elements manually

In cases where you may not be using`AnimatedVisibility`or`AnimatedContent`, you can manage the shared element visibility yourself. Use`Modifier.sharedElementWithCallerManagedVisibility()`and provide your own conditional that determines when an item should be visible or not:

<br />

```kotlin
var selectFirst by remember { mutableStateOf(true) }
val key = remember { Any() }
SharedTransitionLayout(
    Modifier
        .fillMaxSize()
        .padding(10.dp)
        .clickable {
            selectFirst = !selectFirst
        }
) {
    Box(
        Modifier
            .sharedElementWithCallerManagedVisibility(
                rememberSharedContentState(key = key),
                !selectFirst
            )
            .background(Color.Red)
            .size(100.dp)
    ) {
        Text(if (!selectFirst) "false" else "true", color = Color.White)
    }
    Box(
        Modifier
            .offset(180.dp, 180.dp)
            .sharedElementWithCallerManagedVisibility(
                rememberSharedContentState(
                    key = key,
                ),
                selectFirst
            )
            .alpha(0.5f)
            .background(Color.Blue)
            .size(180.dp)
    ) {
        Text(if (selectFirst) "false" else "true", color = Color.White)
    }
}https://github.com/android/snippets/blob/95aeebd507b29719a9e7d5a839f101bbbe42ea72/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/BasicSharedElementSnippets.kt#L307-L343
```

<br />

| **Important:** The shared element remains in the UI tree even when`visible ==
| false`. The shared element starts a transition whenever its size or position changes as it has an active match. Therefore it is recommended to remove the shared element with`visible == false`from the tree once the transition is finished, by observing`SharedTransitionScope.isTransitionActive`.

## Current limitations

These APIs have a few limitations. Most notably:

- No interoperability between Views and Compose is supported. This includes any composable that wraps`AndroidView`, such as a`Dialog`or`ModalBottomSheet`.
- There is no automatic animation support for the following:
  - **Shared Image composables** :
    - `ContentScale`is not animated by default. It snaps to the set end`ContentScale`.
  - **Shape clipping**- There is no built-in support for automatic animation between shapes - for example, animating from a square to a circle as the item transitions.
  - For the unsupported cases, use`Modifier.sharedBounds()`instead of`sharedElement()`and add`Modifier.animateEnterExit()`onto the items.