Material 3 Expressive for Jetpack Compose
Material 3 Expressive is the next evolution of Material Design, bringing emotionally resonant, physics-based, and highly dynamic UI patterns to Android. It introduces spring-based motion, shape morphing, emphasized typography, expressive color palettes, and 28 new or enhanced components.
All M3 Expressive APIs are experimental. Every composable, theme function, and motion token requires
@OptIn(ExperimentalMaterial3ExpressiveApi::class).
Gradle Setup
// build.gradle.kts (app module)
dependencies {
// Material 3 Expressive requires 1.4.0-alpha+ or BOM 2025.05+
implementation(platform("androidx.compose:compose-bom:2025.05.00"))
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material3:material3-adaptive-navigation-suite")
implementation("androidx.compose.animation:animation")
implementation("androidx.compose.foundation:foundation")
}
android {
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.14"
}
}
1. MaterialExpressiveTheme Setup
The entry point for M3 Expressive is MaterialExpressiveTheme, which replaces MaterialTheme and wires expressive defaults for color, motion, shapes, and typography.
Complete Theme Setup
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
}
darkTheme -> expressiveDarkColorScheme()
else -> expressiveLightColorScheme()
}
MaterialExpressiveTheme(
colorScheme = colorScheme,
motionScheme = MotionScheme.expressive(),
shapes = Shapes(
// Standard shapes
small = RoundedCornerShape(8.dp),
medium = RoundedCornerShape(16.dp),
large = RoundedCornerShape(24.dp),
extraLarge = RoundedCornerShape(32.dp),
// Expressive increased shapes
largeIncreased = RoundedCornerShape(28.dp),
extraLargeIncreased = RoundedCornerShape(36.dp),
),
typography = Typography(
// Emphasized type styles for visual hierarchy
displayLargeEmphasized = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Bold,
fontSize = 57.sp,
lineHeight = 64.sp,
letterSpacing = (-0.25).sp,
),
headlineLargeEmphasized = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.SemiBold,
fontSize = 32.sp,
lineHeight = 40.sp,
),
),
content = content
)
}
Theme API Signature
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MaterialExpressiveTheme(
colorScheme: ColorScheme? = null, // null = expressiveLightColorScheme()
motionScheme: MotionScheme? = null, // null = MotionScheme.expressive()
shapes: Shapes? = null, // null = expressive shape defaults
typography: Typography? = null, // null = expressive typography defaults
content: @Composable () -> Unit,
)
Accessing Theme Values
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ThemedContent() {
val colors = MaterialTheme.colorScheme
val typography = MaterialTheme.typography
val shapes = MaterialTheme.shapes
val motion = MaterialTheme.motionScheme
Text(
text = "Expressive Title",
style = typography.displayLargeEmphasized,
color = colors.primary
)
}
2. Motion System
The motion system is the most transformative part of M3 Expressive. It replaces duration-based easing with spring-based physics, creating natural, responsive animations that feel alive.
Core Concept: Spring Physics
Springs are defined by two parameters:
- dampingRatio - How quickly oscillation settles (0 = no damping/infinite bounce, 1 = critically damped/no overshoot)
- stiffness - How fast the spring moves toward its target (higher = faster)
import androidx.compose.animation.core.*
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun SpringExample() {
var expanded by remember { mutableStateOf(false) }
// Expressive spring with overshoot/bounce
val size by animateFloatAsState(
targetValue = if (expanded) 200f else 100f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy, // 0.5f - visible bounce
stiffness = Spring.StiffnessMedium // 1500f
),
label = "size"
)
Box(
modifier = Modifier
.size(size.dp)
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.primaryContainer)
.clickable { expanded = !expanded }
)
}
MotionScheme Tokens
MotionScheme.expressive() provides six categories of motion tokens, split between effects (non-spatial, like fade/scale) and spatial (position/layout changes).
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MotionTokensDemo() {
val motion = MaterialTheme.motionScheme
// === EFFECTS tokens (fade, scale, color change) ===
// Fast effects: micro-interactions, toggles, checkboxes
// ~150ms equivalent, stiff spring, minimal overshoot
val fastEffectsSpec = motion.fastEffectsSpec<Float>()
// Default effects: standard UI feedback, button presses
// ~300ms equivalent, medium spring
val defaultEffectsSpec = motion.defaultEffectsSpec<Float>()
// Slow effects: emphasis animations, attention-drawing
// ~500ms equivalent, gentle spring with bounce
val slowEffectsSpec = motion.slowEffectsSpec<Float>()
// === SPATIAL tokens (position, size, layout) ===
// Fast spatial: small movements, tooltips, small menus
// Quick with slight overshoot
val fastSpatialSpec = motion.fastSpatialSpec<Float>()
// Default spatial: navigation, expanding panels, sheets
// Natural spring with moderate overshoot
val defaultSpatialSpec = motion.defaultSpatialSpec<Float>()
// Slow spatial: full-screen transitions, hero animations
// Gentle spring with pronounced overshoot for drama
val slowSpatialSpec = motion.slowSpatialSpec<Float>()
}
Using Motion Tokens in Animations
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun AnimatedCard(isVisible: Boolean) {
val motion = MaterialTheme.motionScheme
// Use default spatial for position/size animations
val offsetY by animateFloatAsState(
targetValue = if (isVisible) 0f else 100f,
animationSpec = motion.defaultSpatialSpec(),
label = "offsetY"
)
// Use fast effects for opacity
val alpha by animateFloatAsState(
targetValue = if (isVisible) 1f else 0f,
animationSpec = motion.fastEffectsSpec(),
label = "alpha"
)
Card(
modifier = Modifier
.offset(y = offsetY.dp)
.alpha(alpha)
) {
Text("Animated Card")
}
}
Spring Specifications Reference
// Common spring configurations for M3 Expressive
object ExpressiveSprings {
// Bouncy - for playful, attention-grabbing animations
val bouncy = spring<Float>(
dampingRatio = 0.4f, // Significant bounce
stiffness = 400f // Medium-slow
)
// Snappy - for responsive UI elements
val snappy = spring<Float>(
dampingRatio = 0.75f, // Slight overshoot
stiffness = 1000f // Fast
)
// Gentle - for large layout transitions
val gentle = spring<Float>(
dampingRatio = 0.6f, // Moderate bounce
stiffness = 200f // Slow, dramatic
)
// Critical - for precise, no-overshoot animations
val critical = spring<Float>(
dampingRatio = 1.0f, // No overshoot
stiffness = 800f // Medium-fast
)
}
Shape Morphing Animations
Shape morphing transitions smoothly between different shape geometries, a signature M3 Expressive effect.
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.graphics.Shape
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ShapeMorphButton(isActive: Boolean) {
val motion = MaterialTheme.motionScheme
// Morph between pill and rounded rectangle
val cornerPercent by animateIntAsState(
targetValue = if (isActive) 50 else 16,
animationSpec = motion.defaultSpatialSpec(),
label = "cornerMorph"
)
Button(
/* toggle */ },
shape = RoundedCornerShape(cornerPercent),
modifier = Modifier.height(56.dp)
) {
Text(if (isActive) "Active" else "Inactive")
}
}
Shared Element Transitions
import androidx.compose.animation.*
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalSharedTransitionApi::class)
@Composable
fun SharedElementDemo(
sharedTransitionScope: SharedTransitionScope,
animatedContentScope: AnimatedContentScope,
item: Item,
onClick: () -> Unit
) {
with(sharedTransitionScope) {
Card(
modifier = Modifier
.sharedElement(
state = rememberSharedContentState(key = "item-${item.id}"),
animatedVisibilityScope = animatedContentScope,
boundsTransform = { _, _ ->
// Use expressive spring for shared element bounds
spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessMediumLow
)
}
)
.clickable(onClick = onClick)
) {
Text(item.title, style = MaterialTheme.typography.headlineMedium)
}
}
}
Predictive Back Gesture Animations
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun PredictiveBackScreen(onBack: () -> Unit) {
val motion = MaterialTheme.motionScheme
// Scale down as user drags back
var backProgress by remember { mutableFloatStateOf(0f) }
val scale by animateFloatAsState(
targetValue = 1f - (backProgress * 0.1f),
animationSpec = motion.fastSpatialSpec(),
label = "backScale"
)
val cornerRadius by animateDpAsState(
targetValue = (backProgress * 24).dp,
animationSpec = motion.fastSpatialSpec(),
label = "backCorner"
)
Box(
modifier = Modifier
.fillMaxSize()
.scale(scale)
.clip(RoundedCornerShape(cornerRadius))
) {
// Screen content
}
}
Keyframe Animations
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun KeyframeAnimationDemo() {
var trigger by remember { mutableStateOf(false) }
val offsetX by animateFloatAsState(
targetValue = if (trigger) 200f else 0f,
animationSpec = keyframes {
durationMillis = 600
0f at 0 using EaseOut
240f at 200 using EaseInOut // Overshoot
180f at 400 using EaseInOut // Pull back
200f at 600 // Settle
},
label = "keyframeOffset"
)
Box(
modifier = Modifier
.offset(x = offsetX.dp)
.size(60.dp)
.background(
MaterialTheme.colorScheme.tertiary,
RoundedCornerShape(50)
)
.clickable { trigger = !trigger }
)
}
3. Shape System
M3 Expressive introduces 35 new shape tokens beyond the standard Material shapes, including increased variants and expressive morphable shapes.
Shape Tokens
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ShapeTokensReference() {
val shapes = MaterialTheme.shapes
// Standard Material 3 shapes
shapes.extraSmall // 4.dp corner radius
shapes.small // 8.dp
shapes.medium // 12.dp
shapes.large // 16.dp
shapes.extraLarge // 28.dp
// Expressive increased shapes - larger corner radii for emphasis
shapes.smallIncreased // 10.dp
shapes.mediumIncreased // 16.dp
shapes.largeIncreased // 24.dp
shapes.extraLargeIncreased // 32.dp
// Full shapes for pills/circles
shapes.full // 50% corner radius (pill/circle)
}
Custom Shapes with Morphing
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MorphableShapeDemo() {
var isCircle by remember { mutableStateOf(false) }
val motion = MaterialTheme.motionScheme
val cornerPercent by animateIntAsState(
targetValue = if (isCircle) 50 else 12,
animationSpec = motion.defaultSpatialSpec(),
label = "shapeMorph"
)
Surface(
modifier = Modifier
.size(120.dp)
.clickable { isCircle = !isCircle },
shape = RoundedCornerShape(percent = cornerPercent),
color = MaterialTheme.colorScheme.secondaryContainer
) {
Box(contentAlignment = Alignment.Center) {
Icon(
imageVector = Icons.Default.Star,
contentDescription = "Star",
tint = MaterialTheme.colorScheme.onSecondaryContainer
)
}
}
}
Applying Shapes to Components
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ShapedComponents() {
// Card with expressive increased shape
Card(
shape = MaterialTheme.shapes.largeIncreased,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = "Expressive Card",
modifier = Modifier.padding(24.dp)
)
}
Spacer(modifier = Modifier.height(16.dp))
// Button with extra-large increased shape (very rounded)
Button(
},
shape = MaterialTheme.shapes.extraLargeIncreased
) {
Text("Rounded Button")
}
Spacer(modifier = Modifier.height(16.dp))
// Full pill shape
AssistChip(
},
label = { Text("Pill Chip") },
shape = MaterialTheme.shapes.full
)
}
Cut Corner Shapes
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun CutCornerShapeDemo() {
Surface(
shape = CutCornerShape(topStart = 16.dp, bottomEnd = 16.dp),
color = MaterialTheme.colorScheme.tertiaryContainer,
modifier = Modifier.size(100.dp)
) {
// Geometric, angular look
}
}
4. Typography Emphasis
M3 Expressive adds emphasized variants to the type scale. Emphasized styles use heavier weights or variable font optical sizing to create stronger visual hierarchy.
Emphasized Type Styles
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun TypographyEmphasisDemo() {
val typography = MaterialTheme.typography
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
// Display - emphasized for hero headlines
Text("Hero Title", style = typography.displayLargeEmphasized)
Text("Display Medium", style = typography.displayMediumEmphasized)
Text("Display Small", style = typography.displaySmallEmphasized)
// Headline - emphasized for section headers
Text("Section Header", style = typography.headlineLargeEmphasized)
Text("Sub Section", style = typography.headlineMediumEmphasized)
Text("Minor Section", style = typography.headlineSmallEmphasized)
// Title - emphasized for card titles, list headers
Text("Card Title", style = typography.titleLargeEmphasized)
Text("List Title", style = typography.titleMediumEmphasized)
Text("Small Title", style = typography.titleSmallEmphasized)
// Body - emphasized for important body text
Text("Important paragraph text.", style = typography.bodyLargeEmphasized)
Text("Emphasized body.", style = typography.bodyMediumEmphasized)
Text("Fine print emphasis.", style = typography.bodySmallEmphasized)
// Label - emphasized for buttons, tabs
Text("BUTTON", style = typography.labelLargeEmphasized)
Text("TAB", style = typography.labelMediumEmphasized)
Text("CAPTION", style = typography.labelSmallEmphasized)
}
}
Custom Emphasized Typography
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
fun createExpressiveTypography(): Typography {
val baseFamily = FontFamily.Default
return Typography(
// Standard styles
displayLarge = TextStyle(
fontFamily = baseFamily,
fontWeight = FontWeight.Normal,
fontSize = 57.sp,
lineHeight = 64.sp,
letterSpacing = (-0.25).sp,
),
// Emphasized counterpart - heavier weight, tighter tracking
displayLargeEmphasized = TextStyle(
fontFamily = baseFamily,
fontWeight = FontWeight.Bold,
fontSize = 57.sp,
lineHeight = 64.sp,
letterSpacing = (-0.5).sp, // Tighter for emphasis
),
headlineLarge = TextStyle(
fontFamily = baseFamily,
fontWeight = FontWeight.Normal,
fontSize = 32.sp,
lineHeight = 40.sp,
),
headlineLargeEmphasized = TextStyle(
fontFamily = baseFamily,
fontWeight = FontWeight.SemiBold,
fontSize = 32.sp,
lineHeight = 40.sp,
letterSpacing = (-0.2).sp,
),
)
}
Variable Font Emphasis
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun VariableFontEmphasis() {
// With variable fonts (e.g., Google Fonts with axes),
// emphasis can be expressed through optical size and grade
// rather than just weight changes
val emphasizedStyle = MaterialTheme.typography.headlineLargeEmphasized.copy(
// Variable font settings for richer emphasis
fontFeatureSettings = "ss01", // Stylistic set for display use
)
Text(
text = "Variable Font Emphasis",
style = emphasizedStyle
)
}
5. Color System
M3 Expressive extends the color system with richer palettes, stronger tertiary accents, and emphasis-driven color roles.
Expressive Color Schemes
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ColorSchemeDemo() {
// Expressive light scheme - warmer, more vibrant palette
val lightColors = expressiveLightColorScheme()
// Expressive dark scheme - deeper, richer dark palette
val darkColors = expressiveDarkColorScheme()
// Key differences from standard M3 color schemes:
// - Tertiary colors are more prominent (used for high-contrast accents)
// - Container colors have more saturation
// - Surface variants have subtle tonal shifts
// - Error colors are more vivid
}
Color Roles for Emphasis
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ColorEmphasisDemo() {
val colors = MaterialTheme.colorScheme
Column {
// HIGH emphasis - Primary for main actions
Button(
},
colors = ButtonDefaults.buttonColors(
containerColor = colors.primary,
contentColor = colors.onPrimary
)
) {
Text("Primary Action")
}
// MEDIUM emphasis - Secondary for supporting actions
Button(
},
colors = ButtonDefaults.buttonColors(
containerColor = colors.secondary,
contentColor = colors.onSecondary
)
) {
Text("Secondary Action")
}
// ACCENT emphasis - Tertiary for expressive highlights
Button(
},
colors = ButtonDefaults.buttonColors(
containerColor = colors.tertiary,
contentColor = colors.onTertiary
)
) {
Text("Accent Action")
}
// SURFACE emphasis - Subtle, blended into background
Surface(
color = colors.surfaceContainerHighest,
contentColor = colors.onSurface,
shape = MaterialTheme.shapes.medium
) {
Text("Surface Content", modifier = Modifier.padding(16.dp))
}
}
}
Dynamic Color with Expressive Palettes
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun DynamicExpressiveColor() {
val context = LocalContext.current
val darkTheme = isSystemInDarkTheme()
// Dynamic color extracts palette from user wallpaper
// and applies expressive tonal mapping
val colorScheme = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
if (darkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
}
darkTheme -> expressiveDarkColorScheme()
else -> expressiveLightColorScheme()
}
MaterialExpressiveTheme(
colorScheme = colorScheme,
content = { /* app content */ }
)
}
Tertiary Color Usage
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun TertiaryAccentExample() {
val colors = MaterialTheme.colorScheme
// Tertiary is the "expressive accent" - use for:
// - Highlighting active/selected states
// - Drawing attention to new features
// - Badges, tags, notification indicators
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
// Active tab with tertiary accent
Surface(
color = colors.tertiaryContainer,
shape = MaterialTheme.shapes.full
) {
Text(
"Active",
color = colors.onTertiaryContainer,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
// Inactive tab with surface
Surface(
color = colors.surfaceContainerHigh,
shape = MaterialTheme.shapes.full
) {
Text(
"Inactive",
color = colors.onSurfaceVariant,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
}
}
6. Expressive Components
All 28 expressive components with complete code examples. Every component requires:
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
6.1 ButtonGroup
Groups related buttons together with shared container styling and automatic spacing.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ButtonGroupDemo() {
ButtonGroup(
modifier = Modifier.fillMaxWidth()
) {
Button(onClick = { }) { Text("Option A") }
Button(onClick = { }) { Text("Option B") }
Button(onClick = { }) { Text("Option C") }
}
}
// ButtonGroup with mixed button types
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MixedButtonGroup() {
var selectedIndex by remember { mutableIntStateOf(0) }
ButtonGroup {
listOf("Day", "Week", "Month").forEachIndexed { index, label ->
if (index == selectedIndex) {
FilledTonalButton(onClick = { selectedIndex = index }) {
Text(label)
}
} else {
OutlinedButton(onClick = { selectedIndex = index }) {
Text(label)
}
}
}
}
}
6.2 CircularWavyProgressIndicator
A circular progress indicator with a wavy, organic animation style.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun CircularWavyProgressDemo() {
// Indeterminate - continuous wavy animation
CircularWavyProgressIndicator(
modifier = Modifier.size(48.dp),
color = MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
)
Spacer(modifier = Modifier.height(16.dp))
// Determinate - wavy progress toward target
var progress by remember { mutableFloatStateOf(0.65f) }
CircularWavyProgressIndicator(
progress = { progress },
modifier = Modifier.size(48.dp),
color = MaterialTheme.colorScheme.tertiary,
trackColor = MaterialTheme.colorScheme.tertiaryContainer,
)
}
6.3 ContainedLoadingIndicator
A loading indicator contained within a surface, suitable for inline loading states.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ContainedLoadingDemo() {
ContainedLoadingIndicator(
modifier = Modifier.size(width = 200.dp, height = 48.dp),
color = MaterialTheme.colorScheme.primary,
containerColor = MaterialTheme.colorScheme.primaryContainer,
shape = MaterialTheme.shapes.full,
)
}
6.4 DropdownMenuGroup
Groups related dropdown menu items with a visual separator.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun DropdownMenuGroupDemo() {
var expanded by remember { mutableStateOf(false) }
Box {
IconButton(onClick = { expanded = true }) {
Icon(Icons.Default.MoreVert, contentDescription = "Menu")
}
DropdownMenu(expanded = expanded, expanded = false }) {
DropdownMenuGroup(label = { Text("Edit") }) {
DropdownMenuItem(
text = { Text("Cut") },
expanded = false },
leadingIcon = { Icon(Icons.Default.ContentCut, null) }
)
DropdownMenuItem(
text = { Text("Copy") },
expanded = false },
leadingIcon = { Icon(Icons.Default.ContentCopy, null) }
)
DropdownMenuItem(
text = { Text("Paste") },
expanded = false },
leadingIcon = { Icon(Icons.Default.ContentPaste, null) }
)
}
DropdownMenuGroup(label = { Text("Format") }) {
DropdownMenuItem(
text = { Text("Bold") },
expanded = false }
)
DropdownMenuItem(
text = { Text("Italic") },
expanded = false }
)
}
}
}
}
6.5 DropdownMenuPopup
A popup-style dropdown menu with expressive entry/exit animations.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun DropdownMenuPopupDemo() {
var showPopup by remember { mutableStateOf(false) }
Box {
Button(onClick = { showPopup = true }) { Text("Show Popup") }
DropdownMenuPopup(
expanded = showPopup,
showPopup = false },
) {
DropdownMenuItem(
text = { Text("Share") },
showPopup = false },
leadingIcon = { Icon(Icons.Default.Share, null) }
)
DropdownMenuItem(
text = { Text("Delete") },
showPopup = false },
leadingIcon = { Icon(Icons.Default.Delete, null) }
)
}
}
}
6.6 ElevatedToggleButton
A toggle button with elevation changes to indicate state.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ElevatedToggleButtonDemo() {
var checked by remember { mutableStateOf(false) }
ElevatedToggleButton(
checked = checked,
checked = it },
) {
Icon(
imageVector = if (checked) Icons.Filled.Bookmark else Icons.Outlined.BookmarkBorder,
contentDescription = "Bookmark"
)
Spacer(Modifier.width(8.dp))
Text(if (checked) "Saved" else "Save")
}
}
6.7 ExpandedDockedSearchBarWithGap
An expanded search bar that docks to the top with a gap for content to show through.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ExpandedDockedSearchBarDemo() {
var query by rememberSaveable { mutableStateOf("") }
var expanded by rememberSaveable { mutableStateOf(false) }
ExpandedDockedSearchBarWithGap(
inputField = {
SearchBarDefaults.InputField(
query = query,
query = it },
expanded = false },
expanded = expanded,
expanded = it },
placeholder = { Text("Search...") },
leadingIcon = { Icon(Icons.Default.Search, "Search") },
trailingIcon = {
if (query.isNotEmpty()) {
IconButton(onClick = { query = "" }) {
Icon(Icons.Default.Close, "Clear")
}
}
}
)
},
expanded = expanded,
expanded = it },
) {
// Search suggestions / results
LazyColumn {
items(suggestions) { suggestion ->
ListItem(
headlineContent = { Text(suggestion) },
leadingContent = { Icon(Icons.Default.History, null) },
modifier = Modifier.clickable {
query = suggestion
expanded = false
}
)
}
}
}
}
6.8 FlexibleBottomAppBar
A bottom app bar that can expand and collapse with spring animations.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun FlexibleBottomAppBarDemo() {
val scrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior()
Scaffold(
bottomBar = {
FlexibleBottomAppBar(
scrollBehavior = scrollBehavior,
horizontalArrangement = Arrangement.SpaceEvenly,
) {
IconButton(onClick = { }) {
Icon(Icons.Default.Home, contentDescription = "Home")
}
IconButton(onClick = { }) {
Icon(Icons.Default.Search, contentDescription = "Search")
}
IconButton(onClick = { }) {
Icon(Icons.Default.Notifications, contentDescription = "Notifications")
}
IconButton(onClick = { }) {
Icon(Icons.Default.Person, contentDescription = "Profile")
}
}
}
) { padding ->
LazyColumn(
contentPadding = padding,
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) {
items(50) { Text("Item $it", modifier = Modifier.padding(16.dp)) }
}
}
}
6.9 FloatingActionButtonMenu + FloatingActionButtonMenuItem
An expandable FAB that reveals a menu of actions with spring animations.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun FabMenuDemo() {
var expanded by rememberSaveable { mutableStateOf(false) }
Scaffold(
floatingActionButton = {
FloatingActionButtonMenu(
expanded = expanded,
button = {
FloatingActionButton(
expanded = !expanded },
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
) {
Icon(
imageVector = if (expanded) Icons.Default.Close else Icons.Default.Add,
contentDescription = if (expanded) "Close" else "Add"
)
}
}
) {
FloatingActionButtonMenuItem(
expanded = false },
icon = { Icon(Icons.Default.CameraAlt, "Camera") },
text = { Text("Take Photo") },
)
FloatingActionButtonMenuItem(
expanded = false },
icon = { Icon(Icons.Default.Image, "Gallery") },
text = { Text("From Gallery") },
)
FloatingActionButtonMenuItem(
expanded = false },
icon = { Icon(Icons.Default.Description, "Document") },
text = { Text("Upload File") },
)
}
}
) { padding ->
Box(Modifier.padding(padding))
}
}
6.10 HorizontalFloatingToolbar
A floating toolbar that hovers over content with horizontal layout.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun HorizontalFloatingToolbarDemo() {
var visible by remember { mutableStateOf(true) }
Box(modifier = Modifier.fillMaxSize()) {
// Content behind toolbar
Text("Select text to show toolbar")
AnimatedVisibility(
visible = visible,
modifier = Modifier.align(Alignment.BottomCenter).padding(16.dp),
enter = fadeIn() + slideInVertically { it },
exit = fadeOut() + slideOutVertically { it }
) {
HorizontalFloatingToolbar(
expanded = true,
content = {
IconButton(onClick = { }) {
Icon(Icons.Default.FormatBold, "Bold")
}
IconButton(onClick = { }) {
Icon(Icons.Default.FormatItalic, "Italic")
}
IconButton(onClick = { }) {
Icon(Icons.Default.FormatUnderlined, "Underline")
}
VerticalDivider(modifier = Modifier.height(24.dp))
IconButton(onClick = { }) {
Icon(Icons.Default.FormatColorText, "Color")
}
}
)
}
}
}
6.11 LargeExtendedFloatingActionButton
An extra-large FAB with text and icon for primary screen actions.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun LargeExtendedFabDemo() {
LargeExtendedFloatingActionButton(
/* action */ },
icon = {
Icon(
Icons.Default.Edit,
contentDescription = "Compose"
)
},
text = { Text("Compose") },
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
)
}
6.12 LargeFlexibleTopAppBar
A top app bar that supports large titles and collapses on scroll with spring physics.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun LargeFlexibleTopAppBarDemo() {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
topBar = {
LargeFlexibleTopAppBar(
title = { Text("Messages") },
subtitle = { Text("3 unread") },
navigationIcon = {
IconButton(onClick = { }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back")
}
},
actions = {
IconButton(onClick = { }) {
Icon(Icons.Default.Search, "Search")
}
},
scrollBehavior = scrollBehavior,
)
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) { padding ->
LazyColumn(contentPadding = padding) {
items(30) { index ->
ListItem(headlineContent = { Text("Message $index") })
}
}
}
}
6.13 LinearWavyProgressIndicator
A linear progress indicator with a wavy track animation.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun LinearWavyProgressDemo() {
// Indeterminate
LinearWavyProgressIndicator(
modifier = Modifier.fillMaxWidth().height(6.dp),
color = MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
)
Spacer(modifier = Modifier.height(16.dp))
// Determinate
var progress by remember { mutableFloatStateOf(0.4f) }
LinearWavyProgressIndicator(
progress = { progress },
modifier = Modifier.fillMaxWidth().height(6.dp),
color = MaterialTheme.colorScheme.tertiary,
trackColor = MaterialTheme.colorScheme.tertiaryContainer,
)
}
6.14 LoadingIndicator
A standalone loading indicator with expressive animation.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun LoadingIndicatorDemo() {
LoadingIndicator(
modifier = Modifier.size(48.dp),
color = MaterialTheme.colorScheme.primary,
)
// With text
Column(horizontalAlignment = Alignment.CenterHorizontally) {
LoadingIndicator(modifier = Modifier.size(32.dp))
Spacer(Modifier.height(8.dp))
Text(
"Loading...",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
6.15 MaterialExpressiveTheme
See Section 1 above for complete theme setup.
6.16 MediumExtendedFloatingActionButton
A medium-sized FAB with text and icon.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MediumExtendedFabDemo() {
MediumExtendedFloatingActionButton(
/* action */ },
icon = {
Icon(Icons.Default.Add, contentDescription = "Add")
},
text = { Text("New Item") },
)
}
6.17 MediumFlexibleTopAppBar
A medium-height flexible top app bar with spring-based collapse.
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun MediumFlexibleTopAppBarDemo() {
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
topBar = {
MediumFlexibleTopAppBar(
title = { Text("Settings") },
navigationIcon = {
IconButton(onClick = { }) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back")
}
},
scrollBehavior = scrollBehavior,
)
},
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) { padding ->
LazyColumn(contentPadding = padding) {
items(20) { ListItem(headlineContent = { Text("Setting $it") }) }
}
}
}
6.18 MediumFloatingActionButton
A medium-sized FAB without text, between standard and large si
…(truncated)