Adds ripple API to wear material and material3 libraries

The new ripple API replaces the deprecated rememberRipple API - it creates a ripple instance that will use the default theme values. ripple
does not need to be remembered, similar to modifiers - if the parameters compare equally, then the same node can be reused internally.

This CL also migrates all wear:compose-material / wear:compose-material3 components to use the new ripple. There is a temporary
CompositionLocal, LocalUseFallbackRippleImplementation, which can be set to true to fallback to using the old RippleTheme / rememberRipple
implementation, but this is strongly discouraged as it is much less performant than the new implementation. This fallback will be removed in
the next stable release, and exists here to aid migration.

Bug: b/298048146
Bug: b/304985887
Test: RippleTest
Relnote: "Adds new ripple API in wear:compose-material and wear:compose-material3 libraries which replaces the deprecated rememberRipple. Also
adds a temporary CompositionLocal, LocalUseFallbackRippleImplementation, to revert Material components to using the deprecated rememberRipple
/ RippleTheme APIs. This will be removed in the next stable release, and is only intended to be a temporary migration aid for cases where you
are providing a custom RippleTheme. See developer.android.com for migration information and more background information behind this change."

Change-Id: I87fd7d3968ec14c3e684039298b31a1fc620b47c
diff --git a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ButtonTest.kt b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ButtonTest.kt
index 167a921..d16609a 100644
--- a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ButtonTest.kt
+++ b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ButtonTest.kt
@@ -18,7 +18,9 @@
 
 import android.os.Build
 import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.IndicationNodeFactory
 import androidx.compose.foundation.background
+import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
@@ -36,6 +38,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.compositeOver
+import androidx.compose.ui.node.DelegatableNode
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.SemanticsProperties
@@ -415,6 +418,16 @@
         shape = shape,
         border = border,
         buttonSize = 52.dp,
+        ripple = EmptyIndication,
         content = content
     )
 }
+
+internal object EmptyIndication : IndicationNodeFactory {
+    override fun create(interactionSource: InteractionSource): DelegatableNode =
+        object : Modifier.Node() {}
+
+    override fun equals(other: Any?): Boolean = other === this
+
+    override fun hashCode(): Int = -1
+}
diff --git a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ChipTest.kt b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ChipTest.kt
index 756d558..56436a3 100644
--- a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ChipTest.kt
+++ b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ChipTest.kt
@@ -736,6 +736,7 @@
         shape = shape,
         interactionSource = interactionSource,
         role = role,
+        ripple = EmptyIndication,
         content = content
     )
 
@@ -771,6 +772,7 @@
         border = border,
         defaultIconSpacing = defaultIconSpacing,
         role = role,
+        ripple = EmptyIndication
     )
 
     @Composable
@@ -809,6 +811,7 @@
         defaultCompactChipTapTargetPadding = defaultCompactChipTapTargetPadding,
         defaultIconSpacing = defaultIconSpacing,
         role = role,
+        ripple = EmptyIndication
     )
 
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
diff --git a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/SelectionControlsTest.kt b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/SelectionControlsTest.kt
index 8cb1df6..ece305c 100644
--- a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/SelectionControlsTest.kt
+++ b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/SelectionControlsTest.kt
@@ -867,7 +867,8 @@
         progressAnimationSpec =
         tween(200, 0, CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f)),
         width = width,
-        height = height
+        height = height,
+        ripple = EmptyIndication
     )
 
     @Composable
@@ -928,7 +929,8 @@
         progressAnimationSpec =
         tween(150, 0, CubicBezierEasing(0.0f, 0.0f, 0.2f, 1.0f)),
         width = width,
-        height = height
+        height = height,
+        ripple = EmptyIndication
     )
 
     @Composable
@@ -975,7 +977,8 @@
         dotAlphaProgressDelay = dotAlphaProgressDelay,
         easing = progressAnimationEasing,
         width = width,
-        height = height
+        height = height,
+        ripple = EmptyIndication
     )
 
     private fun setupCheckBoxWithCustomColors(checked: Boolean, enabled: Boolean) {
diff --git a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/StepperTest.kt b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/StepperTest.kt
index 338c9c4..9dbbad5 100644
--- a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/StepperTest.kt
+++ b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/StepperTest.kt
@@ -392,6 +392,7 @@
         backgroundColor = backgroundColor,
         enabledButtonProviderValues = enabledButtonProviderValues,
         disabledButtonProviderValues = disabledButtonProviderValues,
+        buttonRipple = EmptyIndication,
         content = content,
     )
 
diff --git a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ToggleButtonTest.kt b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ToggleButtonTest.kt
index 5d092ae..512d711 100644
--- a/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ToggleButtonTest.kt
+++ b/wear/compose/compose-material-core/src/androidTest/kotlin/androidx/wear/compose/materialcore/ToggleButtonTest.kt
@@ -1022,6 +1022,7 @@
         toggleButtonSize = toggleButtonSize,
         interactionSource = interactionSource,
         shape = shape,
+        ripple = EmptyIndication,
         content = content
     )
 }
@@ -1067,7 +1068,8 @@
     contentPadding = contentPadding,
     shape = shape,
     selectionControlWidth = selectionControlWidth,
-    selectionControlHeight = selectionControlHeight
+    selectionControlHeight = selectionControlHeight,
+    ripple = EmptyIndication,
 )
 
 @Composable
@@ -1114,7 +1116,8 @@
     checkedInteractionSource = checkedInteractionSource,
     clickInteractionSource = clickInteractionSource,
     contentPadding = contentPadding,
-    shape = shape
+    shape = shape,
+    ripple = EmptyIndication,
 )
 
 private val CHIP_HORIZONTAL_PADDING = 14.dp
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Button.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Button.kt
index fb8cc57c..2933651 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Button.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Button.kt
@@ -17,6 +17,7 @@
 
 import androidx.annotation.RestrictTo
 import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Indication
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
@@ -25,7 +26,6 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.foundation.layout.size
-import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.ui.Alignment
@@ -59,6 +59,7 @@
  * @param shape Defines the button's shape.
  * @param border Resolves the border for this button in different states.
  * @param buttonSize The default size of the button unless overridden by Modifier.size.
+ * @param ripple Ripple used for this button.
  * @param content The content displayed on the [Button] such as text, icon or image.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -72,6 +73,7 @@
     shape: Shape,
     border: @Composable (enabled: Boolean) -> State<BorderStroke?>?,
     buttonSize: Dp,
