Migrate from Material 2.5 to Material 3 in Compose for Wear OS

Material 3 Expressive is the next evolution of Material Design. It includes updated theming, components, and personalization features like dynamic color.

This guide focuses on migrating from the Wear Compose Material 2.5 (androidx.wear.compose) Jetpack library to the Wear Compose Material 3 (androidx.wear.compose.material3) Jetpack library for apps.

Approaches

For migrating your app code from M2.5 to M3, follow the same approach described in the Compose Material migration phone guidance, in particular:

Dependencies

M3 has a separate package and version to M2.5:

M2.5

implementation("androidx.wear.compose:compose-material:1.4.0")

M3

implementation("androidx.wear.compose:compose-material3:1.6.0")

See the latest M3 versions on the Wear Compose Material 3 releases page.

Wear Compose Foundation library version 1.6.0 introduced some new components that are designed to work with Material 3 components. Similarly, SwipeDismissableNavHost from Wear Compose Navigation library has an updated animation when running on Wear OS 6 (API level 36) or higher. When updating to Wear Compose Material 3 version, we suggest to also update the Wear Compose Foundation and Navigation libraries:

implementation("androidx.wear.compose:compose-foundation:1.6.0")
implementation("androidx.wear.compose:compose-navigation:1.6.0")

Theme

In both M2.5 and M3, the theme composable is named MaterialTheme, but the import packages and parameters differ. In M3, the Colors parameter has been renamed to ColorScheme and MotionScheme has been introduced for implementing transitions.

M2.5

import androidx.wear.compose.material.MaterialTheme

MaterialTheme(
    colors = AppColors,
    typography = AppTypography,
    shapes = AppShapes,
    content = content
)

M3

import androidx.wear.compose.material3.MaterialTheme
// ...
    MaterialTheme(
        colorScheme = ColorScheme(),
        typography = Typography(),
        shapes = Shapes(),
        motionScheme = MotionScheme.standard(),
        content = { /*content here*/ }
    )

Color

The color system in M3 is significantly different from M2.5. 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.5 Colors class, the M3 ColorScheme class, and related functions:

M2.5

import androidx.wear.compose.material.Colors

val appColorScheme: Colors = Colors(
   // M2.5 Color parameters
)

M3

import androidx.wear.compose.material3.ColorScheme
// ...
    val appColorScheme: ColorScheme = ColorScheme(
        // M3 ColorScheme parameters
    )

The following table describes the key differences between M2.5 and M3:

M2.5 M3
Color Has been renamed to ColorScheme
13 colors 28 colors
N/A New dynamic color theming
N/A New tertiary colors for more expression

Dynamic color theming

A new feature in M3 is dynamic color theming. If users change the watch face colors, the colors in the UI change to match.

Use the dynamicColorScheme function to implement dynamic color scheme and provide a defaultColorScheme as a fallback in case dynamic color scheme is not available.

@Composable
fun myApp() {
    val dynamicColorScheme = dynamicColorScheme(LocalContext.current)
    MaterialTheme(colorScheme = dynamicColorScheme ?: myBrandColors) {}
}

internal val myBrandColors: ColorScheme = ColorScheme( /* Specify colors here */)

Typography

The typography system in M3 is different from M2.5 and it includes the following features:

  • Nine new text styles
  • Flex fonts, which allow for customization of the type scales for different weights, widths, and roundness
  • AnimatedText, which uses flex fonts

M2.5

import androidx.wear.compose.material.Typography

val Typography = Typography(
   // M2.5 TextStyle parameters
)

M3

import androidx.wear.compose.material3.Typography

val Typography = Typography(
    // M3 TextStyle parameters
)

Flex fonts

Flex Fonts allow designers to specify the type width and weight for specific sizes.

Text styles

The following TextStyles are available in M3. These are employed by default by various M3 components.

Typography TextStyle
Display displayLarge, displayMedium, displaySmall
Title titleLarge, titleMedium, titleSmall
Label labelLarge, labelMedium, labelSmall
Body bodyLarge, bodyMedium, bodySmall, bodyExtraSmall
Numeral numeralExtraLarge, numeralLarge, numeralMedium, numeralSmall, numeralExtraSmall
Arc arcLarge, arcMedium, arcSmall

Shape

The shape system in M3 is different from M2.5. The number of shape parameters has increased, they're named differently, and they map differently to M3 components. The following shape sizes are available:

  • Extra-small
  • Small
  • Medium
  • Large
  • Extra-large

