placeholder

Compose Modifier

Android
@Composable
public fun Modifier.placeholder(
    placeholderState: PlaceholderState,
    shape: Shape = PlaceholderDefaults.shape,
    color: Color = PlaceholderDefaults.color,
): Modifier

Modifier.placeholder draws a skeleton shape over a component, for situations when no provisional content (such as cached data) is available. The placeholder skeleton can be displayed instead, while the content is loading. The reveal of the content will be animated when it becomes available (and hidden again if the content becomes unavailable), unless the ReducedMotion setting is enabled, in which case those are instantaneous. NOTE: For animations to work, an AppScaffold should be used.

If there is some cached data for this field, it may be better to show that while loading, see

Note that the component should still be sized close to the target, so the final reveal of the content is less disruptive.

Parameters

placeholderStatethe state used to coordinate several placeholder effects.
shapethe shape of the placeholder.
colorthe color to use in the placeholder.

Code Examples

ButtonWithIconAndLabelAndPlaceholders

/**
 * This sample applies placeholders directly over the content that is waiting to be loaded. This
 * approach is suitable for situations where the developer has an approximate knowledge of how big
 * the content is going to be and it doesn't have cached data that can be shown.
 */
@Composable
fun ButtonWithIconAndLabelAndPlaceholders() {
    var labelText by remember { mutableStateOf("") }
    var imageVector: ImageVector? by remember { mutableStateOf(null) }
    val buttonPlaceholderState =
        rememberPlaceholderState(isVisible = labelText.isEmpty() || imageVector == null)
    FilledTonalButton(
        onClick = { /* Do something */ },
        enabled = true,
        modifier = Modifier.fillMaxWidth().placeholderShimmer(buttonPlaceholderState),
        label = {
            Text(
                text = labelText,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier.fillMaxWidth().placeholder(buttonPlaceholderState),
            )
        },
        icon = {
            Box(
                modifier =
                    Modifier.size(ButtonDefaults.IconSize).placeholder(buttonPlaceholderState)
            ) {
                if (imageVector != null) {
                    Icon(
                        imageVector = imageVector!!,
                        contentDescription = "Heart",
                        modifier =
                            Modifier.wrapContentSize(align = Alignment.Center)
                                .size(ButtonDefaults.IconSize)
                                .fillMaxSize(),
                    )
                }
            }
        },
    )
    // Simulate content loading completing in stages
    LaunchedEffect(Unit) {
        delay(2000)
        imageVector = Icons.Filled.Favorite
        delay(1000)
        labelText = "A label"
    }
}

ButtonWithIconAndLabelCachedData

/**
 * This sample doesn't use placeholders for the label as there is some cached data that can be shown
 * while loading.
 */
@Composable
fun ButtonWithIconAndLabelCachedData() {
    var labelText by remember { mutableStateOf("Cached Data") }
    var imageVector: ImageVector? by remember { mutableStateOf(null) }
    val buttonPlaceholderState =
        rememberPlaceholderState(isVisible = labelText.isEmpty() || imageVector == null)
    // Put placeholderShimmer in the container and placeholder in the elements of the content that
    // have no cached data.
    FilledTonalButton(
        onClick = { /* Do something */ },
        enabled = true,
        modifier = Modifier.fillMaxWidth().placeholderShimmer(buttonPlaceholderState),
        label = {
            Text(
                text = labelText,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier.fillMaxWidth(),
            )
        },
        icon = {
            Box(
                modifier =
                    Modifier.size(ButtonDefaults.IconSize).placeholder(buttonPlaceholderState)
            ) {
                if (imageVector != null) {
                    Icon(
                        imageVector = imageVector!!,
                        contentDescription = "Heart",
                        modifier =
                            Modifier.wrapContentSize(align = Alignment.Center)
                                .size(ButtonDefaults.IconSize)
                                .fillMaxSize(),
                    )
                }
            }
        },
    )
    // Simulate content loading completing in stages
    LaunchedEffect(Unit) {
        delay(2000)
        imageVector = Icons.Filled.Favorite
        delay(1000)
        labelText = "A label"
    }
}