MaterialTheme — el sistema de diseño
MaterialTheme es un composable especial que provee colores, tipografía y formas a todos sus descendientes. Cualquier composable dentro de él puede leer esos valores con MaterialTheme.colorScheme, MaterialTheme.typography y MaterialTheme.shapes.
// Cada app tiene su propio tema que envuelve todo el contenido
@Composable
fun MiAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
MaterialTheme(
colorScheme = if (darkTheme) esquemaOscuro else esquemaClaro,
typography = miTypography,
shapes = miShapes,
content = content
)
}
// En la Activity:
setContent {
MiAppTheme {
Surface(color = MaterialTheme.colorScheme.background) {
AppNavigation()
}
}
}
ColorScheme — el sistema de colores de M3
Material 3 usa un sistema de roles de color en lugar de nombres genéricos. Cada color tiene un propósito semántico:
// Definir los colores del tema
private val LightColorScheme = lightColorScheme(
primary = Color(0xFF1565C0), // color principal (botones, FAB)
onPrimary = Color.White, // texto/iconos sobre primary
primaryContainer = Color(0xFFD1E4FF), // contenedores con tono primario
onPrimaryContainer = Color(0xFF001D36),
secondary = Color(0xFF535F70),
surface = Color(0xFFF8F9FF), // superficies (Card, Dialog)
onSurface = Color(0xFF191C20), // texto sobre surface
surfaceVariant = Color(0xFFDFE2EB),
error = Color(0xFFBA1A1A),
background = Color(0xFFF8F9FF)
)
// Usar los roles en los composables — NUNCA hardcodees colores
Text(
text = "Precio",
color = MaterialTheme.colorScheme.onSurfaceVariant // correcto
)
Text(
text = "Precio",
color = Color(0xFF6B6B6B) // incorrecto — no respeta dark mode
)
Material Theme BuilderGenerá tu paleta de colores en m3.material.io/theme-builder. Podés exportar el código Kotlin directamente con todos los colores y roles ya configurados para modo claro y oscuro.
Dark mode — completamente automático
Si usás siempre los roles de MaterialTheme.colorScheme, el dark mode funciona sin escribir una línea extra. Solo definís los dos esquemas y Compose elige el correcto:
private val DarkColorScheme = darkColorScheme(
primary = Color(0xFF9ECAFF),
onPrimary = Color(0xFF003258),
primaryContainer = Color(0xFF004880),
surface = Color(0xFF111318),
onSurface = Color(0xFFE2E2E9),
background = Color(0xFF111318)
)
@Composable
fun MiAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(), // sigue la preferencia del sistema
content: @Composable () -> Unit
) {
val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme
MaterialTheme(colorScheme = colorScheme, content = content)
}
Typography — escala tipográfica
M3 define una escala de 15 estilos de texto con nombres semánticos:
val MiTypography = Typography(
displayLarge = TextStyle(
fontFamily = FontFamily(Font(R.font.roboto_regular)),
fontWeight = FontWeight.Normal,
fontSize = 57.sp,
lineHeight = 64.sp
),
headlineMedium = TextStyle(
fontFamily = FontFamily(Font(R.font.roboto_regular)),
fontWeight = FontWeight.Normal,
fontSize = 28.sp,
lineHeight = 36.sp
),
titleLarge = TextStyle(
fontFamily = FontFamily(Font(R.font.roboto_medium)),
fontWeight = FontWeight.Medium,
fontSize = 22.sp,
lineHeight = 28.sp
),
bodyLarge = TextStyle(
fontFamily = FontFamily(Font(R.font.roboto_regular)),
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily(Font(R.font.roboto_medium)),
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp
)
// ... el resto usa los defaults de M3
)
// Uso en composables:
Text(text = "Título principal", style = MaterialTheme.typography.headlineMedium)
Text(text = "Descripción", style = MaterialTheme.typography.bodyLarge)
Text(text = "Etiqueta", style = MaterialTheme.typography.labelSmall)
Shapes — formas consistentes
val MiShapes = Shapes(
extraSmall = RoundedCornerShape(4.dp), // chips, tooltips
small = RoundedCornerShape(8.dp), // text fields, snackbars
medium = RoundedCornerShape(12.dp), // cards, dialogs pequeños
large = RoundedCornerShape(16.dp), // bottom sheets
extraLarge = RoundedCornerShape(28.dp) // FAB, dialogs grandes
)
// Uso:
Card(shape = MaterialTheme.shapes.medium) { ... }
Button(shape = MaterialTheme.shapes.extraLarge) { ... } // botón pill
Estructura recomendada del tema
// ui/theme/Color.kt — todos los colores del tema
// ui/theme/Type.kt — la escala tipográfica
// ui/theme/Shape.kt — las formas
// ui/theme/Theme.kt — el MiAppTheme composable que junta todo
// Theme.kt
@Composable
fun MiAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true, // Android 12+
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 = MiTypography,
shapes = MiShapes,
content = content
)
}
Dynamic Color — colores del wallpaper
En Android 12+, Dynamic Color extrae la paleta de colores del wallpaper del usuario y genera automáticamente el ColorScheme de tu app. Es una característica diferenciadora de Material You.
// El código del bloque anterior ya lo incluye.
// En Android 12+: el tema cambia según el wallpaper del usuario.
// En Android 11 e inferior: cae al ColorScheme que vos definiste.
¿Activar Dynamic Color o no?Depende de tu app. Para apps de marca (bancos, e-commerce) donde el color es parte de la identidad visual, conviene desactivarlo (dynamicColor = false). Para apps utilitarias o personales, Dynamic Color da una experiencia más integrada con el sistema.