feat: introduce icon-button composables in tv material

Test: added instrumentation and snapshot tests

Relnote: "Introduce IconButton and OutlinedIconButton in Tv Material"

Change-Id: Ib504cefcdd22dc50fd43026efcf976ab8d1d43ad
diff --git a/tv/tv-material/api/public_plus_experimental_current.txt b/tv/tv-material/api/public_plus_experimental_current.txt
index beb7c7f..72025d1 100644
--- a/tv/tv-material/api/public_plus_experimental_current.txt
+++ b/tv/tv-material/api/public_plus_experimental_current.txt
@@ -274,6 +274,32 @@
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.IndicationInstance rememberUpdatedInstance(androidx.compose.foundation.interaction.InteractionSource interactionSource);
   }
 
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class IconButtonDefaults {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ButtonBorder border(optional androidx.tv.material3.Border border, optional androidx.tv.material3.Border focusedBorder, optional androidx.tv.material3.Border pressedBorder, optional androidx.tv.material3.Border disabledBorder, optional androidx.tv.material3.Border focusedDisabledBorder);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ButtonColors colors(optional long containerColor, optional long contentColor, optional long focusedContainerColor, optional long focusedContentColor, optional long pressedContainerColor, optional long pressedContentColor, optional long disabledContainerColor, optional long disabledContentColor);
+    method public float getLargeButtonSize();
+    method public float getLargeIconSize();
+    method public float getMediumButtonSize();
+    method public float getMediumIconSize();
+    method public float getSmallButtonSize();
+    method public float getSmallIconSize();
+    method public androidx.tv.material3.ButtonGlow glow(optional androidx.tv.material3.Glow glow, optional androidx.tv.material3.Glow focusedGlow, optional androidx.tv.material3.Glow pressedGlow);
+    method public androidx.tv.material3.ButtonScale scale(optional @FloatRange(from=0.0) float scale, optional @FloatRange(from=0.0) float focusedScale, optional @FloatRange(from=0.0) float pressedScale, optional @FloatRange(from=0.0) float disabledScale, optional @FloatRange(from=0.0) float focusedDisabledScale);
+    method public androidx.tv.material3.ButtonShape shape(optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.graphics.Shape focusedShape, optional androidx.compose.ui.graphics.Shape pressedShape, optional androidx.compose.ui.graphics.Shape disabledShape, optional androidx.compose.ui.graphics.Shape focusedDisabledShape);
+    property public final float LargeButtonSize;
+    property public final float LargeIconSize;
+    property public final float MediumButtonSize;
+    property public final float MediumIconSize;
+    property public final float SmallButtonSize;
+    property public final float SmallIconSize;
+    field public static final androidx.tv.material3.IconButtonDefaults INSTANCE;
+  }
+
+  public final class IconButtonKt {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void IconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void OutlinedIconButton(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.tv.material3.ButtonScale scale, optional androidx.tv.material3.ButtonGlow glow, optional androidx.tv.material3.ButtonShape shape, optional androidx.tv.material3.ButtonColors colors, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+  }
+
   public final class IconKt {
     method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Icon(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
     method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Icon(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
@@ -342,6 +368,27 @@
     field public static final androidx.tv.material3.OutlinedButtonDefaults INSTANCE;
   }
 
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class OutlinedIconButtonDefaults {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ButtonBorder border(optional androidx.tv.material3.Border border, optional androidx.tv.material3.Border focusedBorder, optional androidx.tv.material3.Border pressedBorder, optional androidx.tv.material3.Border disabledBorder, optional androidx.tv.material3.Border focusedDisabledBorder);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ButtonColors colors(optional long containerColor, optional long contentColor, optional long focusedContainerColor, optional long focusedContentColor, optional long pressedContainerColor, optional long pressedContentColor, optional long disabledContainerColor, optional long disabledContentColor);
+    method public float getLargeButtonSize();
+    method public float getLargeIconSize();
+    method public float getMediumButtonSize();
+    method public float getMediumIconSize();
+    method public float getSmallButtonSize();
+    method public float getSmallIconSize();
+    method public androidx.tv.material3.ButtonGlow glow(optional androidx.tv.material3.Glow glow, optional androidx.tv.material3.Glow focusedGlow, optional androidx.tv.material3.Glow pressedGlow);
+    method public androidx.tv.material3.ButtonScale scale(optional @FloatRange(from=0.0) float scale, optional @FloatRange(from=0.0) float focusedScale, optional @FloatRange(from=0.0) float pressedScale, optional @FloatRange(from=0.0) float disabledScale, optional @FloatRange(from=0.0) float focusedDisabledScale);
+    method public androidx.tv.material3.ButtonShape shape(optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.graphics.Shape focusedShape, optional androidx.compose.ui.graphics.Shape pressedShape, optional androidx.compose.ui.graphics.Shape disabledShape, optional androidx.compose.ui.graphics.Shape focusedDisabledShape);
+    property public final float LargeButtonSize;
+    property public final float LargeIconSize;
+    property public final float MediumButtonSize;
+    property public final float MediumIconSize;
+    property public final float SmallButtonSize;
+    property public final float SmallIconSize;
+    field public static final androidx.tv.material3.OutlinedIconButtonDefaults INSTANCE;
+  }
+
   @androidx.compose.runtime.Stable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ScaleIndication implements androidx.compose.foundation.Indication {
     ctor public ScaleIndication(float scale);
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.IndicationInstance rememberUpdatedInstance(androidx.compose.foundation.interaction.InteractionSource interactionSource);
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/IconButtonScreenshotTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/IconButtonScreenshotTest.kt
new file mode 100644
index 0000000..9e6d21a
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/IconButtonScreenshotTest.kt
@@ -0,0 +1,361 @@
+/*
+ * 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.tv.material3
+
+import android.os.Build
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.sizeIn
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.outlined.FavoriteBorder
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.InputMode
+import androidx.compose.ui.input.InputModeManager
+import androidx.compose.ui.platform.LocalInputModeManager
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.hasClickAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performMouseInput
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@OptIn(ExperimentalTestApi::class, ExperimentalTvMaterial3Api::class)
+class IconButtonScreenshotTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(TV_GOLDEN_MATERIAL3)
+
+    private val wrap = Modifier.wrapContentSize(Alignment.TopStart)
+    private val wrapperTestTag = "iconButtonWrapper"
+
+    @Test
+    fun iconButton_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    IconButton(onClick = { /* doSomething() */ }) {
+                        Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                    }
+                }
+            }
+        }
+        assertAgainstGolden("iconButton_lightTheme")
+    }
+
+    @Test
+    fun iconButton_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    IconButton(onClick = { /* doSomething() */ }) {
+                        Icon(
+                            Icons.Filled.Favorite,
+                            contentDescription = "Localized description"
+                        )
+                    }
+                }
+            }
+        }
+        assertAgainstGolden("iconButton_darkTheme")
+    }
+
+    @Test
+    fun iconButton_lightTheme_disabled() {
+        rule.setContent {
+            LightMaterialTheme {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    IconButton(onClick = { /* doSomething() */ }, enabled = false) {
+                        Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                    }
+                }
+            }
+        }
+        assertAgainstGolden("iconButton_lightTheme_disabled")
+    }
+
+    @Test
+    fun iconButton_darkTheme_disabled() {
+        rule.setContent {
+            DarkMaterialTheme {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    IconButton(onClick = { /* doSomething() */ }, enabled = false) {
+                        Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                    }
+                }
+            }
+        }
+        assertAgainstGolden("iconButton_darkTheme_disabled")
+    }
+
+    @Test
+    fun iconButton_lightTheme_pressed() {
+        rule.setContent {
+            LightMaterialTheme {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    IconButton(onClick = { /* doSomething() */ }) {
+                        Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                    }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNode(hasClickAction())
+            .performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        assertAgainstGolden("iconButton_lightTheme_pressed")
+    }
+
+    @Test
+    fun iconButton_darkTheme_pressed() {
+        rule.setContent {
+            DarkMaterialTheme {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    IconButton(onClick = { /* doSomething() */ }) {
+                        Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                    }
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNode(hasClickAction())
+            .performTouchInput { down(center) }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.waitForIdle() // Wait for measure
+        rule.mainClock.advanceTimeBy(milliseconds = 200)
+
+        assertAgainstGolden("iconButton_darkTheme_pressed")
+    }
+
+    @Test
+    fun iconButton_lightTheme_hovered() {
+        rule.setContent {
+            LightMaterialTheme {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    IconButton(onClick = { /* doSomething() */ }) {
+                        Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                    }
+                }
+            }
+        }
+        rule.onNodeWithTag(wrapperTestTag).performMouseInput {
+            enter(center)
+        }
+
+        assertAgainstGolden("iconButton_lightTheme_hovered")
+    }
+
+    @Test
+    fun iconButton_darkTheme_hovered() {
+        rule.setContent {
+            DarkMaterialTheme {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    IconButton(onClick = { /* doSomething() */ }) {
+                        Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                    }
+                }
+            }
+        }
+        rule.onNodeWithTag(wrapperTestTag).performMouseInput {
+            enter(center)
+        }
+
+        assertAgainstGolden("iconButton_darkTheme_hovered")
+    }
+
+    @Test
+    fun iconButton_lightTheme_focused() {
+        val focusRequester = FocusRequester()
+        var localInputModeManager: InputModeManager? = null
+
+        rule.setContent {
+            LightMaterialTheme {
+                localInputModeManager = LocalInputModeManager.current
+                Box(Modifier.sizeIn(minWidth = 50.dp, minHeight = 50.dp).testTag(wrapperTestTag)) {
+                    IconButton(
+                        onClick = { /* doSomething() */ },
+                        modifier = Modifier
+                            .align(Alignment.Center)
+                            .focusRequester(focusRequester)
+                    ) {
+                        Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            @OptIn(ExperimentalComposeUiApi::class)
+            localInputModeManager!!.requestInputMode(InputMode.Keyboard)
+            focusRequester.requestFocus()
+        }
+
+        assertAgainstGolden("iconButton_lightTheme_focused")
+    }
+
+    @Test
+    fun iconButton_darkTheme_focused() {
+        val focusRequester = FocusRequester()
+        var localInputModeManager: InputModeManager? = null
+
+        rule.setContent {
+            DarkMaterialTheme {
+                localInputModeManager = LocalInputModeManager.current
+                Box(Modifier.sizeIn(minWidth = 50.dp, minHeight = 50.dp).testTag(wrapperTestTag)) {
+                    IconButton(
+                        onClick = { /* doSomething() */ },
+                        modifier = Modifier
+                            .align(Alignment.Center)
+                            .focusRequester(focusRequester)
+                    ) {
+                        Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            @OptIn(ExperimentalComposeUiApi::class)
+            localInputModeManager!!.requestInputMode(InputMode.Keyboard)
+            focusRequester.requestFocus()
+        }
+
+        assertAgainstGolden("iconButton_darkTheme_focused")
+    }
+
+    @Test
+    fun iconButton_largeContentClipped() {
+        rule.setContent {
+            LightMaterialTheme {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    IconButton(onClick = { /* doSomething() */ }) {
+                        Box(
+                            Modifier
+                                .size(100.dp)
+                                .background(Color.Blue))
+                    }
+                }
+            }
+        }
+        assertAgainstGolden("iconButton_largeContentClipped")
+    }
+
+    @Test
+    fun outlinedIconButton_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    OutlinedIconButton(onClick = { /* doSomething() */ }) {
+                        Icon(
+                            Icons.Outlined.FavoriteBorder,
+                            contentDescription = "Localized description"
+                        )
+                    }
+                }
+            }
+        }
+        assertAgainstGolden("outlinedIconButton_lightTheme")
+    }
+
+    @Test
+    fun outlinedIconButton_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    OutlinedIconButton(onClick = { /* doSomething() */ }) {
+                        Icon(
+                            Icons.Outlined.FavoriteBorder,
+                            contentDescription = "Localized description"
+                        )
+                    }
+                }
+            }
+        }
+        assertAgainstGolden("outlinedIconButton_darkTheme")
+    }
+
+    @Test
+    fun outlinedIconButton_lightTheme_disabled() {
+        rule.setContent {
+            LightMaterialTheme {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    OutlinedIconButton(onClick = { /* doSomething() */ }, enabled = false) {
+                        Icon(
+                            Icons.Outlined.FavoriteBorder,
+                            contentDescription = "Localized description"
+                        )
+                    }
+                }
+            }
+        }
+        assertAgainstGolden("outlinedIconButton_lightTheme_disabled")
+    }
+
+    @Test
+    fun outlinedIconButton_darkTheme_disabled() {
+        rule.setContent {
+            DarkMaterialTheme {
+                Box(wrap.testTag(wrapperTestTag)) {
+                    OutlinedIconButton(onClick = { /* doSomething() */ }, enabled = false) {
+                        Icon(
+                            Icons.Outlined.FavoriteBorder,
+                            contentDescription = "Localized description"
+                        )
+                    }
+                }
+            }
+        }
+        assertAgainstGolden("outlinedIconButton_darkTheme_disabled")
+    }
+
+    private fun assertAgainstGolden(goldenName: String) {
+        rule.onNodeWithTag(wrapperTestTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, goldenName)
+    }
+}
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/IconButtonTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/IconButtonTest.kt
new file mode 100644
index 0000000..8e1fdc1
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/IconButtonTest.kt
@@ -0,0 +1,982 @@
+/*
+ * 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.tv.material3
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertIsEnabled
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(
+    ExperimentalTestApi::class,
+    ExperimentalComposeUiApi::class,
+    ExperimentalTvMaterial3Api::class
+)
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class IconButtonTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun filledIconButton_DefaultSize() {
+        rule.setContent {
+            IconButton(
+                modifier = Modifier
+                    .testTag(FilledIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(FilledIconButtonIconSize)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag(FilledIconButtonTag)
+            .assertWidthIsEqualTo(40.dp)
+            .assertHeightIsEqualTo(40.dp)
+    }
+
+    @Test
+    fun filledIconButton_SmallSize() {
+        rule.setContent {
+            IconButton(
+                modifier = Modifier
+                    .size(IconButtonDefaults.SmallButtonSize)
+                    .testTag(FilledIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(FilledIconButtonIconSize)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag(FilledIconButtonTag)
+            .assertWidthIsEqualTo(28.dp)
+            .assertHeightIsEqualTo(28.dp)
+    }
+
+    @Test
+    fun filledIconButton_MediumSize() {
+        rule.setContent {
+            IconButton(
+                modifier = Modifier
+                    .size(IconButtonDefaults.MediumButtonSize)
+                    .testTag(FilledIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(FilledIconButtonIconSize)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag(FilledIconButtonTag)
+            .assertWidthIsEqualTo(40.dp)
+            .assertHeightIsEqualTo(40.dp)
+    }
+
+    @Test
+    fun filledIconButton_LargeSize() {
+        rule.setContent {
+            IconButton(
+                modifier = Modifier
+                    .size(IconButtonDefaults.LargeButtonSize)
+                    .testTag(FilledIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(FilledIconButtonIconSize)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag(FilledIconButtonTag)
+            .assertWidthIsEqualTo(56.dp)
+            .assertHeightIsEqualTo(56.dp)
+    }
+
+    @Test
+    fun filledIconButton_CustomSize() {
+        rule.setContent {
+            IconButton(
+                modifier = Modifier
+                    .size(64.dp)
+                    .testTag(FilledIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(FilledIconButtonIconSize)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag(FilledIconButtonTag)
+            .assertWidthIsEqualTo(64.dp)
+            .assertHeightIsEqualTo(64.dp)
+    }
+
+    @Test
+    fun filledIconButton_size_withoutMinimumTouchTarget() {
+        val width = 24.dp
+        val height = 24.dp
+        rule.setContent {
+            IconButton(
+                modifier = Modifier
+                    .testTag(FilledIconButtonTag)
+                    .size(width, height),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(FilledIconButtonIconSize)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag(FilledIconButtonTag, useUnmergedTree = true)
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun filledIconButton_defaultSemantics() {
+        rule.setContent {
+            Box {
+                IconButton(modifier = Modifier.testTag(FilledIconButtonTag), onClick = {}) {
+                    Box(
+                        modifier = Modifier
+                            .size(IconButtonDefaults.MediumIconSize)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(FilledIconButtonTag)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Button))
+            .assertIsEnabled()
+    }
+
+    @Test
+    fun filledIconButton_disabledSemantics() {
+        rule.setContent {
+            Box {
+                IconButton(
+                    modifier = Modifier.testTag(FilledIconButtonTag),
+                    onClick = {},
+                    enabled = false
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(IconButtonDefaults.MediumIconSize)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(FilledIconButtonTag)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Button))
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun filledIconButton_findByTagAndClick() {
+        var counter = 0
+        val onClick: () -> Unit = { ++counter }
+
+        rule.setContent {
+            Box {
+                IconButton(
+                    modifier = Modifier.testTag(FilledIconButtonTag),
+                    onClick = onClick
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(IconButtonDefaults.MediumIconSize)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                }
+            }
+        }
+        rule.onNodeWithTag(FilledIconButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+        rule.runOnIdle {
+            Truth.assertThat(counter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun filledIconButton_canBeDisabled() {
+        rule.setContent {
+            var enabled by remember { mutableStateOf(true) }
+            Box {
+                IconButton(
+                    modifier = Modifier.testTag(FilledIconButtonTag),
+                    onClick = { enabled = false },
+                    enabled = enabled
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(IconButtonDefaults.MediumIconSize)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                }
+            }
+        }
+        rule.onNodeWithTag(FilledIconButtonTag)
+            // Confirm the button starts off enabled, with a click action
+            .assertHasClickAction()
+            .assertIsEnabled()
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+            // Then confirm it's disabled with click action after clicking it
+            .assertHasClickAction()
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun filledIconButton_clickIsIndependentBetweenButtons() {
+        var addButtonCounter = 0
+        val addButtonOnClick: () -> Unit = { ++addButtonCounter }
+        val addButtonTag = "AddButton"
+
+        var phoneButtonCounter = 0
+        val phoneButtonOnClick: () -> Unit = { ++phoneButtonCounter }
+        val phoneButtonTag = "PhoneButton"
+
+        rule.setContent {
+            Column {
+                IconButton(
+                    modifier = Modifier.testTag(addButtonTag),
+                    onClick = addButtonOnClick
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(IconButtonDefaults.MediumIconSize)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                }
+                IconButton(
+                    modifier = Modifier.testTag(phoneButtonTag),
+                    onClick = phoneButtonOnClick
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(IconButtonDefaults.MediumIconSize)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(addButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(addButtonCounter).isEqualTo(1)
+            Truth.assertThat(phoneButtonCounter).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag(phoneButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(addButtonCounter).isEqualTo(1)
+            Truth.assertThat(phoneButtonCounter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun filledIconButton_buttonPositioningSmallSize() {
+        rule.setContent {
+            IconButton(
+                modifier = Modifier
+                    .size(IconButtonDefaults.SmallButtonSize)
+                    .testTag(FilledIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(IconButtonDefaults.SmallIconSize)
+                        .testTag(FilledIconButtonIconTag)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        val buttonBounds = rule.onNodeWithTag(FilledIconButtonTag).getUnclippedBoundsInRoot()
+        val iconBounds = rule.onNodeWithTag(FilledIconButtonIconTag).getUnclippedBoundsInRoot()
+
+        (iconBounds.left - buttonBounds.left).assertIsEqualTo(
+            6.dp,
+            "padding between the start of the button and the start of the icon."
+        )
+
+        (iconBounds.top - buttonBounds.top).assertIsEqualTo(
+            6.dp,
+            "padding between the top of the button and the top of the icon."
+        )
+
+        (buttonBounds.right - iconBounds.right).assertIsEqualTo(
+            6.dp,
+            "padding between the end of the icon and the end of the button."
+        )
+
+        (buttonBounds.bottom - iconBounds.bottom).assertIsEqualTo(
+            6.dp,
+            "padding between the bottom of the button and the bottom of the icon."
+        )
+    }
+
+    @Test
+    fun filledIconButton_buttonPositioningDefaultOrMediumSize() {
+        rule.setContent {
+            IconButton(
+                modifier = Modifier.testTag(FilledIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(IconButtonDefaults.MediumIconSize)
+                        .testTag(FilledIconButtonIconTag)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        val buttonBounds = rule.onNodeWithTag(FilledIconButtonTag).getUnclippedBoundsInRoot()
+        val iconBounds = rule.onNodeWithTag(FilledIconButtonIconTag).getUnclippedBoundsInRoot()
+
+        (iconBounds.left - buttonBounds.left).assertIsEqualTo(
+            10.dp,
+            "padding between the start of the button and the start of the icon."
+        )
+
+        (iconBounds.top - buttonBounds.top).assertIsEqualTo(
+            10.dp,
+            "padding between the top of the button and the top of the icon."
+        )
+
+        (buttonBounds.right - iconBounds.right).assertIsEqualTo(
+            10.dp,
+            "padding between the end of the icon and the end of the button."
+        )
+
+        (buttonBounds.bottom - iconBounds.bottom).assertIsEqualTo(
+            10.dp,
+            "padding between the bottom of the button and the bottom of the icon."
+        )
+    }
+
+    @Test
+    fun filledIconButton_buttonPositioningLargeSize() {
+        rule.setContent {
+            IconButton(
+                modifier = Modifier
+                    .size(IconButtonDefaults.LargeButtonSize)
+                    .testTag(FilledIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(IconButtonDefaults.LargeIconSize)
+                        .testTag(FilledIconButtonIconTag)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        val buttonBounds = rule.onNodeWithTag(FilledIconButtonTag).getUnclippedBoundsInRoot()
+        val iconBounds = rule.onNodeWithTag(FilledIconButtonIconTag).getUnclippedBoundsInRoot()
+
+        (iconBounds.left - buttonBounds.left).assertIsEqualTo(
+            14.dp,
+            "padding between the start of the button and the start of the icon."
+        )
+
+        (iconBounds.top - buttonBounds.top).assertIsEqualTo(
+            14.dp,
+            "padding between the top of the button and the top of the icon."
+        )
+
+        (buttonBounds.right - iconBounds.right).assertIsEqualTo(
+            14.dp,
+            "padding between the end of the icon and the end of the button."
+        )
+
+        (buttonBounds.bottom - iconBounds.bottom).assertIsEqualTo(
+            14.dp,
+            "padding between the bottom of the button and the bottom of the icon."
+        )
+    }
+
+    @Test
+    fun filledIconButton_defaultSize_materialIconSize_iconPositioning() {
+        val diameter = IconButtonDefaults.MediumIconSize
+        rule.setContent {
+            Box {
+                IconButton(onClick = {}) {
+                    Box(
+                        Modifier
+                            .size(diameter)
+                            .testTag(FilledIconButtonIconTag)
+                    )
+                }
+            }
+        }
+
+        // Icon should be centered inside the FilledIconButton
+        rule.onNodeWithTag(FilledIconButtonIconTag, useUnmergedTree = true)
+            .assertLeftPositionInRootIsEqualTo(20.dp / 2)
+            .assertTopPositionInRootIsEqualTo(20.dp / 2)
+    }
+
+    @Test
+    fun filledIconButton_defaultSize_customIconSize_iconPositioning() {
+        val diameter = 20.dp
+        rule.setContent {
+            Box {
+                IconButton(onClick = {}) {
+                    Box(
+                        Modifier
+                            .size(diameter)
+                            .testTag(FilledIconButtonIconTag)
+                    )
+                }
+            }
+        }
+
+        // Icon should be centered inside the FilledIconButton
+        rule.onNodeWithTag(FilledIconButtonIconTag, useUnmergedTree = true)
+            .assertLeftPositionInRootIsEqualTo(10.dp)
+            .assertTopPositionInRootIsEqualTo(10.dp)
+    }
+
+    @Test
+    fun outlinedIconButton_DefaultSize() {
+        rule.setContent {
+            OutlinedIconButton(
+                modifier = Modifier
+                    .testTag(OutlinedIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(OutlinedIconButtonIconSize)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag(OutlinedIconButtonTag)
+            .assertWidthIsEqualTo(40.dp)
+            .assertHeightIsEqualTo(40.dp)
+    }
+
+    @Test
+    fun outlinedIconButton_SmallSize() {
+        rule.setContent {
+            OutlinedIconButton(
+                modifier = Modifier
+                    .size(OutlinedIconButtonDefaults.SmallButtonSize)
+                    .testTag(OutlinedIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(OutlinedIconButtonIconSize)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag(OutlinedIconButtonTag)
+            .assertWidthIsEqualTo(28.dp)
+            .assertHeightIsEqualTo(28.dp)
+    }
+
+    @Test
+    fun outlinedIconButton_MediumSize() {
+        rule.setContent {
+            OutlinedIconButton(
+                modifier = Modifier
+                    .size(OutlinedIconButtonDefaults.MediumButtonSize)
+                    .testTag(OutlinedIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(OutlinedIconButtonIconSize)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag(OutlinedIconButtonTag)
+            .assertWidthIsEqualTo(40.dp)
+            .assertHeightIsEqualTo(40.dp)
+    }
+
+    @Test
+    fun outlinedIconButton_LargeSize() {
+        rule.setContent {
+            OutlinedIconButton(
+                modifier = Modifier
+                    .size(OutlinedIconButtonDefaults.LargeButtonSize)
+                    .testTag(OutlinedIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(OutlinedIconButtonIconSize)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag(OutlinedIconButtonTag)
+            .assertWidthIsEqualTo(56.dp)
+            .assertHeightIsEqualTo(56.dp)
+    }
+
+    @Test
+    fun outlinedIconButton_CustomSize() {
+        rule.setContent {
+            OutlinedIconButton(
+                modifier = Modifier
+                    .size(64.dp)
+                    .testTag(OutlinedIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(OutlinedIconButtonIconSize)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag(OutlinedIconButtonTag)
+            .assertWidthIsEqualTo(64.dp)
+            .assertHeightIsEqualTo(64.dp)
+    }
+
+    @Test
+    fun outlinedIconButton__size_withoutMinimumTouchTarget() {
+        val width = 24.dp
+        val height = 24.dp
+        rule.setContent {
+            OutlinedIconButton(
+                modifier = Modifier
+                    .testTag(OutlinedIconButtonTag)
+                    .size(width, height),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(OutlinedIconButtonIconSize)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        rule.onNodeWithTag(OutlinedIconButtonTag, useUnmergedTree = true)
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun outlinedIconButton_defaultSemantics() {
+        rule.setContent {
+            Box {
+                OutlinedIconButton(
+                    modifier = Modifier.testTag(OutlinedIconButtonTag),
+                    onClick = {}
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(OutlinedIconButtonDefaults.MediumIconSize)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(OutlinedIconButtonTag)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Button))
+            .assertIsEnabled()
+    }
+
+    @Test
+    fun outlinedIconButton_disabledSemantics() {
+        rule.setContent {
+            Box {
+                OutlinedIconButton(
+                    modifier = Modifier.testTag(OutlinedIconButtonTag),
+                    onClick = {},
+                    enabled = false
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(OutlinedIconButtonDefaults.MediumIconSize)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(OutlinedIconButtonTag)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Button))
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun outlinedIconButton_findByTagAndClick() {
+        var counter = 0
+        val onClick: () -> Unit = { ++counter }
+
+        rule.setContent {
+            Box {
+                OutlinedIconButton(
+                    modifier = Modifier.testTag(OutlinedIconButtonTag),
+                    onClick = onClick
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(OutlinedIconButtonDefaults.MediumIconSize)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                }
+            }
+        }
+        rule.onNodeWithTag(OutlinedIconButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+        rule.runOnIdle {
+            Truth.assertThat(counter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun outlinedIconButton_canBeDisabled() {
+        rule.setContent {
+            var enabled by remember { mutableStateOf(true) }
+            Box {
+                OutlinedIconButton(
+                    modifier = Modifier.testTag(OutlinedIconButtonTag),
+                    onClick = { enabled = false },
+                    enabled = enabled
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(OutlinedIconButtonDefaults.MediumIconSize)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                }
+            }
+        }
+        rule.onNodeWithTag(OutlinedIconButtonTag)
+            // Confirm the button starts off enabled, with a click action
+            .assertHasClickAction()
+            .assertIsEnabled()
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+            // Then confirm it's disabled with click action after clicking it
+            .assertHasClickAction()
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun outlinedIconButton_clickIsIndependentBetweenButtons() {
+        var addButtonCounter = 0
+        val addButtonOnClick: () -> Unit = { ++addButtonCounter }
+        val addButtonTag = "AddButton"
+
+        var phoneButtonCounter = 0
+        val phoneButtonOnClick: () -> Unit = { ++phoneButtonCounter }
+        val phoneButtonTag = "PhoneButton"
+
+        rule.setContent {
+            Column {
+                OutlinedIconButton(
+                    modifier = Modifier.testTag(addButtonTag),
+                    onClick = addButtonOnClick
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(OutlinedIconButtonDefaults.MediumIconSize)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                }
+                OutlinedIconButton(
+                    modifier = Modifier.testTag(phoneButtonTag),
+                    onClick = phoneButtonOnClick
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .size(OutlinedIconButtonDefaults.MediumIconSize)
+                            .semantics(mergeDescendants = true) {}
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(addButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(addButtonCounter).isEqualTo(1)
+            Truth.assertThat(phoneButtonCounter).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag(phoneButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(addButtonCounter).isEqualTo(1)
+            Truth.assertThat(phoneButtonCounter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun outlinedIconButton_PositioningSmallSize() {
+        rule.setContent {
+            OutlinedIconButton(
+                modifier = Modifier
+                    .size(OutlinedIconButtonDefaults.SmallButtonSize)
+                    .testTag(OutlinedIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(OutlinedIconButtonDefaults.SmallIconSize)
+                        .testTag(OutlinedIconButtonIconTag)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        val buttonBounds = rule.onNodeWithTag(OutlinedIconButtonTag).getUnclippedBoundsInRoot()
+        val iconBounds = rule.onNodeWithTag(OutlinedIconButtonIconTag).getUnclippedBoundsInRoot()
+
+        (iconBounds.left - buttonBounds.left).assertIsEqualTo(
+            6.dp,
+            "padding between the start of the button and the start of the icon."
+        )
+
+        (iconBounds.top - buttonBounds.top).assertIsEqualTo(
+            6.dp,
+            "padding between the top of the button and the top of the icon."
+        )
+
+        (buttonBounds.right - iconBounds.right).assertIsEqualTo(
+            6.dp,
+            "padding between the end of the icon and the end of the button."
+        )
+
+        (buttonBounds.bottom - iconBounds.bottom).assertIsEqualTo(
+            6.dp,
+            "padding between the bottom of the button and the bottom of the icon."
+        )
+    }
+
+    @Test
+    fun outlinedIconButton_PositioningDefaultOrMediumSize() {
+        rule.setContent {
+            OutlinedIconButton(
+                modifier = Modifier.testTag(OutlinedIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(OutlinedIconButtonDefaults.MediumIconSize)
+                        .testTag(OutlinedIconButtonIconTag)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        val buttonBounds = rule.onNodeWithTag(OutlinedIconButtonTag).getUnclippedBoundsInRoot()
+        val iconBounds = rule.onNodeWithTag(OutlinedIconButtonIconTag).getUnclippedBoundsInRoot()
+
+        (iconBounds.left - buttonBounds.left).assertIsEqualTo(
+            10.dp,
+            "padding between the start of the button and the start of the icon."
+        )
+
+        (iconBounds.top - buttonBounds.top).assertIsEqualTo(
+            10.dp,
+            "padding between the top of the button and the top of the icon."
+        )
+
+        (buttonBounds.right - iconBounds.right).assertIsEqualTo(
+            10.dp,
+            "padding between the end of the icon and the end of the button."
+        )
+
+        (buttonBounds.bottom - iconBounds.bottom).assertIsEqualTo(
+            10.dp,
+            "padding between the bottom of the button and the bottom of the icon."
+        )
+    }
+
+    @Test
+    fun outlinedIconButton_PositioningLargeSize() {
+        rule.setContent {
+            OutlinedIconButton(
+                modifier = Modifier
+                    .size(OutlinedIconButtonDefaults.LargeButtonSize)
+                    .testTag(OutlinedIconButtonTag),
+                onClick = {}
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(OutlinedIconButtonDefaults.LargeIconSize)
+                        .testTag(OutlinedIconButtonIconTag)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        val buttonBounds = rule.onNodeWithTag(OutlinedIconButtonTag).getUnclippedBoundsInRoot()
+        val iconBounds = rule.onNodeWithTag(OutlinedIconButtonIconTag).getUnclippedBoundsInRoot()
+
+        (iconBounds.left - buttonBounds.left).assertIsEqualTo(
+            14.dp,
+            "padding between the start of the button and the start of the icon."
+        )
+
+        (iconBounds.top - buttonBounds.top).assertIsEqualTo(
+            14.dp,
+            "padding between the top of the button and the top of the icon."
+        )
+
+        (buttonBounds.right - iconBounds.right).assertIsEqualTo(
+            14.dp,
+            "padding between the end of the icon and the end of the button."
+        )
+
+        (buttonBounds.bottom - iconBounds.bottom).assertIsEqualTo(
+            14.dp,
+            "padding between the bottom of the button and the bottom of the icon."
+        )
+    }
+
+    @Test
+    fun outlinedIconButton_defaultSize_materialIconSize_iconPositioning() {
+        val diameter = OutlinedIconButtonDefaults.MediumIconSize
+        rule.setContent {
+            Box {
+                OutlinedIconButton(onClick = {}) {
+                    Box(
+                        Modifier
+                            .size(diameter)
+                            .testTag(OutlinedIconButtonIconTag)
+                    )
+                }
+            }
+        }
+
+        // Icon should be centered inside the OutlinedIconButton
+        rule.onNodeWithTag(OutlinedIconButtonIconTag, useUnmergedTree = true)
+            .assertLeftPositionInRootIsEqualTo(20.dp / 2)
+            .assertTopPositionInRootIsEqualTo(20.dp / 2)
+    }
+
+    @Test
+    fun outlinedIconButton_defaultSize_customIconSize_iconPositioning() {
+        val diameter = 20.dp
+        rule.setContent {
+            Box {
+                OutlinedIconButton(onClick = {}) {
+                    Box(
+                        Modifier
+                            .size(diameter)
+                            .testTag(OutlinedIconButtonIconTag)
+                    )
+                }
+            }
+        }
+
+        // Icon should be centered inside the OutlinedIconButton
+        rule.onNodeWithTag(OutlinedIconButtonIconTag, useUnmergedTree = true)
+            .assertLeftPositionInRootIsEqualTo(10.dp)
+            .assertTopPositionInRootIsEqualTo(10.dp)
+    }
+}
+
+private const val FilledIconButtonTag = "FilledIconButton"
+private const val FilledIconButtonIconTag = "FilledIconButtonIcon"
+private val FilledIconButtonIconSize = 18.0.dp
+
+private const val OutlinedIconButtonTag = "OutlinedIconButton"
+private const val OutlinedIconButtonIconTag = "OutlinedIconButtonIcon"
+private val OutlinedIconButtonIconSize = 18.0.dp
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Button.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Button.kt
index cf59b79..b547d9d 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Button.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Button.kt
@@ -211,55 +211,3 @@
         }
     }
 }
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-private fun ButtonShape.toClickableSurfaceShape(): ClickableSurfaceShape = ClickableSurfaceShape(
-    shape = shape,
-    focusedShape = focusedShape,
-    pressedShape = pressedShape,
-    disabledShape = disabledShape,
-    focusedDisabledShape = focusedDisabledShape
-)
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-private fun ButtonColors.toClickableSurfaceContainerColor(): ClickableSurfaceColor =
-    ClickableSurfaceColor(
-        color = containerColor,
-        focusedColor = focusedContainerColor,
-        pressedColor = pressedContainerColor,
-        disabledColor = disabledContainerColor,
-    )
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-private fun ButtonColors.toClickableSurfaceContentColor(): ClickableSurfaceColor =
-    ClickableSurfaceColor(
-        color = contentColor,
-        focusedColor = focusedContentColor,
-        pressedColor = pressedContentColor,
-        disabledColor = disabledContentColor,
-    )
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-private fun ButtonScale.toClickableSurfaceScale() = ClickableSurfaceScale(
-    scale = scale,
-    focusedScale = focusedScale,
-    pressedScale = pressedScale,
-    disabledScale = disabledScale,
-    focusedDisabledScale = focusedDisabledScale
-)
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-private fun ButtonBorder.toClickableSurfaceBorder() = ClickableSurfaceBorder(
-    border = border,
-    focusedBorder = focusedBorder,
-    pressedBorder = pressedBorder,
-    disabledBorder = disabledBorder,
-    focusedDisabledBorder = focusedDisabledBorder
-)
-
-@OptIn(ExperimentalTvMaterial3Api::class)
-private fun ButtonGlow.toClickableSurfaceGlow() = ClickableSurfaceGlow(
-    glow = glow,
-    focusedGlow = focusedGlow,
-    pressedGlow = pressedGlow
-)
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/ButtonStyles.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ButtonStyles.kt
index 89fe0a7..75e56dd 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/ButtonStyles.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ButtonStyles.kt
@@ -243,3 +243,55 @@
         return "ButtonGlow(glow=$glow, focusedGlow=$focusedGlow, pressedGlow=$pressedGlow)"
     }
 }
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ButtonShape.toClickableSurfaceShape(): ClickableSurfaceShape = ClickableSurfaceShape(
+    shape = shape,
+    focusedShape = focusedShape,
+    pressedShape = pressedShape,
+    disabledShape = disabledShape,
+    focusedDisabledShape = focusedDisabledShape
+)
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ButtonColors.toClickableSurfaceContainerColor(): ClickableSurfaceColor =
+    ClickableSurfaceColor(
+        color = containerColor,
+        focusedColor = focusedContainerColor,
+        pressedColor = pressedContainerColor,
+        disabledColor = disabledContainerColor,
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ButtonColors.toClickableSurfaceContentColor(): ClickableSurfaceColor =
+    ClickableSurfaceColor(
+        color = contentColor,
+        focusedColor = focusedContentColor,
+        pressedColor = pressedContentColor,
+        disabledColor = disabledContentColor,
+    )
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ButtonScale.toClickableSurfaceScale() = ClickableSurfaceScale(
+    scale = scale,
+    focusedScale = focusedScale,
+    pressedScale = pressedScale,
+    disabledScale = disabledScale,
+    focusedDisabledScale = focusedDisabledScale
+)
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ButtonBorder.toClickableSurfaceBorder() = ClickableSurfaceBorder(
+    border = border,
+    focusedBorder = focusedBorder,
+    pressedBorder = pressedBorder,
+    disabledBorder = disabledBorder,
+    focusedDisabledBorder = focusedDisabledBorder
+)
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun ButtonGlow.toClickableSurfaceGlow() = ClickableSurfaceGlow(
+    glow = glow,
+    focusedGlow = focusedGlow,
+    pressedGlow = pressedGlow
+)
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/IconButton.kt b/tv/tv-material/src/main/java/androidx/tv/material3/IconButton.kt
new file mode 100644
index 0000000..1c66a17
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/IconButton.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.tv.material3
+
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+
+/**
+ * Material Design standard icon button for TV.
+ *
+ * Icon buttons help people take supplementary actions with a single tap. They’re used when a
+ * compact button is required, such as in a toolbar or image list.
+ *
+ * [content] should typically be an [Icon]. If using a custom icon, note that the typical size for
+ * the internal icon is 24 x 24 dp.
+ *
+ * The default text style for internal [Text] components will be set to [Typography.labelLarge].
+ *
+ * @param onClick called when this button is clicked
+ * @param modifier the [Modifier] to be applied to this button
+ * @param enabled controls the enabled state of this button. When `false`, this component will not
+ * respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services.
+ * @param scale Defines size of the Button relative to its original size.
+ * @param glow Shadow to be shown behind the Button.
+ * @param shape Defines the Button's shape.
+ * @param colors Color to be used for background and content of the Button
+ * @param border Defines a border around the Button.
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this button. You can create and pass in your own `remember`ed instance to observe
+ * [Interaction]s and customize the appearance / behavior of this button in different states.
+ * @param content the content of the button, typically an [Icon]
+ */
+@ExperimentalTvMaterial3Api
+@NonRestartableComposable
+@Composable
+fun IconButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    scale: ButtonScale = IconButtonDefaults.scale(),
+    glow: ButtonGlow = IconButtonDefaults.glow(),
+    shape: ButtonShape = IconButtonDefaults.shape(),
+    colors: ButtonColors = IconButtonDefaults.colors(),
+    border: ButtonBorder = IconButtonDefaults.border(),
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    content: @Composable BoxScope.() -> Unit
+) {
+    Surface(
+        modifier = modifier
+            .semantics { role = Role.Button }
+            .size(IconButtonDefaults.MediumButtonSize),
+        onClick = onClick,
+        enabled = enabled,
+        shape = shape.toClickableSurfaceShape(),
+        color = colors.toClickableSurfaceContainerColor(),
+        contentColor = colors.toClickableSurfaceContentColor(),
+        scale = scale.toClickableSurfaceScale(),
+        border = border.toClickableSurfaceBorder(),
+        glow = glow.toClickableSurfaceGlow(),
+        interactionSource = interactionSource
+    ) {
+        Box(
+            modifier = Modifier.fillMaxSize(),
+            contentAlignment = Alignment.Center,
+            content = content
+        )
+    }
+}
+
+/**
+ * Material Design standard icon button for TV.
+ *
+ * Icon buttons help people take supplementary actions with a single tap. They’re used when a
+ * compact button is required, such as in a toolbar or image list.
+ *
+ * [content] should typically be an [Icon]. If using a custom icon, note that the typical size for
+ * the internal icon is 24 x 24 dp.
+ * This icon button has an overall minimum touch target size of 48 x 48dp, to meet accessibility
+ * guidelines.
+ *
+ * The default text style for internal [Text] components will be set to [Typography.labelLarge].
+ *
+ * @param onClick called when this button is clicked
+ * @param modifier the [Modifier] to be applied to this button
+ * @param enabled controls the enabled state of this button. When `false`, this component will not
+ * respond to user input, and it will appear visually disabled and disabled to accessibility
+ * services.
+ * @param scale Defines size of the Button relative to its original size
+ * @param glow Shadow to be shown behind the Button.
+ * @param shape Defines the Button's shape.
+ * @param colors Color to be used for background and content of the Button
+ * @param border Defines a border around the Button.
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this button. You can create and pass in your own `remember`ed instance to observe
+ * [Interaction]s and customize the appearance / behavior of this button in different states.
+ * @param content the content of the button, typically an [Icon]
+ */
+@ExperimentalTvMaterial3Api
+@NonRestartableComposable
+@Composable
+fun OutlinedIconButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    scale: ButtonScale = OutlinedIconButtonDefaults.scale(),
+    glow: ButtonGlow = OutlinedIconButtonDefaults.glow(),
+    shape: ButtonShape = OutlinedIconButtonDefaults.shape(),
+    colors: ButtonColors = OutlinedIconButtonDefaults.colors(),
+    border: ButtonBorder = OutlinedIconButtonDefaults.border(),
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    content: @Composable BoxScope.() -> Unit
+) {
+    Surface(
+        modifier = modifier
+            .semantics { role = Role.Button }
+            .size(OutlinedIconButtonDefaults.MediumButtonSize),
+        onClick = onClick,
+        enabled = enabled,
+        shape = shape.toClickableSurfaceShape(),
+        color = colors.toClickableSurfaceContainerColor(),
+        contentColor = colors.toClickableSurfaceContentColor(),
+        scale = scale.toClickableSurfaceScale(),
+        border = border.toClickableSurfaceBorder(),
+        glow = glow.toClickableSurfaceGlow(),
+        interactionSource = interactionSource
+    ) {
+        Box(
+            modifier = Modifier.fillMaxSize(),
+            contentAlignment = Alignment.Center,
+            content = content
+        )
+    }
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/IconButtonDefaults.kt b/tv/tv-material/src/main/java/androidx/tv/material3/IconButtonDefaults.kt
new file mode 100644
index 0000000..c56fd82
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/IconButtonDefaults.kt
@@ -0,0 +1,351 @@
+/*
+ * 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.tv.material3
+
+import androidx.compose.foundation.interaction.Interaction
+import androidx.annotation.FloatRange
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.unit.dp
+
+@ExperimentalTvMaterial3Api
+object IconButtonDefaults {
+    private val ContainerShape = CircleShape
+
+    /** The size of a small icon inside [IconButton] */
+    val SmallIconSize = 16.dp
+
+    /** The size of a medium icon inside [IconButton] */
+    val MediumIconSize = 20.dp
+
+    /** The size of a large icon inside [IconButton] */
+    val LargeIconSize = 28.dp
+
+    /** The size of a small [IconButton] */
+    val SmallButtonSize = 28.dp
+
+    /** The size of a medium [IconButton]. */
+    val MediumButtonSize = 40.dp
+
+    /** The size of a large [IconButton]. */
+    val LargeButtonSize = 56.dp
+
+    /**
+     * Creates a [ButtonShape] that represents the default container shapes used in a
+     * IconButton.
+     *
+     * @param shape the shape used when the Button is enabled, and has no other [Interaction]s.
+     * @param focusedShape the shape used when the Button is enabled and focused.
+     * @param pressedShape the shape used when the Button is enabled pressed.
+     * @param disabledShape the shape used when the Button is not enabled.
+     * @param focusedDisabledShape the shape used when the Button is not enabled and focused.
+     */
+    fun shape(
+        shape: Shape = ContainerShape,
+        focusedShape: Shape = shape,
+        pressedShape: Shape = shape,
+        disabledShape: Shape = shape,
+        focusedDisabledShape: Shape = disabledShape
+    ) = ButtonShape(
+        shape = shape,
+        focusedShape = focusedShape,
+        pressedShape = pressedShape,
+        disabledShape = disabledShape,
+        focusedDisabledShape = focusedDisabledShape
+    )
+
+    /**
+     * Creates a [ButtonColors] that represents the default colors used in a IconButton.
+     *
+     * @param containerColor the container color of this Button when enabled
+     * @param contentColor the content color of this Button when enabled
+     * @param focusedContainerColor the container color of this Button when enabled and focused
+     * @param focusedContentColor the content color of this Button when enabled and focused
+     * @param pressedContainerColor the container color of this Button when enabled and pressed
+     * @param pressedContentColor the content color of this Button when enabled and pressed
+     * @param disabledContainerColor the container color of this Button when not enabled
+     * @param disabledContentColor the content color of this Button when not enabled
+     */
+    @ReadOnlyComposable
+    @Composable
+    fun colors(
+        containerColor: Color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.8f),
+        contentColor: Color = MaterialTheme.colorScheme.onSurface,
+        focusedContainerColor: Color = MaterialTheme.colorScheme.onSurface,
+        focusedContentColor: Color = MaterialTheme.colorScheme.inverseOnSurface,
+        pressedContainerColor: Color = focusedContainerColor,
+        pressedContentColor: Color = focusedContentColor,
+        disabledContainerColor: Color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.4f),
+        disabledContentColor: Color = contentColor,
+    ) = ButtonColors(
+        containerColor = containerColor,
+        contentColor = contentColor,
+        focusedContainerColor = focusedContainerColor,
+        focusedContentColor = focusedContentColor,
+        pressedContainerColor = pressedContainerColor,
+        pressedContentColor = pressedContentColor,
+        disabledContainerColor = disabledContainerColor,
+        disabledContentColor = disabledContentColor,
+    )
+
+    /**
+     * Creates a [ButtonScale] that represents the default scales used in a IconButton.
+     * scales are used to modify the size of a composable in different [Interaction]
+     * states e.g. 1f (original) in default state, 1.2f (scaled up) in focused state,
+     * 0.8f (scaled down) in pressed state, etc.
+     *
+     * @param scale the scale to be used for this Button when enabled
+     * @param focusedScale the scale to be used for this Button when focused
+     * @param pressedScale the scale to be used for this Button when pressed
+     * @param disabledScale the scale to be used for this Button when disabled
+     * @param focusedDisabledScale the scale to be used for this Button when disabled and
+     * focused
+     */
+    fun scale(
+        @FloatRange(from = 0.0) scale: Float = 1f,
+        @FloatRange(from = 0.0) focusedScale: Float = 1.1f,
+        @FloatRange(from = 0.0) pressedScale: Float = scale,
+        @FloatRange(from = 0.0) disabledScale: Float = scale,
+        @FloatRange(from = 0.0) focusedDisabledScale: Float = disabledScale
+    ) = ButtonScale(
+        scale = scale,
+        focusedScale = focusedScale,
+        pressedScale = pressedScale,
+        disabledScale = disabledScale,
+        focusedDisabledScale = focusedDisabledScale
+    )
+
+    /**
+     * Creates a [ButtonBorder] that represents the default [Border]s applied on a
+     * IconButton in different [Interaction] states.
+     *
+     * @param border the [Border] to be used for this Button when enabled
+     * @param focusedBorder the [Border] to be used for this Button when focused
+     * @param pressedBorder the [Border] to be used for this Button when pressed
+     * @param disabledBorder the [Border] to be used for this Button when disabled
+     * @param focusedDisabledBorder the [Border] to be used for this Button when disabled and
+     * focused
+     */
+    @ReadOnlyComposable
+    @Composable
+    fun border(
+        border: Border = Border.None,
+        focusedBorder: Border = border,
+        pressedBorder: Border = focusedBorder,
+        disabledBorder: Border = border,
+        focusedDisabledBorder: Border = Border(
+            border = BorderStroke(
+                width = 2.dp,
+                color = MaterialTheme.colorScheme.border.copy(alpha = 0.2f)
+            ),
+            shape = ContainerShape
+        )
+    ) = ButtonBorder(
+        border = border,
+        focusedBorder = focusedBorder,
+        pressedBorder = pressedBorder,
+        disabledBorder = disabledBorder,
+        focusedDisabledBorder = focusedDisabledBorder
+    )
+
+    /**
+     * Creates a [ButtonGlow] that represents the default [Glow]s used in a IconButton.
+     *
+     * @param glow the Glow behind this Button when enabled
+     * @param focusedGlow the Glow behind this Button when focused
+     * @param pressedGlow the Glow behind this Button when pressed
+     */
+    fun glow(
+        glow: Glow = Glow.None,
+        focusedGlow: Glow = glow,
+        pressedGlow: Glow = glow
+    ) = ButtonGlow(
+        glow = glow,
+        focusedGlow = focusedGlow,
+        pressedGlow = pressedGlow
+    )
+}
+
+@ExperimentalTvMaterial3Api
+object OutlinedIconButtonDefaults {
+    private val ContainerShape = CircleShape
+
+    val SmallIconSize = 16.dp
+    val MediumIconSize = 20.dp
+    val LargeIconSize = 28.dp
+
+    /** The size of a small [OutlinedIconButton] */
+    val SmallButtonSize = 28.dp
+
+    /** The size of a medium [OutlinedIconButton]. */
+    val MediumButtonSize = 40.dp
+
+    /** The size of a large [OutlinedIconButton]. */
+    val LargeButtonSize = 56.dp
+
+    /**
+     * Creates a [ButtonShape] that represents the default container shapes used in an
+     * OutlinedIconButton.
+     *
+     * @param shape the shape used when the Button is enabled, and has no other [Interaction]s.
+     * @param focusedShape the shape used when the Button is enabled and focused.
+     * @param pressedShape the shape used when the Button is enabled pressed.
+     * @param disabledShape the shape used when the Button is not enabled.
+     * @param focusedDisabledShape the shape used when the Button is not enabled and focused.
+     */
+    fun shape(
+        shape: Shape = ContainerShape,
+        focusedShape: Shape = shape,
+        pressedShape: Shape = shape,
+        disabledShape: Shape = shape,
+        focusedDisabledShape: Shape = disabledShape
+    ) = ButtonShape(
+        shape = shape,
+        focusedShape = focusedShape,
+        pressedShape = pressedShape,
+        disabledShape = disabledShape,
+        focusedDisabledShape = focusedDisabledShape
+    )
+
+    /**
+     * Creates a [ButtonColors] that represents the default colors used in a OutlinedIconButton.
+     *
+     * @param containerColor the container color of this Button when enabled
+     * @param contentColor the content color of this Button when enabled
+     * @param focusedContainerColor the container color of this Button when enabled and focused
+     * @param focusedContentColor the content color of this Button when enabled and focused
+     * @param pressedContainerColor the container color of this Button when enabled and pressed
+     * @param pressedContentColor the content color of this Button when enabled and pressed
+     * @param disabledContainerColor the container color of this Button when not enabled
+     * @param disabledContentColor the content color of this Button when not enabled
+     */
+    @ReadOnlyComposable
+    @Composable
+    fun colors(
+        containerColor: Color = Color.Transparent,
+        contentColor: Color = MaterialTheme.colorScheme.onSurface,
+        focusedContainerColor: Color = MaterialTheme.colorScheme.onSurface,
+        focusedContentColor: Color = MaterialTheme.colorScheme.inverseOnSurface,
+        pressedContainerColor: Color = focusedContainerColor,
+        pressedContentColor: Color = focusedContentColor,
+        disabledContainerColor: Color = containerColor,
+        disabledContentColor: Color = contentColor,
+    ) = ButtonColors(
+        containerColor = containerColor,
+        contentColor = contentColor,
+        focusedContainerColor = focusedContainerColor,
+        focusedContentColor = focusedContentColor,
+        pressedContainerColor = pressedContainerColor,
+        pressedContentColor = pressedContentColor,
+        disabledContainerColor = disabledContainerColor,
+        disabledContentColor = disabledContentColor,
+    )
+
+    /**
+     * Creates a [ButtonScale] that represents the default scales used in an
+     * OutlinedIconButton.
+     * scales are used to modify the size of a composable in different [Interaction]
+     * states e.g. 1f (original) in default state, 1.2f (scaled up) in focused state,
+     * 0.8f (scaled down) in pressed state, etc.
+     *
+     * @param scale the scale to be used for this Button when enabled
+     * @param focusedScale the scale to be used for this Button when focused
+     * @param pressedScale the scale to be used for this Button when pressed
+     * @param disabledScale the scale to be used for this Button when disabled
+     * @param focusedDisabledScale the scale to be used for this Button when disabled and
+     * focused
+     */
+    fun scale(
+        @FloatRange(from = 0.0) scale: Float = 1f,
+        @FloatRange(from = 0.0) focusedScale: Float = 1.1f,
+        @FloatRange(from = 0.0) pressedScale: Float = scale,
+        @FloatRange(from = 0.0) disabledScale: Float = scale,
+        @FloatRange(from = 0.0) focusedDisabledScale: Float = disabledScale
+    ) = ButtonScale(
+        scale = scale,
+        focusedScale = focusedScale,
+        pressedScale = pressedScale,
+        disabledScale = disabledScale,
+        focusedDisabledScale = focusedDisabledScale
+    )
+
+    /**
+     * Creates a [ButtonBorder] that represents the default [Border]s applied on an
+     * OutlinedIconButton in different [Interaction] states.
+     *
+     * @param border the [Border] to be used for this Button when enabled
+     * @param focusedBorder the [Border] to be used for this Button when focused
+     * @param pressedBorder the [Border] to be used for this Button when pressed
+     * @param disabledBorder the [Border] to be used for this Button when disabled
+     * @param focusedDisabledBorder the [Border] to be used for this Button when disabled and
+     * focused
+     */
+    @ReadOnlyComposable
+    @Composable
+    fun border(
+        border: Border = Border(
+            border = BorderStroke(
+                width = 2.dp,
+                color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f)
+            ),
+            shape = ContainerShape
+        ),
+        focusedBorder: Border = Border(
+            border = BorderStroke(width = 2.dp, color = MaterialTheme.colorScheme.onSurface),
+            shape = ContainerShape
+        ),
+        pressedBorder: Border = Border(
+            border = BorderStroke(width = 2.dp, color = MaterialTheme.colorScheme.onSurface),
+            shape = ContainerShape
+        ),
+        disabledBorder: Border = Border(
+            border = BorderStroke(
+                width = 2.dp,
+                color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.2f)
+            ),
+            shape = ContainerShape
+        ),
+        focusedDisabledBorder: Border = disabledBorder
+    ) = ButtonBorder(
+        border = border,
+        focusedBorder = focusedBorder,
+        pressedBorder = pressedBorder,
+        disabledBorder = disabledBorder,
+        focusedDisabledBorder = focusedDisabledBorder
+    )
+
+    /**
+     * Creates a [ButtonGlow] that represents the default [Glow]s used in an OutlinedIconButton.
+     *
+     * @param glow the Glow behind this Button when enabled
+     * @param focusedGlow the Glow behind this Button when focused
+     * @param pressedGlow the Glow behind this Button when pressed
+     */
+    fun glow(
+        glow: Glow = Glow.None,
+        focusedGlow: Glow = glow,
+        pressedGlow: Glow = glow
+    ) = ButtonGlow(
+        glow = glow,
+        focusedGlow = focusedGlow,
+        pressedGlow = pressedGlow
+    )
+}