Material Design 3 is the next evolution of Material Design. It includes updated theming, components, and Material You personalization features like dynamic color. It's an update to Material Design 2 and is cohesive with the new visual style and system UI on Android 12 and above.
This guide focuses on migrating from the Compose Material (androidx.compose.material) Jetpack library to the Compose Material 3 (androidx.compose.material3) Jetpack library.
Approaches
In general you should not use both M2 and M3 in a single app long-term. This is due to the fact that the two design systems and respective libraries differ significantly in terms of their UX/UI designs and Compose implementations.
Your app may use a design system, such as one created using Figma. In such cases, we also highly recommend that you or your design team migrate it from M2 to M3 before starting the Compose migration. It doesn't make sense to migrate an app to M3 if its UX/UI design is based on M2.
Furthermore, your approach to migration should differ depending on your app's size, complexity, and UX/UI design. Doing so helps you to minimize the impact on your codebase. You should take a phased approach to migration.
When to migrate
You should start the migration as soon as possible. However, it's important to consider whether or not your app is in a realistic position to fully migrate from M2 to M3. There are some blocker scenarios to consider investigating before you start:
Scenario | Recommended approach |
---|---|
No blockers | Begin phased migration |
A component from M2 is not available in M3 yet. See the Components and layouts section below. | Begin phased migration |
You or your design team haven't migrated the app's design system from M2 to M3 | Migrate design system from M2 to M3, then begin phased migration |
Even if you're affected by the above scenarios you should take a phased approach to migration before committing and releasing an app update. In these cases, you would use M2 and M3 side-by-side, and gradually phase out M2 while migrating to M3.
Phased approach
The general steps to a phased migration are as follows:
- Add M3 dependency alongside M2 dependency.
- Add M3 version(s) of your app's theme(s) alongside M2 version(s) of your app's theme(s).
- Migrate individual modules, screens, or composables to M3, depending on the size and complexity of your app (see below sections for details).
- Once fully migrated, remove the M2 version(s) of your app's theme(s).
- Remove M2 dependency.
Dependencies
M3 has a separate package and version to M2:
M2
implementation "androidx.compose.material:material:$m2-version"
M3
implementation "androidx.compose.material3:material3:$m3-version"
See the latest M3 versions on the Compose Material 3 releases page.
Other Material dependencies outside of the main M2 and M3 libraries have not changed. They use a mix of the M2 and M3 packages and versions, but this has no impact on migration. They can be used as-is with M3:
Library | Package and version |
---|---|
Compose Material Icons | androidx.compose.material:material-icons-*:$m2-version |
Compose Material Ripple | androidx.compose.material:material-ripple:$m2-version |
Experimental APIs
Some M3 APIs are considered experimental. In such cases you need to opt in at
the function or file level using the ExperimentalMaterial3Api
annotation:
import androidx.compose.material3.ExperimentalMaterial3Api
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppComposable() {
// M3 composables
}
Theming
In both M2 and M3, the theme composable is named MaterialTheme
but the import
packages and parameters differ:
M2
import androidx.compose.material.MaterialTheme
MaterialTheme(
colors = AppColors,
typography = AppTypography,
shapes = AppShapes
) {
// M2 content
}
M3
import androidx.compose.material3.MaterialTheme
MaterialTheme(
colorScheme = AppColorScheme,
typography = AppTypography,
shapes = AppShapes
) {
// M3 content
}
Color
The color system in M3 is significantly different to M2. The
number of color parameters has increased, they have different names, and they
map differently to M3 components. In Compose, this applies to the M2
Colors
class, the M3 ColorScheme
class, and related functions:
M2
import androidx.compose.material.lightColors
import androidx.compose.material.darkColors
val AppLightColors = lightColors(
// M2 light Color parameters
)
val AppDarkColors = darkColors(
// M2 dark Color parameters
)
val AppColors = if (darkTheme) {
AppDarkColors
} else {
AppLightColors
}
M3
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
val AppLightColorScheme = lightColorScheme(
// M3 light Color parameters
)
val AppDarkColorScheme = darkColorScheme(
// M3 dark Color parameters
)
val AppColorScheme = if (darkTheme) {
AppDarkColorScheme
} else {
AppLightColorScheme
}
Given the significant differences between the M2 and M3 color systems, there's
no reasonable mapping for Color
parameters. Instead, use the Material
Theme Builder tool to generate an M3 color scheme. Use the M2
colors as core source colors in the tool, which the tool expands into tonal
palettes used by the M3 color scheme. The following mappings are recommended as
a starting point:
M2 | Material Theme Builder |
---|---|
primary |
Primary |
primaryVariant |
Secondary |
secondary |
Tertiary |
surface or background |
Neutral |
You can copy the color hex code values for light and dark themes from the tool and use them to implement an M3 ColorScheme instance. Alternatively, Material Theme Builder can export Compose code.
isLight
Unlike the M2 Colors
class, the M3 ColorScheme
class doesn't include an
isLight
parameter. In general you should try and model whatever needs
this information at the theme level. For example:
M2
import androidx.compose.material.lightColors
import androidx.compose.material.darkColors
import androidx.compose.material.MaterialTheme
@Composable
private fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) darkColors(…) else lightColors(…)
MaterialTheme(
colors = colors,
content = content
)
}
@Composable
fun AppComposable() {
AppTheme {
val cardElevation = if (MaterialTheme.colors.isLight) 0.dp else 4.dp
…
}
}
M3
import androidx.compose.material3.lightColorScheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.MaterialTheme
val LocalCardElevation = staticCompositionLocalOf { Dp.Unspecified }
@Composable
private fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val cardElevation = if (darkTheme) 4.dp else 0.dp
CompositionLocalProvider(LocalCardElevation provides cardElevation) {
val colorScheme = if (darkTheme) darkColorScheme(…) else lightColorScheme(…)
MaterialTheme(
colorScheme = colorScheme,
content = content
)
}
}
@Composable
fun AppComposable() {
AppTheme {
val cardElevation = LocalCardElevation.current
…
}
}
See the Custom design systems in Compose guide for more information.
Dynamic color
A new feature in M3 is dynamic color. Instead of using custom
colors, an M3 ColorScheme
can make use of device wallpaper colors on Android
12 and above, using the following functions:
Typography
The typography system in M3 is different to M2. The number of
typography parameters is roughly the same, but they have different names and
they map differently to M3 components. In Compose, this applies to the M2
Typography
class and the M3 Typography
class:
M2
import androidx.compose.material.Typography
val AppTypography = Typography(
// M2 TextStyle parameters
)
M3
import androidx.compose.material3.Typography
val AppTypography = Typography(
// M3 TextStyle parameters
)
The following TextStyle
parameter mappings are recommended as a starting
point:
M2 | M3 |
---|---|
h1 |
displayLarge |
h2 |
displayMedium |
h3 |
displaySmall |
N/A | headlineLarge |
h4 |
headlineMedium |
h5 |
headlineSmall |
h6 |
titleLarge |
subtitle1 |
titleMedium |
subtitle2 |
titleSmall |
body1 |
bodyLarge |
body2 |
bodyMedium |
caption |
bodySmall |
button |
labelLarge |
N/A | labelMedium |
overline |
labelSmall |
Shape
The shape system in M3 is different to M2. The number of shape
parameters has increased, they're named differently and they map differently to
M3 components. In Compose, this applies to the M2 Shapes
class and the
M3 Shapes
class:
M2
import androidx.compose.material.Shapes
val AppShapes = Shapes(
// M2 Shape parameters
)
M3
import androidx.compose.material3.Shapes
val AppShapes = Shapes(
// M3 Shape parameters
)
The following Shape
parameter mappings are recommended as a starting
point:
M2 | M3 |
---|---|
N/A | extraSmall |
small |
small |
medium |
medium |
large |
large |
N/A | extraLarge |
Components and layouts
Most components and layouts from M2 are available in M3. There are, however, some missing as well as new ones that didn't exist in M2. Furthermore, some M3 components have more variations than their equivalents in M2. In general the M3 API surfaces attempt to be as similar as possible to their closest equivalents in M2.
Given the updated color, typography and shape systems, M3 components tend to map differently to the new theming values. It's a good idea to check out the tokens directory in the Compose Material 3 source code as a source of truth for these mappings.
While some components require special considerations, the following function mappings are recommended as a starting point:
Missing APIs:
M2 | M3 |
---|---|
androidx.compose.material.swipeable |
Not available yet |
Replaced APIs:
M2 | M3 |
---|---|
androidx.compose.material.BackdropScaffold |
No M3 equivalent, migrate to Scaffold or BottomSheetScaffold instead |
androidx.compose.material.BottomDrawer |
No M3 equivalent, migrate to ModalBottomSheet instead |
Renamed APIs:
All other APIs:
See the latest M3 components and layouts on the Compose Material 3 API reference overview, and keep an eye out on the releases page for new and updated APIs.
Scaffold, snackbars and navigation drawer
Scaffold in M3 is different to M2. In both M2 and M3, the main layout composable
is named Scaffold
but the import packages and parameters differ:
M2
import androidx.compose.material.Scaffold
Scaffold(
// M2 scaffold parameters
)
M3
import androidx.compose.material3.Scaffold
Scaffold(
// M3 scaffold parameters
)
The M2 Scaffold
contains a backgroundColor
parameter is now named to
containerColor
in the M3 Scaffold
:
M2
import androidx.compose.material.Scaffold
Scaffold(
backgroundColor = …,
content = { … }
)
M3
import androidx.compose.material3.Scaffold
Scaffold(
containerColor = …,
content = { … }
)
The M2 ScaffoldState
class no longer exists in M3 as it contains a
drawerState
parameter which is no longer needed. To show snackbars with
the M3 Scaffold
, use SnackbarHostState
instead:
M2
import androidx.compose.material.Scaffold
import androidx.compose.material.rememberScaffoldState
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
content = {
…
scope.launch {
scaffoldState.snackbarHostState.showSnackbar(…)
}
}
)
M3
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
content = {
…
scope.launch {
snackbarHostState.showSnackbar(…)
}
}
)
All of the drawer*
parameters from the M2 Scaffold
have been removed from
the M3 Scaffold
. These include parameters such as drawerShape
and
drawerContent
. To show a drawer with the M3 Scaffold
, use a navigation
drawer composable, such as ModalNavigationDrawer
, instead:
M2
import androidx.compose.material.DrawerValue
import
import androidx.compose.material.Scaffold
import androidx.compose.material.rememberDrawerState
import androidx.compose.material.rememberScaffoldState
val scaffoldState = rememberScaffoldState(
drawerState = rememberDrawerState(DrawerValue.Closed)
)
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
drawerContent = { … },
drawerGesturesEnabled = …,
drawerShape = …,
drawerElevation = …,
drawerBackgroundColor = …,
drawerContentColor = …,
drawerScrimColor = …,
content = {
…
scope.launch {
scaffoldState.drawerState.open()
}
}
)
M3
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.Scaffold
import androidx.compose.material3.rememberDrawerState
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet(
drawerShape = …,
drawerTonalElevation = …,
drawerContainerColor = …,
drawerContentColor = …,
content = { … }
)
},
gesturesEnabled = …,
scrimColor = …,
content = {
Scaffold(
content = {
…
scope.launch {
drawerState.open()
}
}
)
}
)
Top app bar
Top app bars in M3 are different to those in M2. In both M2
and M3, the main top app bar composable is named TopAppBar
but the import
packages and parameters differ:
M2
import androidx.compose.material.TopAppBar
TopAppBar(…)
M3
import androidx.compose.material3.TopAppBar
TopAppBar(…)
Consider using the M3 CenterAlignedTopAppBar
if you were previously
centering content within the M2 TopAppBar
. It's good to be aware of the
MediumTopAppBar
and LargeTopAppBar
as well.
M3 top app bars contain a new scrollBehavior
parameter to provide different
functionality on scroll through the TopAppBarScrollBehavior
class, such
as changing elevation. This works in conjunction with scrolling content via
Modifer.nestedScroll
. This was possible in the M2 TopAppBar
by
manually changing the elevation
parameter:
M2
import androidx.compose.material.AppBarDefaults
import androidx.compose.material.Scaffold
import androidx.compose.material.TopAppBar
val state = rememberLazyListState()
val isAtTop by remember {
derivedStateOf {
state.firstVisibleItemIndex == 0 && state.firstVisibleItemScrollOffset == 0
}
}
Scaffold(
topBar = {
TopAppBar(
elevation = if (isAtTop) {
0.dp
} else {
AppBarDefaults.TopAppBarElevation
},
…
)
},
content = {
LazyColumn(state = state) { … }
}
)
M3
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
TopAppBar(
scrollBehavior = scrollBehavior,
…
)
},
content = {
LazyColumn { … }
}
)
Bottom navigation / Navigation bar
Bottom navigation in M2 has been renamed to navigation bar in
M3. In M2 there are the BottomNavigation
and
BottomNavigationItem
composables, while in M3 there are the
NavigationBar
and NavigationBarItem
composables:
M2
import androidx.compose.material.BottomNavigation
import androidx.compose.material.BottomNavigationItem
BottomNavigation {
BottomNavigationItem(…)
BottomNavigationItem(…)
BottomNavigationItem(…)
}
M3
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
NavigationBar {
NavigationBarItem(…)
NavigationBarItem(…)
NavigationBarItem(…)
}
Buttons, icon buttons and FABs
Buttons, icon buttons and floating action buttons (FABs) in M3 are different to those in M2. M3 includes all of the M2 button composables:
M2
import androidx.compose.material.Button
import androidx.compose.material.ExtendedFloatingActionButton
import androidx.compose.material.FloatingActionButton
import androidx.compose.material.IconButton
import androidx.compose.material.IconToggleButton
import androidx.compose.material.OutlinedButton
import androidx.compose.material.TextButton
// M2 buttons
Button(…)
OutlinedButton(…)
TextButton(…)
// M2 icon buttons
IconButton(…)
IconToggleButton(…)
// M2 FABs
FloatingActionButton(…)
ExtendedFloatingActionButton(…)
M3
import androidx.compose.material3.Button
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconToggleButton
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.TextButton
// M3 buttons
Button(…)
OutlinedButton(…)
TextButton(…)
// M3 icon buttons
IconButton(…)
IconToggleButton(…)
// M3 FABs
FloatingActionButton(…)
ExtendedFloatingActionButton(…)
M3 also includes new button variations. Check them out on the Compose Material 3 API reference overview.
Switch
Switch in M3 is different to M2. In both M2 and M3, the switch
composable is named Switch
but the import packages differ:
M2
import androidx.compose.material.Switch
Switch(…)
M3
import androidx.compose.material3.Switch
Switch(…)
Surfaces and elevation
The surface and elevation systems in M3 are different to M2. There are two types of elevation in M3:
- Shadow elevation (casts a shadow, same as M2)
- Tonal elevation (overlays a color, new to M3)
In Compose this applies to the M2 Surface
function and the M3
Surface
function:
M2
import androidx.compose.material.Surface
Surface(
elevation = …
) { … }
M3
import androidx.compose.material3.Surface
Surface(
shadowElevation = …,
tonalElevation = …
) { … }
You can use the elevation
Dp
values in M2 for both shadowElevation
and/or tonalElevation
in M3, depending on the UX/UI design preference.
Surface
is the backing composable behind most components, so component
composables might also expose elevation parameters you must migrate in the same
way.
Tonal elevation in M3 replaces the concept of elevation overlays in M2 dark
themes . As a result, ElevationOverlay
and LocalElevationOverlay
don't exist in M3, and LocalAbsoluteElevation
in M2 has changed to
LocalAbsoluteTonalElevation
in M3.
Emphasis and content alpha
Emphasis in M3 is significantly different to M2. In M2, emphasis involved using on colors with certain alpha values to differentiate content like text and icons. In M3, there are now a couple different approaches:
- Using on colors alongside their variant on colors from the expanded M3 color system.
- Using different font weights for text.
As a result, ContentAlpha
and LocalContentAlpha
don't exist in
M3 and need to be replaced.
The following mappings are recommended as a starting point:
M2 | M3 |
---|---|
onSurface with ContentAlpha.high |
onSurface in general, FontWeight.Medium - FontWeight.Black for text |
onSurface with ContentAlpha.medium |
onSurfaceVariant in general, FontWeight.Thin - FontWeight.Normal for text |
onSurface with ContentAlpha.disabled |
onSurface.copy(alpha = 0.38f) |
Here's an example of icon emphasis in M2 vs. M3:
M2
import androidx.compose.material.ContentAlpha
import androidx.compose.material.LocalContentAlpha
// High emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
Icon(…)
}
// Medium emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Icon(…)
}
// Disabled emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
Icon(…)
}
M3
import androidx.compose.material3.LocalContentColor
// High emphasis
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
Icon(…)
}
// Medium emphasis
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurfaceVariant) {
Icon(…)
}
// Disabled emphasis
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)) {
Icon(…)
}
Here are examples of text emphasis in M2 and M3:
M2
import androidx.compose.material.ContentAlpha
import androidx.compose.material.LocalContentAlpha
// High emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
Text(…)
}
// Medium emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.medium) {
Text(…)
}
// Disabled emphasis
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.disabled) {
Text(…)
}
M3
import androidx.compose.material3.LocalContentColor
// High emphasis
Text(
…,
fontWeight = FontWeight.Bold
)
// Medium emphasis
Text(
…,
fontWeight = FontWeight.Normal
)
// Disabled emphasis
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)) {
Text(
…,
fontWeight = FontWeight.Normal
)
}
Backgrounds and containers
Backgrounds in M2 are named containers in M3. In general, you can replace
background*
parameters in M2 with container*
in M3, using the same values.
For example:
M2
Badge(
backgroundColor = MaterialTheme.colors.primary
) { … }
M3
Badge(
containerColor = MaterialTheme.colorScheme.primary
) { … }
Useful links
To learn more about migrating from M2 to M3 in Compose, consult the following additional resources.
Docs
Sample apps
- Reply M3 sample app
- Jetchat sample app M2 to M3 migration
- Jetnews sample app M2 to M3 migration
- Now in Android M3 hero app :core-designsystem module
Videos
API reference and source code
Recommended for you
- Note: link text is displayed when JavaScript is off
- Material Design 2 in Compose
- Material Design 3 in Compose
- Custom design systems in Compose