O Material Design sempre foi uma linguagem visual competente, mas durante anos carregou uma crítica justa entre times de produto Android: ele era “plano demais”, geométrico e, em muitos apps, indistinguível. O Google respondeu a isso com o Material 3 Expressive, uma evolução lançada em meados de 2025 que amplifica expressividade visual sem quebrar o contrato do Material 3 que já tínhamos em produção. Em 2026, com a androidx.compose.material3 madura e o Android 16, adotar o Expressive deixou de ser experimento e virou a configuração padrão recomendada para apps novos.

Neste guia você implementa o Material 3 Expressive com Jetpack Compose e Kotlin em 2026, do zero à migração de um tema existente, cobrindo as novas formas (Shape), tipografia expressiva, fonte variável, motion com Spring e SharedTransition, layouts adaptativos por WindowSizeClass e testes visuais. Se você ainda não dominou o Compose, vale revisar antes o guia de Jetpack Compose e o artigo sobre edge-to-edge no Android 16, porque o Expressive pressupõe uma base sólida de theming e janela.

1. O que muda do Material 3 para o Material 3 Expressive

O Expressive não é o Material 4. É uma expansão do mesmo material3 que você já usa, ativada por API e por tokens de tema. As mudanças centrais são:

  • Shapes mais vivos: além do Shapes clássico (extra-small a large), o Expressive introduz o LargeShapes, MediumShapes e SmallShapes com cantos malhosos (RoundedCornerShape, SquircleShape) e dimensões maiores, dando personalidade a cards, botões e sheets.
  • Tipografia expressiva: tipografia com tamanhos e pesos mais contrastantes, pensada para display. Em vez de só headlineLarge, o tema expõe um Typography com variações ópticas e suporte de primeira classe a fonte variável (variable font), permitindo FontVariation.Settings por token.
  • Motion como fundação: animações baseadas em Spring (não mais só tweens lineares), SharedTransitionLayout para transições entre telas e expressiveMotionScheme() que entrega curvas consistentes.
  • Layouts adaptativos nativos: o WindowSizeClass passa a guiar não só colunas, mas densidade, spacing e tipo de navegação (bottom bar vs. rail vs. drawer) de forma declarativa.
  • Cores tonais mais ricas: a paleta dinâmica (dynamicColor) continua, mas o Expressive adiciona harmonias de cor e o conceito de “cor de destaque” que se adapta ao conteúdo.

A boa notícia para quem já tem app em produção: a migração é incremental. Você pode ativar Expressive por componente, sem reescrever o tema inteiro.

2. Dependências e versões

No libs.versions.toml (padrão que vale seguir — veja o artigo sobre Gradle Version Catalog):

[versions]
androidxComposeBom = "2026.03.00"
androidxMaterial3Expressive = "1.4.0"

[libraries]
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" }
androidx-material3-expressive = { group = "androidx.compose.material3", name = "material3", version.ref = "androidxMaterial3Expressive" }

No app/build.gradle.kts:

dependencies {
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.material3.expressive)
}

Em 2026 a material3 já traz o Expressive embutido — não há um artifact separado material3-expressive. O que existe é um scheme (expressiveColorScheme, expressiveMotionScheme) e componentes novos (FlexibleBottomSheet, LoadingIndicator) que você opta por usar.

3. Esquema de cores expressivo

O ponto de entrada é o MaterialExpressiveTheme, que sobrescreve o MaterialTheme clássico injetando os esquemas expressivos:

@Composable
fun KotlinBrTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    dynamicColor: Boolean = true,
    content: @Composable () -> Unit
) {
    val context = LocalContext.current
    val baseScheme = when {
        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ->
            if (darkTheme) dynamicDarkColorScheme(context)
            else dynamicLightColorScheme(context)
        darkTheme -> DarkColors
        else -> LightColors
    }

    val expressiveScheme = if (darkTheme) {
        expressiveDarkColorScheme(baseScheme)
    } else {
        expressiveLightColorScheme(baseScheme)
    }

    MaterialExpressiveTheme(
        colorScheme = expressiveScheme,
        typography = KotlinTypography,
        shapes = KotlinShapes(),
        motionScheme = expressiveMotionScheme(),
        content = content
    )
}

O expressiveLightColorScheme deriva variações extra (tons 60, 70, 80) a partir do baseScheme, expondo cores como colorScheme.containerHighest e colorScheme.onAccentContainer. Quando dynamicColor está ativo (Android 12+), o esquema herda o papel de parede — comportamento idêntico ao Material 3 clássico, mas com mais graus de cor disponíveis.