In Compose, this applies to the M2 Shapes class and the M3 Shapes class:

M2.5

import androidx.wear.compose.material.Shapes

val Shapes = Shapes(
   // M2.5 Shapes parameters
)

M3

import androidx.wear.compose.material3.Shapes

val Shapes = Shapes(
    // M3 Shapes parameters
)

Use the Shapes parameter mapping from Migrate from Material 2 to Material 3 in Compose as a starting point.

Shape morphing

M3 introduces Shape Morphing: shapes now morph in response to interactions.

Shape Morphing behavior is available as a variation on a number of round buttons, see the following list of buttons that support Shape Morphing:

Buttons Shape morphing function
IconButton IconButtonDefaults.animatedShape animates the icon button on press
IconToggleButton IconToggleButtonDefaults.animatedShape animates the icon toggle button on press and
IconToggleButtonDefaults.variantAnimatedShapes animates the icon toggle button on press and check/uncheck
TextButton TextButtonDefaults.animatedShape animates the text button on press
TextToggleButton TextToggleButtonDefaults.animatedShapes animates the text toggle on press and TextToggleButtonDefaults.variantAnimatedShapes animates the text toggle on press and check/uncheck

Components and Layout

Most components and layouts from M2.5 are available in M3. However, some M3 components and layouts didn't exist in M2.5. Furthermore, some M3 components have more variations than their equivalents in M2.5.

While some components require special considerations, the following function mappings are recommended as a starting point:

Material 2.5 Material 3
androidx.wear.compose.material.dialog.Alert androidx.wear.compose.material3.AlertDialog
androidx.wear.compose.material.Button androidx.wear.compose.material3.IconButton or androidx.wear.compose.material3.TextButton
androidx.wear.compose.material.Card androidx.wear.compose.material3.Card
androidx.wear.compose.material.TitleCard androidx.wear.compose.material3.TitleCard
androidx.wear.compose.material.AppCard androidx.wear.compose.material3.AppCard
androidx.wear.compose.material.Checkbox No M3 equivalent, migrate to androidx.wear.compose.material3.CheckboxButton or androidx.wear.compose.material3.SplitCheckboxButton
androidx.wear.compose.material.Chip androidx.wear.compose.material3.Button or
androidx.wear.compose.material3.OutlinedButton or
androidx.wear.compose.material3.FilledTonalButton or
androidx.wear.compose.material3.ChildButton
androidx.wear.compose.material.CompactChip androidx.wear.compose.material3.CompactButton
androidx.wear.compose.material.InlineSlider androidx.wear.compose.material3.Slider
androidx.wear.compose.material.LocalContentAlpha() Has been removed as not used by Text or Icon in Material 3
androidx.wear.compose.material.PositionIndicator androidx.wear.compose.material3.ScrollIndicator
androidx.wear.compose.material.RadioButton No M3 equivalent, migrate to androidx.wear.compose.material3.RadioButton or androidx.wear.compose.material3.SplitRadioButton
androidx.wear.compose.material.SwipeToRevealCard androidx.wear.compose.material3.SwipeToReveal
androidx.wear.compose.material.SwipeToRevealChip androidx.wear.compose.material3.SwipeToReveal
android.wear.compose.material.Scaffold androidx.wear.compose.material3.AppScaffold and androidx.wear.compose.material3.ScreenScaffold
androidx.wear.compose.material.SplitToggleChip No M3 equivalent, migrate to androidx.wear.compose.material3.SplitCheckboxButton, androidx.wear.compose.material3.SplitSwitchButton, or androidx.wear.compose.material3.SplitRadioButton
androidx.wear.compose.material.Switch No M3 equivalent, migrate to androidx.wear.compose.material3.SwitchButton or androidx.wear.compose.material3.SplitSwitchButton
androidx.wear.compose.material.ToggleButton androidx.wear.compose.material3.IconToggleButton or androidx.wear.compose.material3.TextToggleButton
androidx.wear.compose.material.ToggleChip androidx.wear.compose.material3.CheckboxButton or
androidx.wear.compose.material3.RadioButton or
androidx.wear.compose.material3.SwitchButton
androidx.wear.compose.material.Vignette Removed as not included in Material 3 Expressive design for Wear OS

Here is a full list of all the Material 3 components:

