Compose theming with Material 3 is built on three pillars: color, typography, and shape. Here’s how to configure and customize them.
Color scheme
// Light scheme
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF6750A4),
onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFEADDFF),
onPrimaryContainer = Color(0xFF21005D),
secondary = Color(0xFF625B71),
onSecondary = Color(0xFFFFFFFF),
secondaryContainer = Color(0xFFE8DEF8),
onSecondaryContainer = Color(0xFF1D192B),
tertiary = Color(0xFF7D5260),
onTertiary = Color(0xFFFFFFFF),
tertiaryContainer = Color(0xFFFFD8E4),
onTertiaryContainer = Color(0xFF31111D),
error = Color(0xFFB3261E),
onError = Color(0xFFFFFFFF),
errorContainer = Color(0xFFF9DEDC),
onErrorContainer = Color(0xFF410E0B),
background = Color(0xFFFFFBFE),
onBackground = Color(0xFF1C1B1F),
surface = Color(0xFFFFFBFE),
onSurface = Color(0xFF1C1B1F),
surfaceVariant = Color(0xFFE7E0EC),
onSurfaceVariant = Color(0xFF49454F),
outline = Color(0xFF79747E),
outlineVariant = Color(0xFFCAC4D0),
)
// Dark scheme
private val DarkColorScheme = darkColorScheme(
primary = Color(0xFFD0BCFF),
onPrimary = Color(0xFF381E72),
primaryContainer = Color(0xFF4F378B),
onPrimaryContainer = Color(0xFFEADDFF),
secondary = Color(0xFFCCC2DC),
onSecondary = Color(0xFF332D41),
secondaryContainer = Color(0xFF4A4458),
onSecondaryContainer = Color(0xFFE8DEF8),
tertiary = Color(0xFFEFB8C8),
onTertiary = Color(0xFF492532),
tertiaryContainer = Color(0xFF633B48),
onTertiaryContainer = Color(0xFFFFD8E4),
error = Color(0xFFF2B8B5),
onError = Color(0xFF601410),
errorContainer = Color(0xFF8C1D18),
onErrorContainer = Color(0xFFF9DEDC),
background = Color(0xFF1C1B1F),
onBackground = Color(0xFFE6E1E5),
surface = Color(0xFF1C1B1F),
onSurface = Color(0xFFE6E1E5),
surfaceVariant = Color(0xFF49454F),
onSurfaceVariant = Color(0xFFCAC4D0),
outline = Color(0xFF938F99),
outlineVariant = Color(0xFF49454F),
)Dynamic color (Android 12+)
@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,
shapes = Shapes,
content = content,
)
}Using theme colors
// Access colors from the theme
val colors = MaterialTheme.colorScheme
Text(
text = "Hello",
color = colors.onSurface,
)
Surface(
color = colors.surface,
contentColor = colors.onSurface,
) {
Text("Content inherits onSurface color")
}
// Use semantic color roles
Button(
colors = ButtonDefaults.buttonColors(
containerColor = colors.primary,
contentColor = colors.onPrimary,
)
) { Text("Primary action") }
OutlinedButton(
colors = ButtonDefaults.outlinedButtonColors(
contentColor = colors.primary,
)
) { Text("Secondary action") }
// Surface color variants
Surface(color = colors.surfaceVariant) {
Text("Subtle background", color = colors.onSurfaceVariant)
}Typography
val Typography = Typography(
displayLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 57.sp,
lineHeight = 64.sp,
letterSpacing = (-0.25).sp,
),
displayMedium = TextStyle(fontSize = 45.sp, lineHeight = 52.sp),
displaySmall = TextStyle(fontSize = 36.sp, lineHeight = 44.sp),
headlineLarge = TextStyle(fontSize = 32.sp, lineHeight = 40.sp),
headlineMedium = TextStyle(fontSize = 28.sp, lineHeight = 36.sp),
headlineSmall = TextStyle(fontSize = 24.sp, lineHeight = 32.sp),
titleLarge = TextStyle(fontSize = 22.sp, lineHeight = 28.sp, fontWeight = FontWeight.Medium),
titleMedium = TextStyle(fontSize = 16.sp, lineHeight = 24.sp, fontWeight = FontWeight.Medium),
titleSmall = TextStyle(fontSize = 14.sp, lineHeight = 20.sp, fontWeight = FontWeight.Medium),
bodyLarge = TextStyle(fontSize = 16.sp, lineHeight = 24.sp, letterSpacing = 0.5.sp),
bodyMedium = TextStyle(fontSize = 14.sp, lineHeight = 20.sp, letterSpacing = 0.25.sp),
bodySmall = TextStyle(fontSize = 12.sp, lineHeight = 16.sp, letterSpacing = 0.4.sp),
labelLarge = TextStyle(fontSize = 14.sp, lineHeight = 20.sp, fontWeight = FontWeight.Medium, letterSpacing = 0.1.sp),
labelMedium = TextStyle(fontSize = 12.sp, lineHeight = 16.sp, fontWeight = FontWeight.Medium, letterSpacing = 0.5.sp),
labelSmall = TextStyle(fontSize = 11.sp, lineHeight = 16.sp, fontWeight = FontWeight.Medium, letterSpacing = 0.5.sp),
)
// Usage
Text("Headline", style = MaterialTheme.typography.headlineMedium)
Text("Body", style = MaterialTheme.typography.bodyLarge)
Text("Caption", style = MaterialTheme.typography.labelSmall)Custom fonts
// Define font family
val MyFonts = FontFamily(
Font(R.font.my_font_regular, FontWeight.Normal),
Font(R.font.my_font_medium, FontWeight.Medium),
Font(R.font.my_font_bold, FontWeight.Bold),
)
// Use in typography
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = MyFonts,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
),
headlineMedium = TextStyle(
fontFamily = MyFonts,
fontWeight = FontWeight.Bold,
fontSize = 28.sp,
),
)Shapes
val Shapes = Shapes(
small = RoundedCornerShape(4.dp), // chips, text fields
medium = RoundedCornerShape(8.dp), // cards, dialogs
large = RoundedCornerShape(16.dp), // bottom sheets, FABs
extraLarge = RoundedCornerShape(28.dp), // modal bottom sheets
)
// Usage — MaterialTheme.shapes is automatically applied
Card { /* uses medium shape */ }
Button({ /* ... */ }) { /* uses small shape */ }
// Override per component
Card(shape = RoundedCornerShape(24.dp)) { /* custom shape */ }Custom component styling with CompositionLocal
// Define custom component defaults
@Composable
fun MyButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
colors: ButtonColors = MyButtonDefaults.colors(),
content: @Composable RowScope.() -> Unit,
) {
Button(
onClick = onClick,
modifier = modifier,
enabled = enabled,
colors = colors,
content = content,
)
}
object MyButtonDefaults {
@Composable
fun colors(
containerColor: Color = MaterialTheme.colorScheme.primary,
contentColor: Color = MaterialTheme.colorScheme.onPrimary,
) = ButtonDefaults.buttonColors(
containerColor = containerColor,
contentColor = contentColor,
)
}Elevation
// Surface with elevation
Surface(
tonalElevation = 2.dp, // color tint shift (M3)
shadowElevation = 4.dp, // actual shadow
shape = RoundedCornerShape(12.dp),
) {
Text("Elevated surface")
}
// Card with elevation
ElevatedCard(
elevation = CardDefaults.elevatedCardElevation(
defaultElevation = 4.dp,
)
) {
Text("Elevated card")
}