4. Shapes e formas expressivas

A maior mudança visual fica nas formas. O Expressive introduz Shapes() com mais níveis e cantos maiores:

@Composable
fun KotlinShapes(): Shapes = Shapes(
    extraSmall = RoundedCornerShape(8.dp),
    small = RoundedCornerShape(12.dp),
    medium = RoundedCornerShape(20.dp),
    large = RoundedCornerShape(32.dp),
    extraLarge = RoundedCornerShape(48.dp)
)

Para componentes de destaque — FAB, cards hero, bottom sheets — o Expressive oferece SquircleShape (canto “superelíptico”, o mesmo do iOS) e o MaterialShapes com formas matemáticas nomeadas (Cookie9Sided, Slanted). Use com parcimônia: formas exóticas funcionam em pontos focais, não em listas inteiras.

FloatingActionButton(
    onClick = { /* ... */ },
    shape = MaterialShapes.Cookie9Sided.toShape(),
    containerColor = MaterialTheme.colorScheme.primaryContainer,
    contentColor = MaterialTheme.colorScheme.onPrimaryContainer
) {
    Icon(Icons.Default.Add, contentDescription = "Adicionar")
}

5. Tipografia expressiva com fonte variável

A tipografia expressiva ganha mais contraste. Um Typography típico:

val KotlinTypography = Typography(
    displayLarge = TextStyle(
        fontFamily = VariableFont,
        fontWeight = FontWeight.W700,
        fontSize = 57.sp,
        lineHeight = 64.sp,
        letterSpacing = (-0.25).sp
    ),
    headlineLarge = TextStyle(
        fontFamily = VariableFont,
        fontWeight = FontWeight.W600,
        fontSize = 32.sp,
        lineHeight = 40.sp
    ),
    bodyLarge = TextStyle(
        fontFamily = VariableFont,
        fontWeight = FontWeight.W400,
        fontSize = 16.sp,
        lineHeight = 24.sp,
        letterSpacing = 0.5.sp
    )
)

Para usar fonte variável, carregue via FontVariation:

val VariableFont = FontFamily(
    Font(
        R.font.inter_variable,
        variationSettings = FontVariation.Settings(
            FontVariation.weight(600),
            FontVariation.slant(0f),
            FontVariation.opticalSize(16f)
        )
    )
)

A vantagem é performance e consistência: um único arquivo .ttf cobre toda a faixa de peso (100–900), eliminando o bundle de múltiplas fontes e garantindo transições suaves em animações que interpolam fontWeight.

6. Motion com Spring e SharedTransition

O expressiveMotionScheme() entrega curvas pré-configuradas, mas o real ganho do Expressive está no uso direto de Spring e SharedTransitionLayout. Um exemplo de transição entre um item de lista e seu detalhe:

SharedTransitionLayout {
    NavHost(navController, startDestination = "lista") {
        composable("lista") {
            ListaScreen(
                onItemClick = { id -> navController.navigate("detalhe/$id") },
                animatedVisibilityScope = this@composable,
                sharedTransitionScope = this@SharedTransitionLayout
            )
        }
        composable("detalhe/{id}") { backStackEntry ->
            DetalheScreen(
                id = backStackEntry.arguments?.getString("id").orEmpty(),
                animatedVisibilityScope = this@composable,
                sharedTransitionScope = this@SharedTransitionLayout
            )
        }
    }
}

Dentro do item compartilhado, marque-o com Modifier.sharedElement():

Image(
    painter = rememberAsyncImagePainter(post.imagemUrl),
    contentDescription = post.titulo,
    modifier = Modifier
        .size(120.dp)
        .sharedElement(
            rememberSharedContentState(key = "imagem-${post.id}"),
            animatedVisibilityScope = animatedVisibilityScope
        )
)

O resultado é a imagem “voando” da lista para o detalhe com interpolação automática de tamanho e posição — sem biblioteca third-party, sem boilerplate de Transition.

Para microinterações, prefira Spring a tween:

val scale by animateFloatAsState(
    targetValue = if (pressed) 0.95f else 1f,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioMediumBouncy,
        stiffness = Spring.StiffnessLow
    ),
    label = "scale"
)

7. Layouts adaptativos com WindowSizeClass

O Expressive formaliza a adaptação por tamanho de janela, algo essencial em 2026 com foldables e tablets rodando Android 16. O WindowSizeClass classifica a largura em compact (<600dp), medium (600–840dp) e expanded (>840dp):