Material 3 Material 2.5 equivalent component (if not new in M3)
androidx.wear.compose.material3.AlertDialog androidx.wear.compose.material.dialog.Alert
androidx.wear.compose.material3.AnimatedPage New
androidx.wear.compose.material3.AnimatedText New
androidx.wear.compose.material3.AppScaffold android.wear.compose.material.Scaffold (with androidx.wear.compose.material3.ScreenScaffold )
androidx.wear.compose.material3.Button androidx.wear.compose.material.Chip
androidx.wear.compose.material3.ButtonGroup New
androidx.wear.compose.material3.Card androidx.wear.compose.material.Card
androidx.wear.compose.material3.CheckboxButton androidx.wear.compose.material.ToggleChip with a checkbox toggle control
androidx.wear.compose.material3.ChildButton androidx.wear.compose.material.Chip (only when no background is required)
androidx.wear.compose.material3.CircularProgressIndicator androidx.wear.compose.material.CircularProgressIndicator
androidx.wear.compose.material3.CompactButton androidx.wear.compose.material.CompactChip
androidx.wear.compose.material3.ConfirmationDialog androidx.wear.compose.material.dialog.Confirmation
androidx.wear.compose.material3.curvedText androidx.wear.compose.material.curvedText
androidx.wear.compose.material3.DatePicker New
androidx.wear.compose.material3.Dialog androidx.wear.compose.material.dialog.Dialog
androidx.wear.compose.material3.EdgeButton New
androidx.wear.compose.material3.FadingExpandingLabel New
androidx.wear.compose.material3.FilledTonalButton androidx.wear.compose.material.Chip when a tonal button background is required
androidx.wear.compose.material3.HorizontalPageIndicator androidx.wear.compose.material.HorizontalPageIndicator
androidx.wear.compose.material3.HorizontalPagerScaffold New
androidx.wear.compose.material3.Icon androidx.wear.compose.material.Icon
androidx.wear.compose.material3.IconButton androidx.wear.compose.material.Button
androidx.wear.compose.material3.IconToggleButton androidx.wear.compose.material.ToggleButton
androidx.wear.compose.material3.LevelIndicator New
androidx.wear.compose.material3.LinearProgressIndicator New
androidx.wear.compose.material3.ListHeader androidx.wear.compose.material.ListHeader
androidx.wear.compose.material3.ListSubHeader New
androidx.wear.compose.material3.MaterialTheme androidx.wear.compose.material.MaterialTheme
androidx.wear.compose.material3.OpenOnPhoneDialog New
androidx.wear.compose.material3.Picker androidx.wear.compose.material.Picker
androidx.wear.compose.material3.PickerGroup androidx.wear.compose.material.PickerGroup
androidx.wear.compose.material3.RadioButton androidx.wear.compose.material.ToggleChip with a radio button toggle control
androidx.wear.compose.material3.ScreenScaffold android.wear.compose.material.Scaffold (with androidx.wear.compose.material3.AppScaffold)
androidx.wear.compose.material3.ScrollIndicator androidx.wear.compose.material.PositionIndicator
androidx.wear.compose.material3.scrollAway androidx.wear.compose.material.scrollAway
androidx.wear.compose.material3.SegmentedCircularProgressIndicator New
androidx.wear.compose.material3.Slider androidx.wear.compose.material.InlineSlider
androidx.wear.compose.material3.SplitRadioButton androidx.wear.compose.material.SplitToggleChip
androidx.wear.compose.material3.SplitCheckboxButton androidx.wear.compose.material.SplitToggleChip
androidx.wear.compose.material3.SplitSwitchButton androidx.wear.compose.material.SplitToggleChip
androidx.wear.compose.material3.Stepper androidx.wear.compose.material.Stepper
androidx.wear.compose.material3.SwipeToDismissBox androidx.wear.compose.material.SwipeToDismissBox
androidx.wear.compose.material3.SwipeToReveal androidx.wear.compose.material.SwipeToRevealCard and androidx.wear.compose.material.SwipeToRevealChip
androidx.wear.compose.material3.SwitchButton androidx.wear.compose.material.ToggleChip with a switch toggle control
androidx.wear.compose.material3.Text androidx.wear.compose.material.Text
androidx.wear.compose.material3.TextButton androidx.wear.compose.material.Button
androidx.wear.compose.material3.TextToggleButton androidx.wear.compose.material.ToggleButton
androidx.wear.compose.material3.TimeText androidx.wear.compose.material.TimeText
androidx.wear.compose.material3.VerticalPagerScaffold New

