Skip to main content

Material 3 Theme Adapter

viewcompose-material3 is the design-system layer for Google Material 3 on Android. It reads Material theme colors, typography, and shapes into platform-independent UiThemeTokens, resolves dynamic-color contexts, and refreshes tokens after configuration or imperative theme changes.

It also owns a bounded Material pressure slice for Surface/Card, Button, Switch, TextField, and NavigationBar. Those APIs resolve named recipes into shared Basic primitives, native behavioral cores, or neutral renderer nodes; this module does not participate in View reconciliation or map a generic node to a Material Components widget. The Android engine therefore remains usable without Material Components; only this module and explicitly Material-backed integrations own that dependency.

Artifact and stability

dependencies {
implementation("com.viewcompose:viewcompose-material3:0.1.0-alpha01")
}
  • Stability: Alpha.
  • Platform: Android library, minSdk 24, compileSdk 36, and Java 11 bytecode.
  • API dependency: viewcompose-ui-foundation.
  • Implementation dependencies: Material Components, AppCompat, and AndroidX Core.
  • Baseline: standard, non-Expressive Material 3 from Material Components 1.13.0.

Theme resolution

Material3ThemeBridge.resolveContext creates the stable themed context that must be shared by the root View and overlays. Material3Theme provides the mapped token snapshot and consumes the design-system-neutral Environment.resourceRevision; standard-host configuration observation is owned by viewcompose-host-android, not Material. Material3ResolvedTheme.refresh() refreshes its stable wrapper before token mapping. Material3ThemeRefreshController remains available only for low-level hosts that have not installed the standard Android resource environment.

val resolvedTheme = Material3ThemeBridge.resolveContext(context)
Material3Theme(resolvedTheme = resolvedTheme) {
Text("Content using Material 3 theme tokens")
}

Material applications receive this lifecycle automatically through the named viewcompose-material3-android integration. Lower-level integrations may continue to resolve the Context and install Material3Theme explicitly, but must supply configuration/resource invalidation at their host boundary.

Material3Theme(tokens = ...) provides the same recipe and diagnostic scope from static tokens without reading Android resources. Both overloads export Material3Reference.recipeSet plus the same five-family backend/conformance attribution through DesignSystemDiagnostics.

Public component pressure slice

Entry pointRecipe/backend boundaryCurrent conformance
Material3Surface, Material3CardMaterial recipes resolved into shared BasicSurfaceExact
Material3ButtonMaterial variant recipe resolved into shared BasicButtonExact
Material3SwitchMaterial colors/type over the native Android Switch behavioral coreEquivalent
Material3TextFieldMaterial decoration around the native Android editing coreEquivalent
Material3NavigationBarMaterial selection recipe over the neutral navigation rendererEquivalent

The complete compiled pressure-slice example is Material3ThemeSamples.kt.

The Material and One UI public vocabularies intentionally remain different. They share neutral execution and diagnostic contracts only; no union component API or global recipe bundle is introduced.

Token baseline and fallback

Material3ThemeDefaults.light() and Material3ThemeDefaults.dark() provide deterministic Material 3 snapshots when no Android themed Context is available or an individual Android attribute is missing. Each snapshot includes:

  • the complete Material color scheme used by the adapter, including surface-container, inverse, outline, and container-content roles;
  • all 15 standard display, headline, title, body, and label typography roles;
  • extra-small, small, medium, large, extra-large, and full shape roles; and
  • the selected standard sizing profile for buttons, text fields, segmented controls, progress indicators, FABs, search, badges, and native compact-input effective targets; and
  • standard interaction opacities: 0.10 pressed, 0.10 focused, and 0.08 hovered.

The standard Button profile uses a 48dp effective target with a centered 40dp visible container for compact and medium buttons, and a 56dp target with a 48dp visible container for large buttons. This is a token choice consumed through UI Foundation's design-system-neutral sizing contract; the Material adapter does not participate in Android hit testing or View drawing.

Button and IconButton Defaults combine those interaction opacities with each variant's enabled content role before emitting a NodeSpec. For example, a primary Button uses onPrimary, while a tonal Button uses onSecondaryContainer. The adapter does not produce selectors and Android Renderer remains unaware of Material role names.

Checkbox, RadioButton, Switch, and Slider use a 48dp minimum effective height. Their native indicator, thumb, track, and label geometry remains centered and platform-rendered; an explicit exact application height or tighter parent constraint still wins. This policy is expressed through UI Foundation's neutral control-sizing token rather than a Material branch in Android Renderer.

Their enabled selection color is the Material primary role resolved by UI Foundation rather than the AppCompat colorControlActivated bridge value. Slider uses secondaryContainer for its inactive segment. The bridge continues to expose legacy state colors for applications that request them explicitly, but they do not replace these component semantic roles.

The Android bridge replaces available values from the active theme. It reads all 15 Material text appearances and the five absolute shapeAppearanceCorner* roles, while legacy Android large/medium/small text appearances remain title/body/label family fallbacks. Missing display and headline values retain the complete static Material snapshot instead of being collapsed onto a legacy size or falling back to UI Foundation's neutral defaults.

UiThemeMetadata.provenance records viewcompose-material3/android-xml, viewcompose-material3/android-dynamic, or viewcompose-material3/static as the base producer. For the pressure slice, every consumed color, state-color, type, shape, control, interaction, and overlay path can resolve its effective origin. A present Android attribute is marked Android theme or dynamic; a missing value remains a named static Material fallback with FrameworkDefault origin; UiThemeOverride marks only the token families replaced by the application. The complete static snapshot also reports FrameworkDefault, rather than misclassifying first-party defaults as application-owned custom values.

UiDesignSystemAttribution.integrations records the overlay transport and per-type presenters. Material Dialog/Popup content uses captured Material locals, Snackbar and modal bottom sheet report their Material Components adapters, and Android Toast is an explicit degraded platform fallback.

The adapter does not add Material policy to Android Renderer. Component defaults resolve semantic roles in UI Foundation before a NodeSpec reaches the renderer. Button visual/effective height separation is explicitly represented by the sizing tokens and NodeSpec. The native compact-input target policy is likewise consumed by UI Foundation; composite Chip target/surface separation, TextField floating-label/focus structure and exact Switch/Slider visual geometry are not implied by the token bridge. The current named TextField and Switch deliberately retain native behavioral cores and report Equivalent conformance. Material3Switch preserves native tap and thumb-drag handling, and accepted caller state does not restart the platform's in-flight thumb transition; further visual replacement requires the Phase 12 behavior and accessibility gate.

The generated reference is available in the viewcompose-material3 API tree.

Compatibility notes

This artifact begins at 0.1.0-alpha01. Android theme bridge types previously shipped from the UI foundation were renamed to the Material3* API family and moved here without compatibility aliases. The current alpha line also adds complete shape and typography roles and a public static Material 3 fallback; consumers that exhaustively construct or destructure affected UI Foundation data classes must update with the corresponding alpha release.

The standard interaction-opacity profile is retained across Android theme mapping because the platform theme does not expose one complete per-state opacity family. Applications can replace the generic UiInteractionTokens or a component's resolved stateLayerColors without depending on Material APIs.

The bridge no longer republishes Android colorControlHighlight through the removed UiColors.ripple or UiStateColors.controlHighlight slots. Interaction feedback is resolved from the Material opacity recipe plus each component's semantic content role; an application that needs a different policy supplies UiInteractionTokens explicitly.

The static Material3Theme overload and the six named pressure-slice entry points are additive Q3 APIs. Their enums and Material3Reference are Q2 identity/value contracts. They expose no Material widget type and do not change generic UI Foundation component signatures.