How to make a Compose screen expressive
Short answer: find the one action and the one piece of information that matter most on the screen. Make them stand out with shape, color and emphasized type. Group the rest. Turn selections into shape changes, and animate with springs. Then stop.
Google’s research behind M3 Expressive found people spotted key UI elements up to four times faster on expressive screens. That only works if one thing stands out.
The checklist
Section titled “The checklist”- Find the primary action and the key fact. One of each.
- Make the action bigger, with a contrasting shape and strong color. For example, a medium button, or a square-ish FAB next to a pill toolbar.
- Emphasize the key fact. Use
titleLargeEmphasizedordisplaySmallEmphasized, and save the huge display styles for hero screens. - Group the rest in
surfaceContainer*containers. The most important group gets the brightest one. - Turn selection into shape: connected
ToggleButtons,SegmentedListItemselected states. - Morph instead of swapping, and use
MaterialTheme.motionSchemesprings. - Stop and test: light, dark, two dynamic color wallpapers, font scale 200%.
/** * Google's before/after: a docked bottom bar with a FAB becomes a floating toolbar plus a * separate FAB in a contrasting shape and color. The FAB is the primary action, so it gets the * vibrant container and the square-ish shape; the toolbar stays a pill. */@Composablefun ComposeActions(actions: List<Pair<ImageVector, String>>, primary: ImageVector, onPrimary: () -> Unit) { HorizontalFloatingToolbar( expanded = true, floatingActionButton = { FloatingToolbarDefaults.VibrantFloatingActionButton(onClick = onPrimary) { Icon(primary, contentDescription = "Compose") } }, ) { actions.forEach { (icon, label) -> IconButton(onClick = {}) { Icon(icon, contentDescription = label) } } }}Hero moments: one or two per product
Section titled “Hero moments: one or two per product”Google’s guidance is one or two hero moments per product, not per screen. Examples are a paywall, an onboarding finish or a “goal reached” screen. Everywhere else, keep it calm.
/** * Shapes as highlights in data, like Google's steps chart: plain pill bars, and a Sunny-shaped * check badge only on the days that hit the goal. The badge is decoration, so the bar still says * what it means (contentDescription on the column, not on the shape). */@Composablefun WeekChart(steps: List<Int>, goal: Int, check: ImageVector, max: Int = 15_000) { Row( modifier = Modifier.height(200.dp), horizontalArrangement = Arrangement.spacedBy(6.dp), verticalAlignment = Alignment.Bottom, ) { steps.forEach { value -> val hit = value >= goal Column( modifier = Modifier.width(40.dp).fillMaxHeight(), verticalArrangement = Arrangement.Bottom, horizontalAlignment = Alignment.CenterHorizontally, ) { Box( modifier = Modifier .width(40.dp) .height((200 * value / max).coerceAtLeast(40).dp) .background( if (hit) MaterialTheme.colorScheme.tertiaryContainer else MaterialTheme.colorScheme.primary, CircleShape, ), contentAlignment = Alignment.TopCenter, ) { if (hit) { Box( Modifier .size(36.dp) .background(MaterialTheme.colorScheme.tertiary, MaterialShapes.Sunny.toShape()), contentAlignment = Alignment.Center, ) { Icon(check, contentDescription = null, tint = MaterialTheme.colorScheme.onTertiary) } } } } } }}Want your agent to get this right every time? Install the skill. If this page helped,star the repo on GitHub.
Built by Sanikant. Set in Google Sans Flex (SIL Open Font License), icons from Material Symbols (Apache 2.0). Material Design is a Google trademark; this project is not affiliated with Google.