And finally a list of some relevant components from Wear Compose Foundation library:

Wear Compose Foundation 1.6.0
androidx.wear.compose.foundation.hierarchicalFocusGroup Used to annotate composables in an application, to keep track of the active part of the composition and coordinate focus.
androidx.wear.compose.foundation.pager.HorizontalPager A horizontally scrolling pager, built on the Compose Foundation components with Wear-specific enhancements to improve performance and adherence to Wear OS guidelines.
androidx.wear.compose.foundation.pager.VerticalPager A vertically scrolling pager, built on the Compose Foundation components with Wear-specific enhancements to improve performance and adherence to Wear OS guidelines.
androidx.wear.compose.foundation.lazy.TransformingLazyColumn Can be used instead of ScalingLazyColumn to add scroll transform effects to each item.

Buttons

Buttons in M3 are different from M2.5. The M2.5 Chip has been replaced by Button. Button implementation provides default values for Text maxLines and textAlign. Those default values can be overridden in the Text element.

M2.5

import androidx.wear.compose.material.Chip

//M2.5 Buttons
Chip(...)
CompactChip(...)
Button(...)

M3

//M3 Buttons
Button(onClick = { }){}
CompactButton(onClick = { }){}
IconButton(onClick = { }){}
TextButton(onClick = { }){}

M3 also includes new button variations. Check them out on the Compose Material 3 API reference overview.

M3 introduces a new button: EdgeButton. EdgeButton is available in 4 different sizes: extra small, small, medium, and large. EdgeButton implementation provide a default value for maxLines depending on the size which can be customized.

If you are using TransformingLazyColumn or ScalingLazyColumn, pass the EdgeButton into the ScreenScaffold so that it morphs, changing its shape with scrolling, instead of adding an EdgeButton as the final list item. See the following code to check how to use EdgeButton with ScreenScaffold and TransformingLazyColumn.

val state = rememberTransformingLazyColumnState()
ScreenScaffold(
    scrollState = state,
    contentPadding =
        rememberResponsiveColumnPadding(
            first = ColumnItemType.ListHeader
        ),
    edgeButton = {
        EdgeButton(
            onClick = { }
        ) {
            Text(stringResource(R.string.show))
        }
    }
){ contentPadding ->
    TransformingLazyColumn(state = state, contentPadding = contentPadding,){
        // additional code here
    }
}

Scaffold

Scaffold in M3 is different from M2.5. In M3, AppScaffold and the new ScreenScaffold composable have replaced Scaffold. AppScaffold and ScreenScaffold lay out the structure of a screen and coordinate transitions of the ScrollIndicator and TimeText components.

AppScaffold allows static screen elements such as TimeText to remain visible during in-app transitions such as swipe-to-dismiss. ​​It provides a slot for the main application content, which will usually be supplied by a navigation component such as SwipeDismissableNavHost

You declare one AppScaffold for Activity and use a ScreenScaffold for each Screen. AppScaffold adds a default TimeTextcomponent to the screens. You can override it if you want to customize it by using the timeText parameter.

M2.5

import androidx.wear.compose.material.Scaffold

Scaffold {...}

M3

    AppScaffold {
        val navController = rememberSwipeDismissableNavController()
        SwipeDismissableNavHost(
            navController = navController,
            startDestination = "message_list"
        ) {
            composable("message_list") {
                MessageList(onMessageClick = { id ->
                    navController.navigate("message_detail/$id")
                })
            }
            composable("message_detail/{id}") {
                MessageDetail(id = it.arguments?.getString("id")!!)
            }
        }
    }
}

