Common Use Cases¶
Practical patterns for everyday loading states.
Loading a List of Items¶
A common pattern is to show placeholder rows while a list loads. Wrap the list in a PlaceholderSurface so every row shimmers in lockstep.
@Composable
fun UserList(users: List<User>?, isLoading: Boolean) {
PlaceholderSurface {
LazyColumn {
items(if (isLoading) 5 else users?.size ?: 0) { index ->
UserListItem(
user = users?.getOrNull(index),
isLoading = isLoading,
)
}
}
}
}
@Composable
fun UserListItem(user: User?, isLoading: Boolean) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
// Avatar
Box(
modifier = Modifier
.size(48.dp)
.placeholder(
enabled = isLoading,
shape = CircleShape,
highlight = PlaceholderDefaults.shimmer,
),
)
Spacer(modifier = Modifier.width(16.dp))
Column {
// Name
Text(
text = user?.name ?: "Loading name",
modifier = Modifier
.fillMaxWidth(0.6f)
.placeholder(
enabled = isLoading,
shape = RoundedCornerShape(4.dp),
),
)
Spacer(modifier = Modifier.height(4.dp))
// Email
Text(
text = user?.email ?: "Loading email",
modifier = Modifier
.fillMaxWidth(0.8f)
.placeholder(
enabled = isLoading,
shape = RoundedCornerShape(4.dp),
),
)
}
}
}
Loading an Image¶
Perfect for showing a skeleton while images load from the network.
var isLoading by remember { mutableStateOf(true) }
AsyncImage(
model = imageUrl,
contentDescription = "Product image",
onSuccess = { isLoading = false },
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.placeholder(
enabled = isLoading,
highlight = PlaceholderDefaults.shimmer,
),
)
Card with Multiple Placeholders¶
@Composable
fun ProductCard(product: Product?, isLoading: Boolean) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
) {
Column(modifier = Modifier.padding(16.dp)) {
// Product image
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.placeholder(
enabled = isLoading,
shape = RoundedCornerShape(8.dp),
highlight = PlaceholderDefaults.shimmer,
),
)
Spacer(modifier = Modifier.height(16.dp))
// Product title
Text(
text = product?.title ?: "Loading title",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier
.fillMaxWidth(0.7f)
.placeholder(
enabled = isLoading,
shape = RoundedCornerShape(4.dp),
),
)
Spacer(modifier = Modifier.height(8.dp))
// Product description
Text(
text = product?.description ?: "",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.fillMaxWidth()
.placeholderText(
enabled = isLoading,
lines = 3,
lastLineFraction = 0.55f,
style = MaterialTheme.typography.bodyMedium,
shape = RoundedCornerShape(4.dp),
),
)
Spacer(modifier = Modifier.height(16.dp))
// Price
Text(
text = product?.price ?: "$0.00",
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier
.width(80.dp)
.placeholder(
enabled = isLoading,
shape = RoundedCornerShape(4.dp),
highlight = PlaceholderDefaults.pulse,
),
)
}
}
}
This card combines Modifier.placeholder (single-rect shapes for image, title, price) with Modifier.placeholderText (multi-line bars for description) — each scaling its own appearance from the same enabled flag.