+    ripple: Indication,
     content: @Composable BoxScope.() -> Unit,
 ) {
     val borderStroke = border(enabled)?.value
@@ -85,7 +87,7 @@
                 onClick = onClick,
                 enabled = enabled,
                 interactionSource = interactionSource,
-                indication = rememberRipple(),
+                indication = ripple,
             )
             .then(
                 if (borderStroke != null) Modifier.border(border = borderStroke, shape = shape)
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Card.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Card.kt
index ef51f18..c2dc1fb 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Card.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Card.kt
@@ -19,6 +19,7 @@
 import androidx.annotation.RestrictTo
 import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.Image
+import androidx.compose.foundation.Indication
 import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.interaction.Interaction
@@ -34,7 +35,6 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
-import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
@@ -79,6 +79,7 @@
  * appearance / behavior of this card in different [Interaction]s.
  * @param role The type of user interface element. Accessibility services might use this
  * to describe the element or do customizations
+ * @param ripple Ripple used for this card
  * @param content A main slot for a content of this card
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -93,6 +94,7 @@
     shape: Shape,
     interactionSource: MutableInteractionSource,
     role: Role?,
+    ripple: Indication,
     content: @Composable ColumnScope.() -> Unit,
 ) {
     Column(
@@ -108,7 +110,7 @@
                 enabled = enabled,
                 onClick = onClick,
                 role = role,
-                indication = rememberRipple(),
+                indication = ripple,
                 interactionSource = interactionSource,
             )
             .then(
@@ -161,6 +163,7 @@
  * appearance / behavior of this card in different [Interaction]s.
  * @param shape Defines the card's shape. It is strongly recommended to use the default as this
  * shape is a key characteristic of the Wear Material Theme
+ * @param ripple Ripple used for this card
  * @param appImage A slot for a small [Image] associated with the application.
  * @param appName A slot for displaying the application name, expected to be a single line of start
  * aligned text.
@@ -181,6 +184,7 @@
     containerPainter: Painter,
     interactionSource: MutableInteractionSource,
     shape: Shape,
+    ripple: Indication,
     appImage: @Composable (RowScope.() -> Unit)?,
     appName: @Composable RowScope.() -> Unit,
     time: @Composable (RowScope.() -> Unit)?,
@@ -196,7 +200,8 @@
         contentPadding = contentPadding,
         interactionSource = interactionSource,
         role = null,
-        shape = shape
+        shape = shape,
+        ripple = ripple
     ) {
         Column {
             Row(
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Chip.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Chip.kt
index 53c58aed..05d429f 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Chip.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Chip.kt
@@ -18,6 +18,7 @@
 
 import androidx.annotation.RestrictTo
 import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Indication
 import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.interaction.Interaction
@@ -36,7 +37,6 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.ui.Alignment
@@ -77,6 +77,7 @@
  * appearance / behavior of this Chip in different [Interaction]s.
  * @param role The type of user interface element. Accessibility services might use this
  * to describe the element or do customizations
+ * @param ripple Ripple used for this chip
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @Composable
@@ -90,6 +91,7 @@
     shape: Shape,
     interactionSource: MutableInteractionSource,
     role: Role?,
+    ripple: Indication,
     content: @Composable RowScope.() -> Unit,
 ) {
     val borderStroke = border(enabled)?.value
@@ -111,7 +113,7 @@
                 enabled = enabled,
                 onClick = onClick,
                 role = role,
-                indication = rememberRipple(),
+                indication = ripple,
                 interactionSource = interactionSource,
             )
             .padding(contentPadding),
@@ -164,6 +166,7 @@
  * @param defaultIconSpacing Spacing between icon and label, if both are provided.
  * @param role Role semantics that accessibility services can use to provide more
  * context to users.
+ * @param ripple Ripple used for this chip
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @Composable
@@ -181,6 +184,7 @@
     border: @Composable (enabled: Boolean) -> State<BorderStroke?>?,
     defaultIconSpacing: Dp,
     role: Role?,
+    ripple: Indication
 ) {
     Chip(
         modifier = modifier,
@@ -191,7 +195,8 @@
         contentPadding = contentPadding,
         shape = shape,
         interactionSource = interactionSource,
-        role = role
+        role = role,
+        ripple = ripple
     ) {
         Row(
             verticalAlignment = Alignment.CenterVertically,
@@ -271,6 +276,7 @@
  * @param defaultIconSpacing Spacing between icon and label, if both are provided
  * @param role Role semantics that accessibility services can use to provide more
  * context to users.
+ * @param ripple Ripple used for this chip
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @Composable
@@ -289,6 +295,7 @@
     defaultCompactChipTapTargetPadding: PaddingValues,
     defaultIconSpacing: Dp,
     role: Role?,
+    ripple: Indication
 ) {
     if (label != null) {
         Chip(
@@ -305,7 +312,8 @@
             shape = shape,
             border = border,
             defaultIconSpacing = defaultIconSpacing,
-            role = role
+            role = role,
+            ripple = ripple
         )
     } else {
         // Icon only compact chips have their own layout with a specific width and center aligned
@@ -322,6 +330,7 @@
             shape = shape,
             interactionSource = interactionSource,
             role = role,
+            ripple = ripple
         ) {
             // Use a box to fill and center align the icon into the single slot of the Chip
             Box(modifier = Modifier
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt
index bcd8688..82c880b 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/SelectionControls.kt
@@ -33,7 +33,6 @@
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.selection.selectable
 import androidx.compose.foundation.selection.toggleable
-import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.ui.Alignment
@@ -77,6 +76,7 @@
  * @param drawBox Draws the checkbox.
  * @param width Width of the checkbox.
  * @param height Height of the checkbox.
+ * @param ripple Ripple used for the checkbox.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @Composable
@@ -91,7 +91,8 @@
     progressAnimationSpec: TweenSpec<Float>,
     drawBox: FunctionDrawBox,
     width: Dp,
-    height: Dp
+    height: Dp,
+    ripple: Indication
 ) {
     val targetState = if (checked) SelectionStage.Checked else SelectionStage.Unchecked
     val transition = updateTransition(targetState, label = "checkboxTransition")
@@ -118,7 +119,7 @@
                 enabled,
                 checked,
                 interactionSource,
-                rememberRipple(),
+                ripple,
                 width,
                 height
             )
@@ -168,6 +169,7 @@
  * @param progressAnimationSpec Animation spec to animate the progress.
  * @param width Width of the switch.
  * @param height Height of the switch.
+ * @param ripple Ripple used for the switch.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @Composable
@@ -187,6 +189,7 @@
     progressAnimationSpec: TweenSpec<Float>,
     width: Dp,
     height: Dp,
+    ripple: Indication
 ) {
     val targetState = if (checked) SelectionStage.Checked else SelectionStage.Unchecked
     val transition = updateTransition(targetState, label = "switchTransition")
@@ -215,7 +218,7 @@
                 enabled,
                 checked,
                 interactionSource,
-                rememberRipple(),
+                ripple,
                 width,
                 height
             )
@@ -262,6 +265,7 @@
  * @param easing Animation spec to animate the progress.
  * @param width Width of the radio button.
  * @param height Height of the radio button.
+ * @param ripple Ripple used for the radio button.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @Composable
@@ -278,7 +282,8 @@
     dotAlphaProgressDelay: Int,
     easing: CubicBezierEasing,
     width: Dp,
-    height: Dp
+    height: Dp,
+    ripple: Indication
 ) {
     val targetState = if (selected) SelectionStage.Checked else SelectionStage.Unchecked
     val transition = updateTransition(targetState)
@@ -315,7 +320,7 @@
                 this.role = Role.RadioButton
             }
             .maybeSelectable(
-                onClick, enabled, selected, interactionSource, rememberRipple(), width, height
+                onClick, enabled, selected, interactionSource, ripple, width, height
             )
             .drawWithCache
             {
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Stepper.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Stepper.kt
index 70428a4..0e9ca3f 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Stepper.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/Stepper.kt
@@ -17,6 +17,7 @@
 package androidx.wear.compose.materialcore
 
 import androidx.annotation.RestrictTo
+import androidx.compose.foundation.Indication
 import androidx.compose.foundation.background
 import androidx.compose.foundation.indication
 import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -30,7 +31,6 @@
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.wrapContentWidth
-import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.ProvidedValue
@@ -71,6 +71,7 @@
  * @param disabledButtonProviderValues Values of CompositionLocal providers for disabled button such
  * as LocalContentColor, LocalContentAlpha, LocalTextStyle which are dependent on a specific
  * material design version and are not part of this material-agnostic library.
+ * @param buttonRipple Unbounded ripple used for the decrease and increase button
  * @param content Content body for the Stepper.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -86,6 +87,7 @@
     backgroundColor: Color,
     enabledButtonProviderValues: Array<ProvidedValue<*>>,
     disabledButtonProviderValues: Array<ProvidedValue<*>>,
+    buttonRipple: Indication,
     content: @Composable BoxScope.() -> Unit
 ) {
     require(steps >= 0) { "steps should be >= 0" }
@@ -118,6 +120,7 @@
             enabled = increaseButtonEnabled,
             buttonProviderValues = if (increaseButtonEnabled) enabledButtonProviderValues
             else disabledButtonProviderValues,
+            ripple = buttonRipple,
             content = increaseIcon
         )
         Box(
@@ -135,6 +138,7 @@
             enabled = decreaseButtonEnabled,
             buttonProviderValues = if (decreaseButtonEnabled) enabledButtonProviderValues
             else disabledButtonProviderValues,
+            ripple = buttonRipple,
             content = decreaseIcon
         )
     }
@@ -147,6 +151,7 @@
     paddingValues: PaddingValues,
     enabled: Boolean,
     buttonProviderValues: Array<ProvidedValue<*>>,
+    ripple: Indication,
     content: @Composable () -> Unit
 ) {
     val interactionSource = remember { MutableInteractionSource() }
@@ -161,7 +166,7 @@
                 indication = null
             )
             .wrapContentWidth()
-            .indication(interactionSource, rememberRipple(bounded = false))
+            .indication(interactionSource, ripple)
             .padding(paddingValues),
         contentAlignment = contentAlignment,
     ) {
diff --git a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
index ce2be36..85e2513 100644
--- a/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
+++ b/wear/compose/compose-material-core/src/main/java/androidx/wear/compose/materialcore/ToggleButton.kt
@@ -18,6 +18,7 @@
 
 import androidx.annotation.RestrictTo
 import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Indication
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
@@ -41,7 +42,6 @@
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.layout.wrapContentWidth
 import androidx.compose.foundation.selection.toggleable
-import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.ui.Alignment
@@ -83,6 +83,7 @@
  * appearance / behavior of this ToggleButton in different [Interaction]s.
  * @param shape Defines the shape for this toggle button. It is strongly recommended to use the
  * default as this shape is a key characteristic of the Wear Material Theme.
+ * @param ripple Ripple used for this toggle button
  * @param content The icon, image or text to be drawn inside the toggle button.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -97,6 +98,7 @@
     toggleButtonSize: Dp,
     interactionSource: MutableInteractionSource,
     shape: Shape,
+    ripple: Indication,
     content: @Composable BoxScope.() -> Unit,
 ) {
     // Round toggle button
@@ -112,7 +114,7 @@
                 onValueChange = onCheckedChange,
                 enabled = enabled,
                 interactionSource = interactionSource,
-                indication = rememberRipple()
+                indication = ripple
             )
             .then(
                 if (borderStroke != null) Modifier.border(border = borderStroke, shape = shape)
@@ -163,6 +165,7 @@
  * default as this shape is a key characteristic of the Wear Material Theme
  * @param selectionControlWidth Width for the selection control.
  * @param selectionControlHeight Height for the selection control.
+ * @param ripple Ripple used for this toggle button
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @Composable
@@ -180,7 +183,8 @@
     contentPadding: PaddingValues,
     shape: Shape,
     selectionControlWidth: Dp,
-    selectionControlHeight: Dp
+    selectionControlHeight: Dp,
+    ripple: Indication
 ) {
     // Stadium/Chip shaped toggle button
     Row(
@@ -192,7 +196,7 @@
                 enabled = enabled,
                 value = checked,
                 onValueChange = onCheckedChange,
-                indication = rememberRipple(),
+                indication = ripple,
                 interactionSource = interactionSource
             )
             .padding(contentPadding),
@@ -263,6 +267,7 @@
  * content
  * @param shape Defines the SplitToggleButton's shape. It is strongly recommended to use the
  * default as this shape is a key characteristic of the Wear Material Theme
+ * @param ripple Ripple used for this toggle button
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @Composable
@@ -280,7 +285,8 @@
     checkedInteractionSource: MutableInteractionSource,
     clickInteractionSource: MutableInteractionSource,
     contentPadding: PaddingValues,
-    shape: Shape
+    shape: Shape,
+    ripple: Indication
 ) {
     val (startPadding, endPadding) = contentPadding.splitHorizontally()
 
@@ -296,7 +302,7 @@
                 .clickable(
                     enabled = enabled,
                     onClick = onClick,
-                    indication = rememberRipple(),
+                    indication = ripple,
                     interactionSource = clickInteractionSource,
                 )
                 .semantics {
@@ -328,7 +334,7 @@
                     enabled = enabled,
                     value = checked,
                     onValueChange = onCheckedChange,
-                    indication = rememberRipple(),
+                    indication = ripple,
                     interactionSource = checkedInteractionSource
                 )
                 .fillMaxHeight()
diff --git a/wear/compose/compose-material/api/current.txt b/wear/compose/compose-material/api/current.txt
index 03bbbc7..5bb549f 100644
--- a/wear/compose/compose-material/api/current.txt
+++ b/wear/compose/compose-material/api/current.txt
@@ -478,6 +478,13 @@
     property public final float factorAtMin;
   }
 
+  public final class RippleKt {
+    method @SuppressCompatibility @androidx.wear.compose.material.ExperimentalWearMaterialApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalUseFallbackRippleImplementation();
+    method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(androidx.compose.ui.graphics.ColorProducer color, optional boolean bounded, optional float radius);
+    method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(optional boolean bounded, optional float radius, optional long color);
+    property @SuppressCompatibility @androidx.wear.compose.material.ExperimentalWearMaterialApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalUseFallbackRippleImplementation;
+  }
+
   public final class ScaffoldKt {
     method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? vignette, optional kotlin.jvm.functions.Function0<kotlin.Unit>? positionIndicator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? pageIndicator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
diff --git a/wear/compose/compose-material/api/restricted_current.txt b/wear/compose/compose-material/api/restricted_current.txt
index 03bbbc7..5bb549f 100644
--- a/wear/compose/compose-material/api/restricted_current.txt
+++ b/wear/compose/compose-material/api/restricted_current.txt
@@ -478,6 +478,13 @@
     property public final float factorAtMin;
   }
 
+  public final class RippleKt {
+    method @SuppressCompatibility @androidx.wear.compose.material.ExperimentalWearMaterialApi public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalUseFallbackRippleImplementation();
+    method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(androidx.compose.ui.graphics.ColorProducer color, optional boolean bounded, optional float radius);
+    method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(optional boolean bounded, optional float radius, optional long color);
+    property @SuppressCompatibility @androidx.wear.compose.material.ExperimentalWearMaterialApi public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalUseFallbackRippleImplementation;
+  }
+
   public final class ScaffoldKt {
     method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? vignette, optional kotlin.jvm.functions.Function0<kotlin.Unit>? positionIndicator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? pageIndicator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? timeText, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Button.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Button.kt
index b298e04..49355d6 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Button.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Button.kt
@@ -163,6 +163,7 @@
         shape = shape,
         border = { border.borderStroke(enabled = it) },
         buttonSize = ButtonDefaults.DefaultButtonSize,
+        ripple = rippleOrFallbackImplementation(),
         content = provideScopeContent(
             colors.contentColor(enabled = enabled),
             MaterialTheme.typography.button,
@@ -345,6 +346,7 @@
         shape = shape,
         border = { border.borderStroke(it) },
         buttonSize = ButtonDefaults.ExtraSmallButtonSize,
+        ripple = rippleOrFallbackImplementation(),
         content = provideScopeContent(
             colors.contentColor(enabled = enabled),
             MaterialTheme.typography.button,
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt
index 81a53a4..f480fec 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Card.kt
@@ -115,6 +115,7 @@
         shape = shape,
         interactionSource = interactionSource,
         role = role,
+        ripple = rippleOrFallbackImplementation(),
     ) {
         CompositionLocalProvider(
             LocalContentColor provides contentColor,
@@ -202,6 +203,7 @@
         containerPainter = backgroundPainter,
         interactionSource = remember { MutableInteractionSource() },
         shape = MaterialTheme.shapes.large,
+        ripple = rippleOrFallbackImplementation(),
         appImage = appImage?.let { { appImage() } },
         appName = {
             CompositionLocalProvider(
@@ -308,7 +310,8 @@
         contentPadding = CardDefaults.ContentPadding,
         interactionSource = remember { MutableInteractionSource() },
         role = null,
-        shape = MaterialTheme.shapes.large
+        shape = MaterialTheme.shapes.large,
+        ripple = rippleOrFallbackImplementation(),
     ) {
         Column {
             Row(
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt
index e5161f7b..a9bf879 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Chip.kt
@@ -35,7 +35,6 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.Stable
@@ -204,7 +203,7 @@
                 enabled = enabled,
                 onClick = onClick,
                 role = role,
-                indication = rememberRipple(),
+                indication = rippleOrFallbackImplementation(),
                 interactionSource = interactionSource,
             )
             .padding(contentPadding),
@@ -707,6 +706,7 @@
         defaultCompactChipTapTargetPadding = ChipDefaults.CompactChipTapTargetPadding,
         defaultIconSpacing = ChipDefaults.IconSpacing,
         role = Role.Button,
+        ripple = rippleOrFallbackImplementation()
     )
 }
 
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt
index 86d8124..5017f99 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/MaterialTheme.kt
@@ -17,12 +17,8 @@
 
 import androidx.compose.foundation.LocalIndication
 import androidx.compose.foundation.text.selection.LocalTextSelectionColors
-import androidx.compose.material.ripple.LocalRippleTheme
-import androidx.compose.material.ripple.RippleTheme
-import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.runtime.remember
 import androidx.wear.compose.foundation.LocalSwipeToDismissBackgroundScrimColor
@@ -71,15 +67,17 @@
         // provided, and overwrite the values set in it.
         colors.copy()
     }.apply { updateColorsFrom(colors) }
-    val rippleIndication = rememberRipple()
+    val rippleIndication = rippleOrFallbackImplementation()
     val selectionColors = rememberTextSelectionColors(rememberedColors)
+    @Suppress("DEPRECATION_ERROR")
     CompositionLocalProvider(
         LocalColors provides rememberedColors,
         LocalShapes provides shapes,
         LocalTypography provides typography,
         LocalContentAlpha provides ContentAlpha.high,
         LocalIndication provides rippleIndication,
-        LocalRippleTheme provides MaterialRippleTheme,
+        // TODO: b/304985887 - remove after one stable release
+        androidx.compose.material.ripple.LocalRippleTheme provides CompatRippleTheme,
         LocalTextSelectionColors provides selectionColors,
         LocalSwipeToDismissBackgroundScrimColor provides rememberedColors.background,
         LocalSwipeToDismissContentScrimColor provides rememberedColors.background
@@ -104,18 +102,3 @@
         @Composable
         get() = LocalShapes.current
 }
-
-@Immutable
-private object MaterialRippleTheme : RippleTheme {
-    @Composable
-    override fun defaultColor() = RippleTheme.defaultRippleColor(
-        contentColor = LocalContentColor.current,
-        lightTheme = false
-    )
-
-    @Composable
-    override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
-        contentColor = LocalContentColor.current,
-        lightTheme = false
-    )
-}
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Ripple.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Ripple.kt
new file mode 100644
index 0000000..06fd1c6
--- /dev/null
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Ripple.kt
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material
+
+import androidx.compose.foundation.Indication
+import androidx.compose.foundation.IndicationNodeFactory
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.material.ripple.RippleAlpha
+import androidx.compose.material.ripple.createRippleModifierNode
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.ProvidableCompositionLocal
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorProducer
+import androidx.compose.ui.graphics.isSpecified
+import androidx.compose.ui.graphics.luminance
+import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.unit.Dp
+
+/**
+ * Creates a Ripple using the provided values and values inferred from the theme.
+ *
+ * A Ripple is a Material implementation of [Indication] that expresses different [Interaction]s
+ * by drawing ripple animations and state layers.
+ *
+ * A Ripple responds to [PressInteraction.Press] by starting a new animation, and
+ * responds to other [Interaction]s by showing a fixed state layer with varying alpha values
+ * depending on the [Interaction].
+ *
+ * [MaterialTheme] provides Ripples using [androidx.compose.foundation.LocalIndication], so a Ripple
+ * will be used as the default [Indication] inside components such as
+ * [androidx.compose.foundation.clickable] and [androidx.compose.foundation.indication], in
+ * addition to Material provided components that use a Ripple as well.
+ *
+ * You can also explicitly create a Ripple and provide it to custom components in order to change
+ * the parameters from the default, such as to create an unbounded ripple with a fixed size.
+ *
+ * To create a Ripple with a manually defined color that can change over time, see the other
+ * [ripple] overload with a [ColorProducer] parameter. This will avoid unnecessary recompositions
+ * when changing the color, and preserve existing ripple state when the color changes.
+ *
+ * @param bounded If true, ripples are clipped by the bounds of the target layout. Unbounded
+ * ripples always animate from the target layout center, bounded ripples animate from the touch
+ * position.
+ * @param radius the radius for the ripple. If [Dp.Unspecified] is provided then the size will be
+ * calculated based on the target layout size.
+ * @param color the color of the ripple. This color is usually the same color used by the text or
+ * iconography in the component. This color will then have alpha applied to calculate the final
+ * color used to draw the ripple. If [Color.Unspecified] is provided the color used will be
+ * the default ripple color instead.
+ */
+@Stable
+fun ripple(
+    bounded: Boolean = true,
+    radius: Dp = Dp.Unspecified,
+    color: Color = Color.Unspecified
+): IndicationNodeFactory {
+    return if (radius == Dp.Unspecified && color == Color.Unspecified) {
+        if (bounded) return DefaultBoundedRipple else DefaultUnboundedRipple
+    } else {
+        RippleNodeFactory(bounded, radius, color)
+    }
+}
+
+/**
+ * Creates a Ripple using the provided values and values inferred from the theme.
+ *
+ * A Ripple is a Material implementation of [Indication] that expresses different [Interaction]s
+ * by drawing ripple animations and state layers.
+ *
+ * A Ripple responds to [PressInteraction.Press] by starting a new ripple animation, and
+ * responds to other [Interaction]s by showing a fixed state layer with varying alpha values
+ * depending on the [Interaction].
+ *
+ * [MaterialTheme] provides Ripples using [androidx.compose.foundation.LocalIndication], so a Ripple
+ * will be used as the default [Indication] inside components such as
+ * [androidx.compose.foundation.clickable] and [androidx.compose.foundation.indication], in
+ * addition to Material provided components that use a Ripple as well.
+ *
+ * You can also explicitly create a Ripple and provide it to custom components in order to change
+ * the parameters from the default, such as to create an unbounded ripple with a fixed size.
+ *
+ * To create a Ripple with a static color, see the [ripple] overload with a [Color] parameter. This
+ * overload is optimized for Ripples that have dynamic colors that change over time, to reduce
+ * unnecessary recompositions.
+ *
+ * @param color the color of the ripple. This color is usually the same color used by the text or
+ * iconography in the component. This color will then have alpha applied to calculate the final
+ *  * color used to draw the ripple. If you are creating this [ColorProducer] outside of composition
+ *  (where it will be automatically remembered), make sure that its instance is stable
+ *  (such as by remembering the object that holds it), or remember the returned [ripple] object to
+ *  make sure that ripple nodes are not being created each recomposition.
+ * @param bounded If true, ripples are clipped by the bounds of the target layout. Unbounded
+ * ripples always animate from the target layout center, bounded ripples animate from the touch
+ * position.
+ * @param radius the radius for the ripple. If [Dp.Unspecified] is provided then the size will be
+ * calculated based on the target layout size.
+ */
+@Stable
+fun ripple(
+    color: ColorProducer,
+    bounded: Boolean = true,
+    radius: Dp = Dp.Unspecified
+): IndicationNodeFactory {
+    return RippleNodeFactory(bounded, radius, color)
+}
+
+/**
+ * Default values used by [ripple].
+ */
+private object RippleDefaults {
+    /**
+     * Represents the default color that will be used for a ripple if a color has not been
+     * explicitly set on the ripple instance.
+     *
+     * @param contentColor the color of content (text or iconography) in the component that
+     * contains the ripple.
+     */
+    fun rippleColor(
+        contentColor: Color,
+    ): Color {
+        val contentLuminance = contentColor.luminance()
+        // If we are on a colored surface (typically indicated by low luminance content), the
+        // ripple color should be white.
+        return if (contentLuminance < 0.5) {
+            Color.White
+            // Otherwise use contentColor
+        } else {
+            contentColor
+        }
+    }
+}
+
+/**
+ * Temporary CompositionLocal to allow configuring whether the old ripple implementation that uses
+ * the deprecated [androidx.compose.material.ripple.RippleTheme] API should be used in Material
+ * components and LocalIndication, instead of the new [ripple] API. This flag defaults to false,
+ * and will be removed after one stable release: it should only be used to temporarily unblock
+ * upgrading.
+ *
+ * Provide this CompositionLocal before you provide [MaterialTheme] to make sure it is correctly
+ * provided through LocalIndication.
+ */
+// TODO: b/304985887 - remove after one stable release
+@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+@get:ExperimentalWearMaterialApi
+@ExperimentalWearMaterialApi
+val LocalUseFallbackRippleImplementation: ProvidableCompositionLocal<Boolean> =
+    staticCompositionLocalOf { false }
+
+// TODO: b/304985887 - remove after one stable release
+@Suppress("DEPRECATION_ERROR")
+@OptIn(ExperimentalWearMaterialApi::class)
+@Composable
+internal fun rippleOrFallbackImplementation(
+    bounded: Boolean = true,
+    radius: Dp = Dp.Unspecified,
+    color: Color = Color.Unspecified
+): Indication {
+    return if (LocalUseFallbackRippleImplementation.current) {
+        androidx.compose.material.ripple.rememberRipple(bounded, radius, color)
+    } else {
+        ripple(bounded, radius, color)
+    }
+}
+
+// TODO: b/304985887 - remove after one stable release
+@Suppress("DEPRECATION_ERROR")
+@Immutable
+internal object CompatRippleTheme : androidx.compose.material.ripple.RippleTheme {
+
+    @Deprecated("Super method is deprecated")
+    @Composable
+    override fun defaultColor() = RippleDefaults.rippleColor(LocalContentColor.current)
+
+    @Deprecated("Super method is deprecated")
+    @Composable
+    override fun rippleAlpha() = RippleAlpha
+}
+
+@Stable
+private class RippleNodeFactory private constructor(
+    private val bounded: Boolean,
+    private val radius: Dp,
+    private val colorProducer: ColorProducer?,
+    private val color: Color
+) : IndicationNodeFactory {
+    constructor(
+        bounded: Boolean,
+        radius: Dp,
+        colorProducer: ColorProducer
+    ) : this(bounded, radius, colorProducer, Color.Unspecified)
+
+    constructor(
+        bounded: Boolean,
+        radius: Dp,
+        color: Color
+    ) : this(bounded, radius, null, color)
+
+    override fun create(interactionSource: InteractionSource): DelegatableNode {
+        val colorProducer = colorProducer ?: ColorProducer { color }
+        return DelegatingThemeAwareRippleNode(interactionSource, bounded, radius, colorProducer)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is RippleNodeFactory) return false
+
+        if (bounded != other.bounded) return false
+        if (radius != other.radius) return false
+        if (colorProducer != other.colorProducer) return false
+        return color == other.color
+    }
+
+    override fun hashCode(): Int {
+        var result = bounded.hashCode()
+        result = 31 * result + radius.hashCode()
+        result = 31 * result + colorProducer.hashCode()
+        result = 31 * result + color.hashCode()
+        return result
+    }
+}
+
+private class DelegatingThemeAwareRippleNode(
+    private val interactionSource: InteractionSource,
+    private val bounded: Boolean,
+    private val radius: Dp,
+    private val color: ColorProducer,
+) : DelegatingNode(), CompositionLocalConsumerModifierNode {
+    override fun onAttach() {
+        val calculateColor = ColorProducer {
+            val userDefinedColor = color()
+            if (userDefinedColor.isSpecified) {
+                userDefinedColor
+            } else {
+                RippleDefaults.rippleColor(contentColor = currentValueOf(LocalContentColor))
+            }
+        }
+
+        delegate(createRippleModifierNode(
+            interactionSource,
+            bounded,
+            radius,
+            calculateColor,
+            CalculateRippleAlpha
+        ))
+    }
+}
+
+private val DefaultBoundedRipple = RippleNodeFactory(
+    bounded = true,
+    radius = Dp.Unspecified,
+    color = Color.Unspecified
+)
+
+private val DefaultUnboundedRipple = RippleNodeFactory(
+    bounded = false,
+    radius = Dp.Unspecified,
+    color = Color.Unspecified
+)
+
+private val CalculateRippleAlpha = { RippleAlpha }
+
+private val RippleAlpha = RippleAlpha(
+    pressedAlpha = 0.10f,
+    focusedAlpha = 0.12f,
+    draggedAlpha = 0.08f,
+    hoveredAlpha = 0.04f
+)
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Stepper.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Stepper.kt
index 0b28d50..33da0fc 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Stepper.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Stepper.kt
@@ -101,6 +101,7 @@
             LocalContentColor provides iconColor.copy(alpha = ContentAlpha.disabled),
             LocalContentAlpha provides iconColor.copy(alpha = ContentAlpha.disabled).alpha
         ),
+        buttonRipple = rippleOrFallbackImplementation(bounded = false)
     ) {
         CompositionLocalProvider(
             LocalContentColor provides contentColor
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/SwipeToReveal.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/SwipeToReveal.kt
index 5a3c2a3..4c130f0 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/SwipeToReveal.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/SwipeToReveal.kt
@@ -37,7 +37,6 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.Delete
 import androidx.compose.material.icons.outlined.MoreVert
-import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.remember
@@ -277,7 +276,7 @@
         modifier = modifier
             .clickable(
                 interactionSource = interactionSource,
-                indication = rememberRipple(),
+                indication = rippleOrFallbackImplementation(),
                 role = Role.Button,
                 onClick = {
                     revealState.lastActionType = RevealActionType.UndoAction
@@ -546,7 +545,7 @@
             .fillMaxSize()
             .clickable(
                 interactionSource = interactionSource,
-                indication = rememberRipple(),
+                indication = rippleOrFallbackImplementation(),
                 role = Role.Button,
                 onClick = {
                     revealState.lastActionType = actionType
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleButton.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleButton.kt
index 4a208e3..814f9e3 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleButton.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleButton.kt
@@ -225,6 +225,7 @@
         toggleButtonSize = ToggleButtonDefaults.DefaultToggleButtonSize,
         interactionSource = interactionSource,
         shape = shape,
+        ripple = rippleOrFallbackImplementation(),
         content = provideScopeContent(
             colors.contentColor(enabled = enabled, checked = checked),
             MaterialTheme.typography.button,
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleChip.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleChip.kt
index 006e320..8b0775a 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleChip.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleChip.kt
@@ -156,7 +156,8 @@
     contentPadding = contentPadding,
     shape = shape,
     selectionControlHeight = TOGGLE_CONTROL_HEIGHT,
-    selectionControlWidth = TOGGLE_CONTROL_WIDTH
+    selectionControlWidth = TOGGLE_CONTROL_WIDTH,
+    ripple = rippleOrFallbackImplementation()
 )
 
 /**
@@ -275,7 +276,8 @@
     checkedInteractionSource = checkedInteractionSource,
     clickInteractionSource = clickInteractionSource,
     contentPadding = contentPadding,
-    shape = shape
+    shape = shape,
+    ripple = rippleOrFallbackImplementation()
 )
 
 /**
diff --git a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleControl.kt b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleControl.kt
index cabf021..0194f26 100644
--- a/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleControl.kt
+++ b/wear/compose/compose-material/src/main/java/androidx/wear/compose/material/ToggleControl.kt
@@ -87,7 +87,8 @@
     drawBox = { drawScope, color, _, _ -> drawScope.drawBox(color) },
     progressAnimationSpec = PROGRESS_ANIMATION_SPEC,
     width = WIDTH,
-    height = HEIGHT
+    height = HEIGHT,
+    ripple = rippleOrFallbackImplementation()
 )
 
 /**
@@ -155,7 +156,8 @@
     },
     progressAnimationSpec = PROGRESS_ANIMATION_SPEC,
     width = WIDTH,
-    height = HEIGHT
+    height = HEIGHT,
+    ripple = rippleOrFallbackImplementation()
 )
 
 /**
@@ -209,7 +211,8 @@
     dotAlphaProgressDelay = FLASH,
     easing = STANDARD_IN,
     width = WIDTH,
-    height = HEIGHT
+    height = HEIGHT,
+    ripple = rippleOrFallbackImplementation()
 )
 
 /**
diff --git a/wear/compose/compose-material3/api/current.txt b/wear/compose/compose-material3/api/current.txt
index 34ed77d..5bb31ba7 100644
--- a/wear/compose/compose-material3/api/current.txt
+++ b/wear/compose/compose-material3/api/current.txt
@@ -360,6 +360,13 @@
     method public static androidx.compose.ui.Modifier rangeSemantics(androidx.compose.ui.Modifier, float value, boolean enabled, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, int steps);
   }
 
+  public final class RippleKt {
+    method @SuppressCompatibility @androidx.wear.compose.material3.ExperimentalWearMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalUseFallbackRippleImplementation();
+    method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(androidx.compose.ui.graphics.ColorProducer color, optional boolean bounded, optional float radius);
+    method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(optional boolean bounded, optional float radius, optional long color);
+    property @SuppressCompatibility @androidx.wear.compose.material3.ExperimentalWearMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalUseFallbackRippleImplementation;
+  }
+
   public final class SelectionControlsKt {
     method @androidx.compose.runtime.Composable public static void Checkbox(boolean checked, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material3.CheckboxColors colors, optional boolean enabled);
     method @androidx.compose.runtime.Composable public static void RadioButton(boolean selected, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material3.RadioButtonColors colors, optional boolean enabled);
diff --git a/wear/compose/compose-material3/api/restricted_current.txt b/wear/compose/compose-material3/api/restricted_current.txt
index 34ed77d..5bb31ba7 100644
--- a/wear/compose/compose-material3/api/restricted_current.txt
+++ b/wear/compose/compose-material3/api/restricted_current.txt
@@ -360,6 +360,13 @@
     method public static androidx.compose.ui.Modifier rangeSemantics(androidx.compose.ui.Modifier, float value, boolean enabled, kotlin.jvm.functions.Function1<? super java.lang.Float,kotlin.Unit> onValueChange, kotlin.ranges.ClosedFloatingPointRange<java.lang.Float> valueRange, int steps);
   }
 
+  public final class RippleKt {
+    method @SuppressCompatibility @androidx.wear.compose.material3.ExperimentalWearMaterial3Api public static androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> getLocalUseFallbackRippleImplementation();
+    method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(androidx.compose.ui.graphics.ColorProducer color, optional boolean bounded, optional float radius);
+    method @androidx.compose.runtime.Stable public static androidx.compose.foundation.IndicationNodeFactory ripple(optional boolean bounded, optional float radius, optional long color);
+    property @SuppressCompatibility @androidx.wear.compose.material3.ExperimentalWearMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalUseFallbackRippleImplementation;
+  }
+
   public final class SelectionControlsKt {
     method @androidx.compose.runtime.Composable public static void Checkbox(boolean checked, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material3.CheckboxColors colors, optional boolean enabled);
     method @androidx.compose.runtime.Composable public static void RadioButton(boolean selected, optional androidx.compose.ui.Modifier modifier, optional androidx.wear.compose.material3.RadioButtonColors colors, optional boolean enabled);
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
index cf271d5..f9b299c 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Button.kt
@@ -36,7 +36,6 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.wrapContentSize
-import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.State
@@ -1240,7 +1239,7 @@
                 enabled = enabled,
                 onClick = onClick,
                 role = Role.Button,
-                indication = rememberRipple(),
+                indication = rippleOrFallbackImplementation(),
                 interactionSource = interactionSource,
             )
             .padding(contentPadding),
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Card.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Card.kt
index 1c42711..bea5798 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Card.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Card.kt
@@ -198,6 +198,7 @@
         contentPadding = contentPadding,
         appImage = appImage?.let { { appImage() } },
         interactionSource = interactionSource,
+        ripple = rippleOrFallbackImplementation(),
         appName = {
             CompositionLocalProvider(
                 LocalContentColor provides colors.appNameColor,
@@ -330,7 +331,8 @@
         contentPadding = contentPadding,
         interactionSource = interactionSource,
         role = null,
-        shape = shape
+        shape = shape,
+        ripple = rippleOrFallbackImplementation(),
     ) {
         Column {
             if (content == null) {
@@ -649,6 +651,7 @@
         interactionSource = interactionSource,
         role = null,
         shape = shape,
+        ripple = rippleOrFallbackImplementation()
     ) {
         CompositionLocalProvider(
             LocalContentColor provides colors.contentColor,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
index 8755ede..5698de2 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/IconButton.kt
@@ -93,6 +93,7 @@
         shape = shape,
         border = { rememberUpdatedState(border) },
         buttonSize = IconButtonDefaults.DefaultButtonSize,
+        ripple = rippleOrFallbackImplementation(),
         content = provideScopeContent(
             colors.contentColor(enabled = enabled),
             content
@@ -316,6 +317,7 @@
         toggleButtonSize = IconButtonDefaults.DefaultButtonSize,
         interactionSource = interactionSource,
         shape = shape,
+        ripple = rippleOrFallbackImplementation(),
         content = provideScopeContent(
             colors.contentColor(enabled = enabled, checked = checked),
             content
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/MaterialTheme.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/MaterialTheme.kt
index cea4edb..dfa54b1 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/MaterialTheme.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/MaterialTheme.kt
@@ -17,12 +17,8 @@
 
 import androidx.compose.foundation.LocalIndication
 import androidx.compose.foundation.text.selection.LocalTextSelectionColors
-import androidx.compose.material.ripple.LocalRippleTheme
-import androidx.compose.material.ripple.RippleTheme
-import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.runtime.remember
 import androidx.wear.compose.foundation.LocalSwipeToDismissBackgroundScrimColor
@@ -66,15 +62,17 @@
         // provided, and overwrite the values set in it.
         colorScheme.copy()
     }.apply { updateColorSchemeFrom(colorScheme) }
-    val rippleIndication = rememberRipple()
+    val rippleIndication = rippleOrFallbackImplementation()
     val selectionColors = rememberTextSelectionColors(rememberedColors)
+    @Suppress("DEPRECATION_ERROR")
     CompositionLocalProvider(
         LocalColorScheme provides rememberedColors,
         LocalShapes provides shapes,
         LocalTypography provides typography,
         LocalContentAlpha provides ContentAlpha.high,
         LocalIndication provides rippleIndication,
-        LocalRippleTheme provides MaterialRippleTheme,
+        // TODO: b/304985887 - remove after one stable release
+        androidx.compose.material.ripple.LocalRippleTheme provides CompatRippleTheme,
         LocalTextSelectionColors provides selectionColors,
         LocalSwipeToDismissBackgroundScrimColor provides rememberedColors.background,
         LocalSwipeToDismissContentScrimColor provides rememberedColors.background
@@ -99,18 +97,3 @@
         @Composable
         get() = LocalShapes.current
 }
-
-@Immutable
-private object MaterialRippleTheme : RippleTheme {
-    @Composable
-    override fun defaultColor() = RippleTheme.defaultRippleColor(
-        contentColor = LocalContentColor.current,
-        lightTheme = false
-    )
-
-    @Composable
-    override fun rippleAlpha() = RippleTheme.defaultRippleAlpha(
-        contentColor = LocalContentColor.current,
-        lightTheme = false
-    )
-}
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Ripple.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Ripple.kt
new file mode 100644
index 0000000..02209a1
--- /dev/null
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Ripple.kt
@@ -0,0 +1,261 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.material3
+
+import androidx.compose.foundation.Indication
+import androidx.compose.foundation.IndicationNodeFactory
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.InteractionSource
+import androidx.compose.foundation.interaction.PressInteraction
+import androidx.compose.material.ripple.RippleAlpha
+import androidx.compose.material.ripple.createRippleModifierNode
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.ProvidableCompositionLocal
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorProducer
+import androidx.compose.ui.graphics.isSpecified
+import androidx.compose.ui.node.CompositionLocalConsumerModifierNode
+import androidx.compose.ui.node.DelegatableNode
+import androidx.compose.ui.node.DelegatingNode
+import androidx.compose.ui.node.currentValueOf
+import androidx.compose.ui.unit.Dp
+
+/**
+ * Creates a Ripple using the provided values and values inferred from the theme.
+ *
+ * A Ripple is a Material implementation of [Indication] that expresses different [Interaction]s
+ * by drawing ripple animations and state layers.
+ *
+ * A Ripple responds to [PressInteraction.Press] by starting a new ripple animation, and
+ * responds to other [Interaction]s by showing a fixed state layer with varying alpha values
+ * depending on the [Interaction].
+ *
+ * [MaterialTheme] provides Ripples using [androidx.compose.foundation.LocalIndication], so a Ripple
+ * will be used as the default [Indication] inside components such as
+ * [androidx.compose.foundation.clickable] and [androidx.compose.foundation.indication], in
+ * addition to Material provided components that use a Ripple as well.
+ *
+ * You can also explicitly create a Ripple and provide it to custom components in order to change
+ * the parameters from the default, such as to create an unbounded ripple with a fixed size.
+ *
+ * To create a Ripple with a manually defined color that can change over time, see the other
+ * [ripple] overload with a [ColorProducer] parameter. This will avoid unnecessary recompositions
+ * when changing the color, and preserve existing ripple state when the color changes.
+ *
+ * @param bounded If true, ripples are clipped by the bounds of the target layout. Unbounded
+ * ripples always animate from the target layout center, bounded ripples animate from the touch
+ * position.
+ * @param radius the radius for the ripple. If [Dp.Unspecified] is provided then the size will be
+ * calculated based on the target layout size.
+ * @param color the color of the ripple. This color is usually the same color used by the text or
+ * iconography in the component. This color will then have alpha applied to calculate the final
+ * color used to draw the ripple. If [Color.Unspecified] is provided the color used will be
+ * [LocalContentColor] instead.
+ */
+@Stable
+fun ripple(
+    bounded: Boolean = true,
+    radius: Dp = Dp.Unspecified,
+    color: Color = Color.Unspecified
+): IndicationNodeFactory {
+    return if (radius == Dp.Unspecified && color == Color.Unspecified) {
+        if (bounded) return DefaultBoundedRipple else DefaultUnboundedRipple
+    } else {
+        RippleNodeFactory(bounded, radius, color)
+    }
+}
+
+/**
+ * Creates a Ripple using the provided values and values inferred from the theme.
+ *
+ * A Ripple is a Material implementation of [Indication] that expresses different [Interaction]s
+ * by drawing ripple animations and state layers.
+ *
+ * A Ripple responds to [PressInteraction.Press] by starting a new ripple animation, and
+ * responds to other [Interaction]s by showing a fixed state layer with varying alpha values
+ * depending on the [Interaction].
+ *
+ * [MaterialTheme] provides Ripples using [androidx.compose.foundation.LocalIndication], so a Ripple
+ * will be used as the default [Indication] inside components such as
+ * [androidx.compose.foundation.clickable] and [androidx.compose.foundation.indication], in
+ * addition to Material provided components that use a Ripple as well.
+ *
+ * You can also explicitly create a Ripple and provide it to custom components in order to change
+ * the parameters from the default, such as to create an unbounded ripple with a fixed size.
+ *
+ * To create a Ripple with a static color, see the [ripple] overload with a [Color] parameter. This
+ * overload is optimized for Ripples that have dynamic colors that change over time, to reduce
+ * unnecessary recompositions.
+ *
+ * @param color the color of the ripple. This color is usually the same color used by the text or
+ * iconography in the component. This color will then have alpha applied to calculate the final
+ * color used to draw the ripple. If you are creating this [ColorProducer] outside of composition
+ * (where it will be automatically remembered), make sure that its instance is stable
+ * (such as by remembering the object that holds it), or remember the returned [ripple] object to
+ * make sure that ripple nodes are not being created each recomposition.
+ * @param bounded If true, ripples are clipped by the bounds of the target layout. Unbounded
+ * ripples always animate from the target layout center, bounded ripples animate from the touch
+ * position.
+ * @param radius the radius for the ripple. If [Dp.Unspecified] is provided then the size will be
+ * calculated based on the target layout size.
+ */
+@Stable
+fun ripple(
+    color: ColorProducer,
+    bounded: Boolean = true,
+    radius: Dp = Dp.Unspecified
+): IndicationNodeFactory {
+    return RippleNodeFactory(bounded, radius, color)
+}
+
+/**
+ * Temporary CompositionLocal to allow configuring whether the old ripple implementation that uses
+ * the deprecated [androidx.compose.material.ripple.RippleTheme] API should be used in Material
+ * components and LocalIndication, instead of the new [ripple] API. This flag defaults to false,
+ * and will be removed after one stable release: it should only be used to temporarily unblock
+ * upgrading.
+ *
+ * Provide this CompositionLocal before you provide [MaterialTheme] to make sure it is correctly
+ * provided through LocalIndication.
+ */
+// TODO: b/304985887 - remove after one stable release
+@Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
+@get:ExperimentalWearMaterial3Api
+@ExperimentalWearMaterial3Api
+val LocalUseFallbackRippleImplementation: ProvidableCompositionLocal<Boolean> =
+    staticCompositionLocalOf { false }
+
+// TODO: b/304985887 - remove after one stable release
+@Suppress("DEPRECATION_ERROR")
+@OptIn(ExperimentalWearMaterial3Api::class)
+@Composable
+internal fun rippleOrFallbackImplementation(
+    bounded: Boolean = true,
+    radius: Dp = Dp.Unspecified,
+    color: Color = Color.Unspecified
+): Indication {
+    return if (LocalUseFallbackRippleImplementation.current) {
+        androidx.compose.material.ripple.rememberRipple(bounded, radius, color)
+    } else {
+        ripple(bounded, radius, color)
+    }
+}
+
+// TODO: b/304985887 - remove after one stable release
+@Suppress("DEPRECATION_ERROR")
+@Immutable
+internal object CompatRippleTheme : androidx.compose.material.ripple.RippleTheme {
+    @Deprecated("Super method is deprecated")
+    @Composable
+    override fun defaultColor() = LocalContentColor.current
+
+    @Deprecated("Super method is deprecated")
+    @Composable
+    override fun rippleAlpha() = RippleAlpha
+}
+
+@Stable
+private class RippleNodeFactory private constructor(
+    private val bounded: Boolean,
+    private val radius: Dp,
+    private val colorProducer: ColorProducer?,
+    private val color: Color
+) : IndicationNodeFactory {
+    constructor(
+        bounded: Boolean,
+        radius: Dp,
+        colorProducer: ColorProducer
+    ) : this(bounded, radius, colorProducer, Color.Unspecified)
+
+    constructor(
+        bounded: Boolean,
+        radius: Dp,
+        color: Color
+    ) : this(bounded, radius, null, color)
+
+    override fun create(interactionSource: InteractionSource): DelegatableNode {
+        val colorProducer = colorProducer ?: ColorProducer { color }
+        return DelegatingThemeAwareRippleNode(interactionSource, bounded, radius, colorProducer)
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is RippleNodeFactory) return false
+
+        if (bounded != other.bounded) return false
+        if (radius != other.radius) return false
+        if (colorProducer != other.colorProducer) return false
+        return color == other.color
+    }
+
+    override fun hashCode(): Int {
+        var result = bounded.hashCode()
+        result = 31 * result + radius.hashCode()
+        result = 31 * result + colorProducer.hashCode()
+        result = 31 * result + color.hashCode()
+        return result
+    }
+}
+
+private class DelegatingThemeAwareRippleNode(
+    private val interactionSource: InteractionSource,
+    private val bounded: Boolean,
+    private val radius: Dp,
+    private val color: ColorProducer,
+) : DelegatingNode(), CompositionLocalConsumerModifierNode {
+    override fun onAttach() {
+        val calculateColor = ColorProducer {
+            val userDefinedColor = color()
+            if (userDefinedColor.isSpecified) {
+                userDefinedColor
+            } else {
+                currentValueOf(LocalContentColor)
+            }
+        }
+
+        delegate(createRippleModifierNode(
+            interactionSource,
+            bounded,
+            radius,
+            calculateColor,
+            CalculateRippleAlpha
+        ))
+    }
+}
+
+private val RippleAlpha: RippleAlpha = RippleAlpha(
+    pressedAlpha = 0.12f,
+    focusedAlpha = 0.12f,
+    draggedAlpha = 0.16f,
+    hoveredAlpha = 0.08f
+)
+
+private val CalculateRippleAlpha = { RippleAlpha }
+
+private val DefaultBoundedRipple = RippleNodeFactory(
+    bounded = true,
+    radius = Dp.Unspecified,
+    color = Color.Unspecified
+)
+private val DefaultUnboundedRipple = RippleNodeFactory(
+    bounded = false,
+    radius = Dp.Unspecified,
+    color = Color.Unspecified
+)
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SelectionControls.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SelectionControls.kt
index 22c2fa9..fde902e 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SelectionControls.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/SelectionControls.kt
@@ -85,7 +85,8 @@
     },
     progressAnimationSpec = PROGRESS_ANIMATION_SPEC,
     width = WIDTH,
-    height = HEIGHT
+    height = HEIGHT,
+    ripple = rippleOrFallbackImplementation()
 )
 
 /**
@@ -150,7 +151,8 @@
     },
     progressAnimationSpec = SWITCH_PROGRESS_ANIMATION_SPEC,
     width = WIDTH,
-    height = HEIGHT
+    height = HEIGHT,
+    ripple = rippleOrFallbackImplementation()
 )
 
 /**
@@ -196,7 +198,8 @@
     dotAlphaProgressDelay = SHORT_2,
     easing = STANDARD_DECELERATE,
     width = WIDTH,
-    height = HEIGHT
+    height = HEIGHT,
+    ripple = rippleOrFallbackImplementation()
 )
 
 /**
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
index 061f918..2041a01 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Slider.kt
@@ -31,7 +31,6 @@
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.ripple.rememberRipple
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Immutable
@@ -136,7 +135,10 @@
         val containerColor = colors.containerColor(enabled)
         val barSeparatorColor = colors.barSeparatorColor(enabled)
         CompositionLocalProvider(
-            LocalIndication provides rememberRipple(bounded = false, radius = this.maxWidth / 2)
+            LocalIndication provides rippleOrFallbackImplementation(
+                bounded = false,
+                radius = this.maxWidth / 2
+            )
         ) {
             Row(
                 verticalAlignment = Alignment.CenterVertically,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Stepper.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Stepper.kt
index 2c71a42..e774080 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Stepper.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/Stepper.kt
@@ -94,7 +94,8 @@
         disabledButtonProviderValues = arrayOf(
             LocalContentColor provides iconColor.copy(alpha = ContentAlpha.disabled),
             LocalContentAlpha provides iconColor.copy(alpha = ContentAlpha.disabled).alpha
-        )
+        ),
+        buttonRipple = rippleOrFallbackImplementation(bounded = false)
     ) {
         CompositionLocalProvider(
             LocalContentColor provides contentColor
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt
index 36e7bfe..dc7b922 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/TextButton.kt
@@ -99,6 +99,7 @@
         shape = shape,
         border = { rememberUpdatedState(border) },
         buttonSize = TextButtonDefaults.DefaultButtonSize,
+        ripple = rippleOrFallbackImplementation(),
         content = provideScopeContent(
             colors.contentColor(enabled = enabled),
             TextButtonTokens.ContentFont.value,
@@ -168,6 +169,7 @@
         toggleButtonSize = TextButtonDefaults.DefaultButtonSize,
         interactionSource = interactionSource,
         shape = shape,
+        ripple = rippleOrFallbackImplementation(),
         content = provideScopeContent(
             colors.contentColor(enabled = enabled, checked = checked),
             TextToggleButtonTokens.ContentFont.value,
diff --git a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleButton.kt b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleButton.kt
index afd00b2..f9abbdb 100644
--- a/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleButton.kt
+++ b/wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/ToggleButton.kt
@@ -138,7 +138,8 @@
         contentPadding = contentPadding,
         shape = shape,
         selectionControlWidth = SELECTION_CONTROL_WIDTH,
-        selectionControlHeight = SELECTION_CONTROL_HEIGHT
+        selectionControlHeight = SELECTION_CONTROL_HEIGHT,
+        ripple = rippleOrFallbackImplementation()
     )
 
 /**
@@ -254,7 +255,8 @@
     checkedInteractionSource = checkedInteractionSource,
     clickInteractionSource = clickInteractionSource,
     contentPadding = contentPadding,
-    shape = shape
+    shape = shape,
+    ripple = rippleOrFallbackImplementation()
 )
 
 /**