// Implementation of one of the screens in the navigation
@Composable
fun MessageDetail(id: String) {
    // .. Screen level content goes here
    val scrollState = rememberTransformingLazyColumnState()

    val padding = rememberResponsiveColumnPadding(
        first = ColumnItemType.BodyText
    )

    ScreenScaffold(
        scrollState = scrollState,
        contentPadding = padding
    ) { scaffoldPaddingValues ->
        // Screen content goes here
        // ...

If you are using a HorizontalPager with HorizontalPagerIndicator, you can migrate to HorizontalPagerScaffold. HorizontalPagerScaffold is placed within an AppScaffold. AppScaffold and HorizontalPagerScaffold lay out the structure of a Pager and coordinate transitions of the HorizontalPageIndicator and TimeText components.

HorizontalPagerScaffold displays the HorizontalPageIndicator at the center-end of the screen by default and coordinates showing and hiding TimeText and HorizontalPageIndicator according to whether the Pager is being paged, this is determined by the PagerState.

There's also a new AnimatedPage component, which animates a page within a Pager with a scaling and scrim effect based on its position.

AppScaffold {
    val pagerState = rememberPagerState(pageCount = { 10 })
    val columnState = rememberTransformingLazyColumnState()
    val contentPadding = rememberResponsiveColumnPadding(
        first = ColumnItemType.ListHeader,
        last = ColumnItemType.BodyText,
    )
    HorizontalPagerScaffold(pagerState = pagerState) {
        HorizontalPager(
            state = pagerState,
        ) { page ->
            AnimatedPage(pageIndex = page, pagerState = pagerState) {
                ScreenScaffold(
                    scrollState = columnState,
                    contentPadding = contentPadding
                ) { contentPadding ->
                    TransformingLazyColumn(
                        state = columnState,
                        contentPadding = contentPadding
                    ) {
                        item {
                            ListHeader(
                                modifier = Modifier.fillMaxWidth()
                            ) {
                                Text(text = "Pager sample")
                            }
                        }
                        item {
                            if (page == 0) {
                                Text(text = "Page #$page. Swipe right")
                            }
                            else{
                                Text(text = "Page #$page. Swipe left and right")
                            }
                        }
                    }
                }

            }
        }
    }
}

Finally, M3 introduces a VerticalPagerScaffold which follows the same pattern as the HorizontalPagerScaffold:

AppScaffold {
    val pagerState = rememberPagerState(pageCount = { 10 })

    VerticalPagerScaffold(pagerState = pagerState) {
        VerticalPager(
            state = pagerState
        ) { page ->
            AnimatedPage(pageIndex = page, pagerState = pagerState) {
                ScreenScaffold {
                    ///…
                }
            }
        }
    }
}

Placeholder

There are some API changes between M2.5 and M3. Placeholder.PlaceholderDefaults now provides two modifiers:

  • Modifier.placeholder, which is drawn instead of content that is not yet loaded
  • A placeholder shimmer effect Modifier.placeholderShimmer which provides a placeholder shimmer effect which runs in an animation loop while waiting for the data to load.

See the following table for additional changes to the Placeholder component.

M2.5 M3
PlaceholderState.startPlaceholderAnimation Has been removed
PlaceholderState.placeholderProgression Has been removed
PlaceholderState.isShowContent Has been renamed to !PlaceholderState.isVisible
PlaceholderState.isWipeOff Has been removed
PlaceholderDefaults.painterWithPlaceholderOverlayBackgroundBrush Has been removed
PlaceholderDefaults.placeholderBackgroundBrush Has been removed
PlaceholderDefaults.placeholderChipColors Has been removed

SwipeDismissableNavHost

SwipeDismissableNavHost is part of wear.compose.navigation. When this component is used with M3, the M3 MaterialTheme updates the LocalSwipeToDismissBackgroundScrimColor and LocalSwipeToDismissContentScrimColor.

TransformingLazyColumn

TransformingLazyColumn is part of wear.compose.lazy.foundation and adds support for scaling and morphing animations on list items during scrolling , enhancing the user experience. It is strongly recommended that apps migrate from ScalingLazyColumn to TransformingLazyColumn

Similarly to ScalingLazyColumn, it provides rememberTransformingLazyColumnState() to create a TransformingLazyColumnState that is remembered across compositions.

For adding scaling and morphing animations, add the following to each list item:

  • Modifier.transformedHeight, which lets you calculate transformed height of the items using a TransformationSpec, you can use rememberTransformationSpec() unless you need further customization.
  • A SurfaceTransformation

To verify that the padding is correct at the top and bottom of the list, use the minimumVerticalContentPadding modifier.

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(
    scrollState = columnState
) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding
    ) {
        item {
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text(text = "Header")
            }
        }
        // ... other items
        item {
            Button(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec),
                onClick = { /* ... */ },
                icon = {
                    Icon(
                        imageVector = Icons.Default.Build,
                        contentDescription = "build",
                    )
                },
            ) {
                Text(
                    text = "Build",
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                )
            }
        }
    }
}

To learn more about migrating from M2.5 to M3 in Compose, consult the following additional resources.

Samples

API reference and source code

Design