How to use Material Design 3 in Jetpack Compose

Applying Material 3 theming, color schemes, typography, and components in Compose

, updated

Material Design 3 (M3) is the default design system in Jetpack Compose. Here’s how to set it up and use it.

Basic M3 theme

// ui/theme/Color.kt
val Purple80 = Color(0xFFD0BCFF)
val Purple40 = Color(0xFF6650A4)
val Pink80 = Color(0xFFEFB8C8)
val Pink40 = Color(0xFF7D5260)

// ui/theme/Theme.kt
private val LightColorScheme = lightColorScheme(
    primary = Purple40,
    onPrimary = Color.White,
    primaryContainer = Purple80,
    secondary = Pink40,
    onSecondary = Color.White,
    tertiary = Color(0xFF7D5260),
    background = Color(0xFFFFFBFE),
    surface = Color(0xFFFFFBFE),
)

private val DarkColorScheme = darkColorScheme(
    primary = Purple80,
    onPrimary = Color.Black,
    primaryContainer = Purple40,
    secondary = Pink80,
    onSecondary = Color.Black,
    tertiary = Pink80,
    background = Color(0xFF1C1B1F),
    surface = Color(0xFF1C1B1F),
)

@Composable
fun MyTheme(
    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 -> DarkColorScheme
        else -> LightColorScheme
    }

    MaterialTheme(
        colorScheme = colorScheme,
        typography = Typography,
        content = content,
    )
}

Typography

// ui/theme/Type.kt
val Typography = Typography(
    displayLarge = TextStyle(
        fontSize = 57.sp,
        lineHeight = 64.sp,
        fontWeight = FontWeight.Normal,
    ),
    headlineMedium = TextStyle(
        fontSize = 28.sp,
        lineHeight = 36.sp,
        fontWeight = FontWeight.Normal,
    ),
    titleLarge = TextStyle(
        fontSize = 22.sp,
        lineHeight = 28.sp,
        fontWeight = FontWeight.Normal,
    ),
    bodyLarge = TextStyle(
        fontSize = 16.sp,
        lineHeight = 24.sp,
        fontWeight = FontWeight.Normal,
    ),
    bodyMedium = TextStyle(
        fontSize = 14.sp,
        lineHeight = 20.sp,
        fontWeight = FontWeight.Normal,
    ),
    labelSmall = TextStyle(
        fontSize = 11.sp,
        lineHeight = 16.sp,
        fontWeight = FontWeight.Medium,
    ),
)

// Using typography
Text(
    text = "Headline",
    style = MaterialTheme.typography.headlineMedium,
)
Text(
    text = "Body text",
    style = MaterialTheme.typography.bodyLarge,
)

Common M3 components

Buttons

// Filled button (primary action)
FilledButton(onClick = { /* ... */ }) {
    Text("Save")
}

// Elevated button
ElevatedButton(onClick = { /* ... */ }) {
    Text("Edit")
}

// Outlined button
OutlinedButton(onClick = { /* ... */ }) {
    Text("Cancel")
}

// Text button
TextButton(onClick = { /* ... */ }) {
    Text("Learn more")
}

// Icon button
IconButton(onClick = { /* ... */ }) {
    Icon(Icons.Default.Favorite, contentDescription = "Favorite")
}

// FAB
FloatingActionButton(onClick = { /* ... */ }) {
    Icon(Icons.Default.Add, contentDescription = "Add")
}

// Extended FAB
ExtendedFloatingActionButton(
    onClick = { /* ... */ },
    icon = { Icon(Icons.Default.Add, contentDescription = null) },
    text = { Text("New item") },
)

Cards

// Elevated card
ElevatedCard(
    onClick = { /* ... */ },
    modifier = Modifier.fillMaxWidth()
) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text("Card Title", style = MaterialTheme.typography.titleLarge)
        Text("Card content goes here", style = MaterialTheme.typography.bodyMedium)
    }
}

// Outlined card
OutlinedCard(
    onClick = { /* ... */ },
) {
    Text("Outlined card content")
}

// Filled card
FilledCard(onClick = { /* ... */ }) {
    Text("Filled card content")
}

TopAppBar

Scaffold(
    topBar = {
        TopAppBar(
            title = { Text("My App") },
            navigationIcon = {
                IconButton(onClick = { navController.popBackStack() }) {
                    Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
                }
            },
            actions = {
                IconButton(onClick = { /* search */ }) {
                    Icon(Icons.Default.Search, contentDescription = "Search")
                }
                IconButton(onClick = { /* settings */ }) {
                    Icon(Icons.Default.Settings, contentDescription = "Settings")
                }
            },
            colors = TopAppBarDefaults.topAppBarColors(
                containerColor = MaterialTheme.colorScheme.primaryContainer,
                titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer,
            )
        )
    }
) { innerPadding ->
    // Content
}

Dialogs

var showDialog by remember { mutableStateOf(false) }

if (showDialog) {
    AlertDialog(
        onDismissRequest = { showDialog = false },
        title = { Text("Confirm") },
        text = { Text("Are you sure you want to delete this item?") },
        confirmButton = {
            TextButton(onClick = {
                // Delete action
                showDialog = false
            }) { Text("Delete") }
        },
        dismissButton = {
            TextButton(onClick = { showDialog = false }) { Text("Cancel") }
        }
    )
}

Snackbars

val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()

Scaffold(
    snackbarHost = { SnackbarHost(snackbarHostState) }
) { innerPadding ->
    Column(modifier = Modifier.padding(innerPadding)) {
        Button(onClick = {
            scope.launch {
                snackbarHostState.showSnackbar("Item deleted", actionLabel = "Undo")
            }
        }) {
            Text("Show Snackbar")
        }
    }
}