val windowSize = currentWindowAdaptiveInfo().windowSizeClass
when (windowSize.windowWidthSizeClass) {
    WindowWidthSizeClass.Compact -> {
        // Smartphone em retrato: bottom bar
        Scaffold(bottomBar = { BottomBar() }) { padding ->
            FeedCompact(contentPadding = padding)
        }
    }
    WindowWidthSizeClass.Medium -> {
        // Tablet pequeno / foldable aberto: rail
        Scaffold { FeedMedium() }
    }
    else -> {
        // Tablet grande / desktop: lista-detalhe
        TwoPaneLayout()
    }
}

Esse padrão também melhora a performance de scroll em telas largas e habilita corretamente o edge-to-edge no Android 16, evitando o clássico bug de conteúdo colado na barra do sistema.

8. Componentes novos que valem a pena

O Expressive trouxe componentes que resolvem dores reais:

  • LoadingIndicator: substitui o CircularProgressIndicator com um spinner que “respira” e pode ganhar cor de destaque, alinhado ao motion expressivo.
  • FlexibleBottomSheet: bottom sheet que respeita gestos e se adapta ao conteúdo sem ocupar a tela toda, ideal para contextos rápidos.
  • SplitToggleChip: chip de filtro com área de toque separada da seleção, melhorando acessibilidade.
  • Carousel: carrossel acessível com snap e suporte a itens de largura variável, substituindo o HorizontalPager para casos de showcase.
LoadingIndicator(
    modifier = Modifier.padding(24.dp),
    color = MaterialTheme.colorScheme.primary
)

9. Migração gradual de um app existente

Para um app já em Material 3 clássico, a migração pode ser feita em três passos reversíveis:

  1. Substituir MaterialTheme por MaterialExpressiveTheme no nível raiz. Como ambos expõem o mesmo colorScheme e typography, os componentes existentes continuam funcionando sem alteração.
  2. Ativar expressiveMotionScheme() e trocar tweens críticos por Spring onde há feedback tátil (botões, FAB, chips).
  3. Adotar WindowSizeClass em telas-chave (lista, detalhe, configurações) antes de tocar em componentes novos.

Cada passo é um commit isolado, testável e revertível. Em apps grandes, vale orquestrar isso junto com a modularização Android em Compose para que cada feature adopte Expressive no seu ritmo.

10. Performance, acessibilidade e testes

O Expressive aumenta o uso de animação e fontes variáveis, então alguns cuidados:

  • Honorar “Remover animações”: o Compose expõe LocalAccessibilityManager.currentReducedMotion() (ou Settings.Global.ANIMATOR_DURATION_SCALE). Desligue Spring bouncy e SharedTransition quando o usuário solicitar movimento reduzido.
  • Baseline profiles: animações e SharedTransition se beneficiam de AOT. Gere profiles com o Macrobenchmark para manter 60fps em dispositivos low-end.
  • Contraste: o esquema expressivo deriva muitos tons; valide contraste WCAG AA nos textos sobre containerHighest.
  • Glance e widgets: widgets de homescreen ainda usam o glance (veja o guia de Glance Widgets) e não herdam MaterialExpressiveTheme — use o ColorScheme do app como referência visual, mas mapeie para ColorProviders do Glance.

Para testes visuais, o snapshot testing em Compose é o gate natural: capture a versão Material 3 clássica, migre e compare os diffs conscientemente.

11. Quando NÃO adotar Expressive

Expressivo é uma escolha de produto, não uma obrigação técnica. Evite quando:

  • O app tem marca muito forte com design system próprio (bancos, fintechs) — o Expressive compete visualmente com sua identidade.
  • A audiência é majoritariamente acessibilidade-first e prefere UIs sóbrias (apps governamentais, saúde) — o contraste extra pode distrair.
  • O time não tem banda para testar animações em diversos dispositivos — motion mal calibrado vira jank em Android low-end, pior que sem animação.

Conclusão

O Material 3 Expressive é o primeiro “Material” que se sente vivo desde o lançamento do Material You. Com Jetpack Compose e Kotlin em 2026, ele entrega ganhos reais de percepção de qualidade, motion fluido e adaptação a foldables — sem exigir reescrita. Aplique de forma incremental, meça performance com baseline profiles, honoreie preferências de acessibilidade e você terá um app que parece de 2026, não de 2021.

Para ir além, combine Expressive com os widgets de homescreen com Glance e uma arquitetura modular em Compose — o resultado é uma base Android moderna, performática e pronta para escalar junto com o ecossistema Kotlin.