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.