feat: introduce buttons composables in tv material

Patchset-1: Direct fork of compose.material3 Button and OutlinedButton
Latest-Patchset: TV related style changes

Test: added instrumentation and snapshot tests

Relnote: "Introduce Buttons in TV Material"

Change-Id: I69c114f31dd374675276629659ac40ec8c2ebbfa
diff --git a/tv/tv-material/api/public_plus_experimental_current.txt b/tv/tv-material/api/public_plus_experimental_current.txt
index 067c9ae..beb7c7f 100644
--- a/tv/tv-material/api/public_plus_experimental_current.txt
+++ b/tv/tv-material/api/public_plus_experimental_current.txt
@@ -24,6 +24,43 @@
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.IndicationInstance rememberUpdatedInstance(androidx.compose.foundation.interaction.InteractionSource interactionSource);
   }
 
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonBorder {
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonColors {
+  }
+
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonDefaults {
+    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 androidx.compose.foundation.layout.PaddingValues getButtonWithIconContentPadding();
+    method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+    method public float getIconSize();
+    method public float getIconSpacing();
+    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 androidx.compose.foundation.layout.PaddingValues ButtonWithIconContentPadding;
+    property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
+    property public final float IconSize;
+    property public final float IconSpacing;
+    field public static final androidx.tv.material3.ButtonDefaults INSTANCE;
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonGlow {
+  }
+
+  public final class ButtonKt {
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Button(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 float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void OutlinedButton(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 float tonalElevation, optional androidx.tv.material3.ButtonBorder border, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit> content);
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonScale {
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ButtonShape {
+  }
+
   @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CardBorder {
   }
 
@@ -288,6 +325,23 @@
     method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static androidx.tv.material3.DrawerState rememberDrawerState(androidx.tv.material3.DrawerValue initialValue);
   }
 
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class OutlinedButtonDefaults {
+    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 androidx.compose.foundation.layout.PaddingValues getButtonWithIconContentPadding();
+    method public androidx.compose.foundation.layout.PaddingValues getContentPadding();
+    method public float getIconSize();
+    method public float getIconSpacing();
+    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 androidx.compose.foundation.layout.PaddingValues ButtonWithIconContentPadding;
+    property public final androidx.compose.foundation.layout.PaddingValues ContentPadding;
+    property public final float IconSize;
+    property public final float IconSpacing;
+    field public static final androidx.tv.material3.OutlinedButtonDefaults 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/ButtonScreenshotTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ButtonScreenshotTest.kt
new file mode 100644
index 0000000..35873cd
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ButtonScreenshotTest.kt
@@ -0,0 +1,279 @@
+/*
+ * 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.layout.Spacer
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+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.onNodeWithText
+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
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+class ButtonScreenshotTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(TV_GOLDEN_MATERIAL3)
+
+    @Test
+    fun default_button_light_theme() {
+        rule.setContent {
+            LightMaterialTheme {
+                Button(onClick = { }) {
+                    Text("Button")
+                }
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "button_light_theme")
+    }
+
+    @Test
+    fun default_button_dark_theme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                Button(onClick = { }) {
+                    Text("Button")
+                }
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "button_dark_theme")
+    }
+
+    @Test
+    fun disabled_button_light_theme() {
+        rule.setContent {
+            LightMaterialTheme {
+                Button(onClick = { }, enabled = false) {
+                    Text("Button")
+                }
+            }
+        }
+
+        rule.onNodeWithText("Button")
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "button_disabled_light_theme")
+    }
+
+    @Test
+    fun disabled_button_dark_theme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                Button(onClick = { }, enabled = false) {
+                    Text("Button")
+                }
+            }
+        }
+
+        rule.onNodeWithText("Button")
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "button_disabled_dark_theme")
+    }
+
+    @Test
+    fun outlined_button_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                OutlinedButton(onClick = {}) {
+                    Text("Outlined Button")
+                }
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "outlined_button_light_theme")
+    }
+
+    @Test
+    fun outlined_button_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                OutlinedButton(onClick = {}) {
+                    Text("Outlined Button")
+                }
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "outlined_button_dark_theme")
+    }
+
+    @Test
+    fun disabled_outlined_button_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                OutlinedButton(
+                    onClick = {},
+                    enabled = false,
+                    modifier = Modifier.testTag("button")
+                ) {
+                    Text("Outlined Button")
+                }
+            }
+        }
+
+        rule.onNodeWithTag("button")
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "outlined_button_disabled_light_theme")
+    }
+
+    @Test
+    fun disabled_outlined_button_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                OutlinedButton(
+                    onClick = {},
+                    enabled = false,
+                    modifier = Modifier.testTag("button")
+                ) {
+                    Text("Outlined Button")
+                }
+            }
+        }
+
+        rule.onNodeWithTag("button")
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "outlined_button_disabled_dark_theme")
+    }
+
+    @Test
+    fun button_withIcon_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                Button(
+                    onClick = { /* Do something! */ },
+                    contentPadding = ButtonDefaults.ButtonWithIconContentPadding
+                ) {
+                    Icon(
+                        Icons.Filled.Favorite,
+                        contentDescription = "Localized description",
+                        modifier = Modifier.size(ButtonDefaults.IconSize)
+                    )
+                    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
+                    Text("Like")
+                }
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "button_withIcon_lightTheme")
+    }
+
+    @Test
+    fun button_withIcon_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                Button(
+                    onClick = { /* Do something! */ },
+                    contentPadding = ButtonDefaults.ButtonWithIconContentPadding
+                ) {
+                    Icon(
+                        Icons.Filled.Favorite,
+                        contentDescription = "Localized description",
+                        modifier = Modifier.size(ButtonDefaults.IconSize)
+                    )
+                    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
+                    Text("Like")
+                }
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "button_withIcon_darkTheme")
+    }
+
+    @Test
+    fun disabled_button_withIcon_lightTheme() {
+        rule.setContent {
+            LightMaterialTheme {
+                Button(
+                    onClick = { /* Do something! */ },
+                    contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
+                    enabled = false,
+                    modifier = Modifier.testTag("button")
+                ) {
+                    Icon(
+                        Icons.Filled.Favorite,
+                        contentDescription = "Localized description",
+                        modifier = Modifier.size(ButtonDefaults.IconSize)
+                    )
+                    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
+                    Text("Like")
+                }
+            }
+        }
+
+        rule.onNodeWithTag("button")
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "button_withIcon_disabled_lightTheme")
+    }
+
+    @Test
+    fun disabled_button_withIcon_darkTheme() {
+        rule.setContent {
+            DarkMaterialTheme {
+                Button(
+                    onClick = { /* Do something! */ },
+                    contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
+                    enabled = false,
+                    modifier = Modifier.testTag("button")
+                ) {
+                    Icon(
+                        Icons.Filled.Favorite,
+                        contentDescription = "Localized description",
+                        modifier = Modifier.size(ButtonDefaults.IconSize)
+                    )
+                    Spacer(Modifier.size(ButtonDefaults.IconSpacing))
+                    Text("Like")
+                }
+            }
+        }
+
+        rule.onNodeWithTag("button")
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "button_withIcon_disabled_darkTheme")
+    }
+}
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/ButtonTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ButtonTest.kt
new file mode 100644
index 0000000..70ba16f
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ButtonTest.kt
@@ -0,0 +1,480 @@
+/*
+ * 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.Spacer
+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.assertIsEnabled
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.test.assertIsNotEnabled
+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
+
+@LargeTest
+@OptIn(
+    ExperimentalTestApi::class,
+    ExperimentalComposeUiApi::class,
+    ExperimentalTvMaterial3Api::class
+)
+@RunWith(AndroidJUnit4::class)
+class ButtonTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun filledButton_defaultSemantics() {
+        rule.setContent {
+            Box {
+                Button(modifier = Modifier.testTag(FilledButtonTag), onClick = {}) {
+                    Text("FilledButton")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(FilledButtonTag)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Button))
+            .assertIsEnabled()
+    }
+
+    @Test
+    fun filledButton_disabledSemantics() {
+        rule.setContent {
+            Box {
+                Button(
+                    modifier = Modifier.testTag(FilledButtonTag),
+                    onClick = {},
+                    enabled = false
+                ) {
+                    Text("FilledButton")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(FilledButtonTag)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Button))
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun filledButton_findByTag_andClick() {
+        var counter = 0
+        val onClick: () -> Unit = { ++counter }
+        val text = "FilledButtonText"
+
+        rule.setContent {
+            Box {
+                Button(modifier = Modifier.testTag(FilledButtonTag), onClick = onClick) {
+                    Text(text)
+                }
+            }
+        }
+        rule.onNodeWithTag(FilledButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+        rule.runOnIdle {
+            Truth.assertThat(counter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun filledButton_canBeDisabled() {
+        rule.setContent {
+            var enabled by remember { mutableStateOf(true) }
+            Box {
+                Button(
+                    modifier = Modifier.testTag(FilledButtonTag),
+                    onClick = { enabled = false },
+                    enabled = enabled
+                ) {
+                    Text("Hello")
+                }
+            }
+        }
+        rule.onNodeWithTag(FilledButtonTag)
+            // 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 filledButton_clickIs_independent_betweenButtons() {
+        var watchButtonCounter = 0
+        val watchButtonOnClick: () -> Unit = { ++watchButtonCounter }
+        val watchButtonTag = "WatchButton"
+
+        var playButtonCounter = 0
+        val playButtonOnClick: () -> Unit = { ++playButtonCounter }
+        val playButtonTag = "PlayButton"
+
+        rule.setContent {
+            Column {
+                Button(
+                    modifier = Modifier.testTag(watchButtonTag),
+                    onClick = watchButtonOnClick
+                ) {
+                    Text("Watch")
+                }
+                Button(
+                    modifier = Modifier.testTag(playButtonTag),
+                    onClick = playButtonOnClick
+                ) {
+                    Text("Play")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(watchButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(watchButtonCounter).isEqualTo(1)
+            Truth.assertThat(playButtonCounter).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag(playButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(watchButtonCounter).isEqualTo(1)
+            Truth.assertThat(playButtonCounter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun filledButton_buttonPositioning() {
+        rule.setContent {
+            Button(
+                onClick = {},
+                modifier = Modifier.testTag(FilledButtonTag)
+            ) {
+                Text(
+                    "FilledButton",
+                    modifier = Modifier
+                        .testTag(FilledButtonTextTag)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        val buttonBounds = rule.onNodeWithTag(FilledButtonTag).getUnclippedBoundsInRoot()
+        val textBounds = rule.onNodeWithTag(FilledButtonTextTag).getUnclippedBoundsInRoot()
+
+        (textBounds.left - buttonBounds.left).assertIsEqualTo(
+            16.dp,
+            "padding between the start of the button and the start of the text."
+        )
+
+        (buttonBounds.right - textBounds.right).assertIsEqualTo(
+            16.dp,
+            "padding between the end of the text and the end of the button."
+        )
+    }
+
+    @Test
+    fun filledButtonWithIcon_positioning() {
+        rule.setContent {
+            Button(
+                onClick = {},
+                contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
+                modifier = Modifier
+                    .testTag(FilledButtonTag)
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(FilledButtonIconSize)
+                        .testTag(FilledButtonIconTag)
+                        .semantics(mergeDescendants = true) {}
+                )
+                Spacer(Modifier.size(FilledButtonIconSpacing))
+                Text(
+                    "Liked it",
+                    modifier = Modifier
+                        .testTag(FilledButtonTextTag)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        val textBounds = rule.onNodeWithTag(FilledButtonTextTag).getUnclippedBoundsInRoot()
+        val iconBounds = rule.onNodeWithTag(FilledButtonIconTag).getUnclippedBoundsInRoot()
+        val buttonBounds = rule.onNodeWithTag(FilledButtonTag).getUnclippedBoundsInRoot()
+
+        (iconBounds.left - buttonBounds.left).assertIsEqualTo(
+            expected = 12.dp,
+            subject = "Padding between start of button and start of icon."
+        )
+
+        (textBounds.left - iconBounds.right).assertIsEqualTo(
+            expected = FilledButtonIconSpacing,
+            subject = "Padding between end of icon and start of text."
+        )
+
+        (buttonBounds.right - textBounds.right).assertIsEqualTo(
+            expected = 16.dp,
+            subject = "padding between end of text and end of button."
+        )
+    }
+
+    @Test
+    fun outlinedButton_defaultSemantics() {
+        rule.setContent {
+            Box {
+                OutlinedButton(modifier = Modifier.testTag(OutlinedButtonTag), onClick = {}) {
+                    Text("OutlinedButton")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(OutlinedButtonTag)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Button))
+            .assertIsEnabled()
+    }
+
+    @Test
+    fun outlinedButton_disabledSemantics() {
+        rule.setContent {
+            Box {
+                OutlinedButton(
+                    modifier = Modifier.testTag(OutlinedButtonTag),
+                    onClick = {},
+                    enabled = false
+                ) {
+                    Text("OutlinedButton")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(OutlinedButtonTag)
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Button))
+            .assertIsNotEnabled()
+    }
+
+    @Test
+    fun outlinedButton_findByTag_andClick() {
+        var counter = 0
+        val onClick: () -> Unit = { ++counter }
+        val text = "OutlinedButtonText"
+
+        rule.setContent {
+            Box {
+                OutlinedButton(modifier = Modifier.testTag(OutlinedButtonTag), onClick = onClick) {
+                    Text(text)
+                }
+            }
+        }
+        rule.onNodeWithTag(OutlinedButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+        rule.runOnIdle {
+            Truth.assertThat(counter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun outlinedButton_canBeDisabled() {
+        rule.setContent {
+            var enabled by remember { mutableStateOf(true) }
+            Box {
+                OutlinedButton(
+                    modifier = Modifier.testTag(OutlinedButtonTag),
+                    onClick = { enabled = false },
+                    enabled = enabled
+                ) {
+                    Text("Hello")
+                }
+            }
+        }
+        rule.onNodeWithTag(OutlinedButtonTag)
+            // 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 outlinedButton_clickIs_independent_betweenButtons() {
+        var watchButtonCounter = 0
+        val watchButtonOnClick: () -> Unit = { ++watchButtonCounter }
+        val watchButtonTag = "WatchButton"
+
+        var playButtonCounter = 0
+        val playButtonOnClick: () -> Unit = { ++playButtonCounter }
+        val playButtonTag = "PlayButton"
+
+        rule.setContent {
+            Column {
+                OutlinedButton(
+                    modifier = Modifier.testTag(watchButtonTag),
+                    onClick = watchButtonOnClick
+                ) {
+                    Text("Watch")
+                }
+                OutlinedButton(
+                    modifier = Modifier.testTag(playButtonTag),
+                    onClick = playButtonOnClick
+                ) {
+                    Text("Play")
+                }
+            }
+        }
+
+        rule.onNodeWithTag(watchButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(watchButtonCounter).isEqualTo(1)
+            Truth.assertThat(playButtonCounter).isEqualTo(0)
+        }
+
+        rule.onNodeWithTag(playButtonTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(watchButtonCounter).isEqualTo(1)
+            Truth.assertThat(playButtonCounter).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun outlinedButton_buttonPositioning() {
+        rule.setContent {
+            OutlinedButton(
+                onClick = {},
+                modifier = Modifier.testTag(OutlinedButtonTag)
+            ) {
+                Text(
+                    "OutlinedButton",
+                    modifier = Modifier
+                        .testTag(OutlinedButtonTextTag)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        val buttonBounds = rule.onNodeWithTag(OutlinedButtonTag).getUnclippedBoundsInRoot()
+        val textBounds = rule.onNodeWithTag(OutlinedButtonTextTag).getUnclippedBoundsInRoot()
+
+        (textBounds.left - buttonBounds.left).assertIsEqualTo(
+            16.dp,
+            "padding between the start of the button and the start of the text."
+        )
+
+        (buttonBounds.right - textBounds.right).assertIsEqualTo(
+            16.dp,
+            "padding between the end of the text and the end of the button."
+        )
+    }
+
+    @Test
+    fun outlinedButton_buttonWithIcon_positioning() {
+        rule.setContent {
+            OutlinedButton(
+                onClick = {},
+                contentPadding = OutlinedButtonDefaults.ButtonWithIconContentPadding,
+                modifier = Modifier
+                    .testTag(OutlinedButtonTag)
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(OutlinedButtonIconSize)
+                        .testTag(OutlinedButtonIconTag)
+                        .semantics(mergeDescendants = true) {}
+                )
+                Spacer(Modifier.size(OutlinedButtonIconSpacing))
+                Text(
+                    "Liked it",
+                    modifier = Modifier
+                        .testTag(OutlinedButtonTextTag)
+                        .semantics(mergeDescendants = true) {}
+                )
+            }
+        }
+
+        val textBounds = rule.onNodeWithTag(OutlinedButtonTextTag).getUnclippedBoundsInRoot()
+        val iconBounds = rule.onNodeWithTag(OutlinedButtonIconTag).getUnclippedBoundsInRoot()
+        val buttonBounds = rule.onNodeWithTag(OutlinedButtonTag).getUnclippedBoundsInRoot()
+
+        (iconBounds.left - buttonBounds.left).assertIsEqualTo(
+            expected = 12.dp,
+            subject = "Padding between start of button and start of icon."
+        )
+
+        (textBounds.left - iconBounds.right).assertIsEqualTo(
+            expected = OutlinedButtonIconSpacing,
+            subject = "Padding between end of icon and start of text."
+        )
+
+        (buttonBounds.right - textBounds.right).assertIsEqualTo(
+            expected = 16.dp,
+            subject = "padding between end of text and end of button."
+        )
+    }
+}
+
+private const val FilledButtonTag = "FilledButton"
+private const val FilledButtonTextTag = "FilledButtonText"
+private const val FilledButtonIconTag = "FilledButtonIcon"
+private val FilledButtonIconSize = 18.0.dp
+private val FilledButtonIconSpacing = 8.dp
+
+private const val OutlinedButtonTag = "OutlinedButton"
+private const val OutlinedButtonTextTag = "OutlinedButtonText"
+private const val OutlinedButtonIconTag = "OutlinedButtonIcon"
+private val OutlinedButtonIconSize = 18.0.dp
+private val OutlinedButtonIconSpacing = 8.dp
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/MaterialThemeCommon.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/MaterialThemeCommon.kt
new file mode 100644
index 0000000..9267ce5
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/MaterialThemeCommon.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.runtime.Composable
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+fun LightMaterialTheme(content: @Composable () -> Unit) {
+    MaterialTheme(lightColorScheme()) {
+        content()
+    }
+}
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+fun DarkMaterialTheme(content: @Composable () -> Unit) {
+    MaterialTheme(darkColorScheme()) {
+        content()
+    }
+}
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
new file mode 100644
index 0000000..cf59b79
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Button.kt
@@ -0,0 +1,265 @@
+/*
+ * 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.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.padding
+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
+import androidx.compose.ui.unit.Dp
+import androidx.tv.material3.tokens.Elevation
+
+/**
+ * Material Design filled button for TV.
+ *
+ * Filled buttons are for high emphasis (important, final actions that complete a flow).
+ *
+ * Choose the best button for an action based on the amount of emphasis it needs. The more important
+ * an action is, the higher emphasis its button should be.
+ *
+ * - See [Button] for high emphasis (important, final actions that complete a flow).
+ * - See [OutlinedButton] for a medium-emphasis button with a border.
+ *
+ * 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 tonalElevation tonal elevation used to apply a color shift to the button to give the it
+ * higher emphasis
+ * @param border Defines a border around the Button.
+ * @param contentPadding the spacing values to apply internally between the container and the
+ * content
+ * @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
+ */
+@ExperimentalTvMaterial3Api
+@NonRestartableComposable
+@Composable
+fun Button(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    scale: ButtonScale = ButtonDefaults.scale(),
+    glow: ButtonGlow = ButtonDefaults.glow(),
+    shape: ButtonShape = ButtonDefaults.shape(),
+    colors: ButtonColors = ButtonDefaults.colors(),
+    tonalElevation: Dp = Elevation.Level0,
+    border: ButtonBorder = ButtonDefaults.border(),
+    contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    content: @Composable RowScope.() -> Unit
+) {
+    ButtonImpl(
+        onClick = onClick,
+        modifier = modifier,
+        enabled = enabled,
+        scale = scale,
+        glow = glow,
+        shape = shape,
+        colors = colors,
+        tonalElevation = tonalElevation,
+        border = border,
+        contentPadding = contentPadding,
+        interactionSource = interactionSource,
+        content = content
+    )
+}
+
+/**
+ * Material Design outlined button for TV.
+ *
+ * Outlined buttons are medium-emphasis buttons. They contain actions that are important, but are
+ * not the primary action in an app. Outlined buttons pair well with [Button]s to indicate an
+ * alternative, secondary action.
+ *
+ * Choose the best button for an action based on the amount of emphasis it needs. The more important
+ * an action is, the higher emphasis its button should be.
+ *
+ * - See [Button] for high emphasis (important, final actions that complete a flow).
+ * - See [OutlinedButton] for a medium-emphasis button with a border.
+ *
+ * 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 tonalElevation tonal elevation used to apply a color shift to the button to give the it
+ * higher emphasis
+ * @param border Defines a border around the Button.
+ * @param contentPadding the spacing values to apply internally between the container and the
+ * content
+ * @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
+ */
+@ExperimentalTvMaterial3Api
+@NonRestartableComposable
+@Composable
+fun OutlinedButton(
+    onClick: () -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    scale: ButtonScale = OutlinedButtonDefaults.scale(),
+    glow: ButtonGlow = OutlinedButtonDefaults.glow(),
+    shape: ButtonShape = OutlinedButtonDefaults.shape(),
+    colors: ButtonColors = OutlinedButtonDefaults.colors(),
+    tonalElevation: Dp = Elevation.Level0,
+    border: ButtonBorder = OutlinedButtonDefaults.border(),
+    contentPadding: PaddingValues = OutlinedButtonDefaults.ContentPadding,
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    content: @Composable RowScope.() -> Unit
+) {
+    ButtonImpl(
+        onClick = onClick,
+        modifier = modifier,
+        enabled = enabled,
+        scale = scale,
+        glow = glow,
+        shape = shape,
+        colors = colors,
+        tonalElevation = tonalElevation,
+        border = border,
+        contentPadding = contentPadding,
+        interactionSource = interactionSource,
+        content = content
+    )
+}
+
+@ExperimentalTvMaterial3Api
+@Composable
+private fun ButtonImpl(
+    onClick: () -> Unit,
+    modifier: Modifier,
+    enabled: Boolean,
+    scale: ButtonScale,
+    glow: ButtonGlow,
+    shape: ButtonShape,
+    colors: ButtonColors,
+    tonalElevation: Dp,
+    border: ButtonBorder,
+    contentPadding: PaddingValues,
+    interactionSource: MutableInteractionSource,
+    content: @Composable RowScope.() -> Unit
+) {
+    Surface(
+        modifier = modifier.semantics { role = Role.Button },
+        onClick = onClick,
+        enabled = enabled,
+        scale = scale.toClickableSurfaceScale(),
+        glow = glow.toClickableSurfaceGlow(),
+        shape = shape.toClickableSurfaceShape(),
+        color = colors.toClickableSurfaceContainerColor(),
+        contentColor = colors.toClickableSurfaceContentColor(),
+        tonalElevation = tonalElevation,
+        border = border.toClickableSurfaceBorder(),
+        interactionSource = interactionSource
+    ) {
+        ProvideTextStyle(value = MaterialTheme.typography.labelLarge) {
+            Row(
+                modifier = Modifier
+                    .defaultMinSize(
+                        minWidth = BaseButtonDefaults.MinWidth,
+                        minHeight = BaseButtonDefaults.MinHeight
+                    )
+                    .padding(contentPadding),
+                horizontalArrangement = Arrangement.Center,
+                verticalAlignment = Alignment.CenterVertically,
+                content = content
+            )
+        }
+    }
+}
+
+@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/ButtonDefaults.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ButtonDefaults.kt
new file mode 100644
index 0000000..e147851
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ButtonDefaults.kt
@@ -0,0 +1,381 @@
+/*
+ * 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.annotation.FloatRange
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.layout.PaddingValues
+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
+
+internal object BaseButtonDefaults {
+    val MinWidth = 58.dp
+    val MinHeight = 40.dp
+}
+
+@ExperimentalTvMaterial3Api
+object ButtonDefaults {
+    private val ContainerShape = CircleShape
+    private val ButtonHorizontalPadding = 16.dp
+    private val ButtonVerticalPadding = 10.dp
+    private val ButtonWithIconHorizontalStartPadding = 12.dp
+
+    val ContentPadding = PaddingValues(
+        start = ButtonHorizontalPadding,
+        top = ButtonVerticalPadding,
+        end = ButtonHorizontalPadding,
+        bottom = ButtonVerticalPadding
+    )
+
+    val ButtonWithIconContentPadding = PaddingValues(
+        start = ButtonWithIconHorizontalStartPadding,
+        top = ButtonVerticalPadding,
+        end = ButtonHorizontalPadding,
+        bottom = ButtonVerticalPadding
+    )
+
+    /** The default size of the icon when used inside any button. */
+    val IconSize = 18.dp
+
+    /**
+     * The default size of the spacing between an icon and a text when they used inside any button.
+     */
+    val IconSpacing = 8.dp
+
+    /**
+     * Creates a [ButtonShape] that represents the default container shapes used in a FilledButton.
+     *
+     * @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 FilledButton.
+     *
+     * @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.copy(alpha = 0.8f),
+        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 = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f),
+    ) = 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 FilledButton.
+     * 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
+     * FilledButton 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 = 1.5.dp,
+                color = MaterialTheme.colorScheme.onSurfaceVariant.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 FilledButton.
+     *
+     * @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 OutlinedButtonDefaults {
+    private val ContainerShape = CircleShape
+    private val ButtonHorizontalPadding = 16.dp
+    private val ButtonVerticalPadding = 10.dp
+    private val ButtonWithIconHorizontalStartPadding = 12.dp
+
+    val ContentPadding = PaddingValues(
+        start = ButtonHorizontalPadding,
+        top = ButtonVerticalPadding,
+        end = ButtonHorizontalPadding,
+        bottom = ButtonVerticalPadding
+    )
+
+    /** The default size of the icon when used inside any button. */
+    val IconSize = 18.dp
+
+    /**
+     * The default size of the spacing between an icon and a text when they used inside any button.
+     */
+    val IconSpacing = 8.dp
+
+    /** The default content padding used by [OutlinedButton] that contains an [Icon]. */
+    val ButtonWithIconContentPadding = PaddingValues(
+        start = ButtonWithIconHorizontalStartPadding,
+        top = ButtonVerticalPadding,
+        end = ButtonHorizontalPadding,
+        bottom = ButtonVerticalPadding
+    )
+
+    /**
+     * Creates a [ButtonShape] that represents the default container shapes used in an
+     * OutlinedButton.
+     *
+     * @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 OutlinedButton.
+     *
+     * @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.copy(alpha = 0.8f),
+        focusedContainerColor: Color = MaterialTheme.colorScheme.onSurface,
+        focusedContentColor: Color = MaterialTheme.colorScheme.inverseOnSurface,
+        pressedContainerColor: Color = focusedContainerColor,
+        pressedContentColor: Color = focusedContentColor,
+        disabledContainerColor: Color = containerColor,
+        disabledContentColor: Color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f),
+    ) = 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 OutlinedButton.
+     * 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
+     * OutlinedButton 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 = 1.5.dp,
+                color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f)
+            ),
+            shape = ContainerShape
+        ),
+        focusedBorder: Border = Border(
+            border = BorderStroke(
+                width = 1.65.dp,
+                color = MaterialTheme.colorScheme.onSurfaceVariant
+            ),
+            shape = ContainerShape
+        ),
+        pressedBorder: Border = Border(
+            border = BorderStroke(
+                width = 1.5.dp,
+                color = MaterialTheme.colorScheme.onSurfaceVariant
+            ),
+            shape = ContainerShape
+        ),
+        disabledBorder: Border = Border(
+            border = BorderStroke(
+                width = 1.5.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 OutlinedButton.
+     *
+     * @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
+    )
+}
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
new file mode 100644
index 0000000..89fe0a7
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ButtonStyles.kt
@@ -0,0 +1,245 @@
+/*
+ * 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.annotation.FloatRange
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+
+/**
+ * Defines [Shape] for all TV [Interaction] states of Button.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ButtonShape internal constructor(
+    internal val shape: Shape,
+    internal val focusedShape: Shape,
+    internal val pressedShape: Shape,
+    internal val disabledShape: Shape,
+    internal val focusedDisabledShape: Shape
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ButtonShape
+
+        if (shape != other.shape) return false
+        if (focusedShape != other.focusedShape) return false
+        if (pressedShape != other.pressedShape) return false
+        if (disabledShape != other.disabledShape) return false
+        if (focusedDisabledShape != other.focusedDisabledShape) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = shape.hashCode()
+        result = 31 * result + focusedShape.hashCode()
+        result = 31 * result + pressedShape.hashCode()
+        result = 31 * result + disabledShape.hashCode()
+        result = 31 * result + focusedDisabledShape.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ButtonShape(shape=$shape, focusedShape=$focusedShape, pressedShape=$pressedShape," +
+            " disabledShape=$disabledShape, focusedDisabledShape=$focusedDisabledShape)"
+    }
+}
+
+/**
+ * Defines [Color]s for all TV [Interaction] states of Button.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ButtonColors internal constructor(
+    internal val containerColor: Color,
+    internal val contentColor: Color,
+    internal val focusedContainerColor: Color,
+    internal val focusedContentColor: Color,
+    internal val pressedContainerColor: Color,
+    internal val pressedContentColor: Color,
+    internal val disabledContainerColor: Color,
+    internal val disabledContentColor: Color,
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ButtonColors
+
+        if (containerColor != other.containerColor) return false
+        if (contentColor != other.contentColor) return false
+        if (focusedContainerColor != other.focusedContainerColor) return false
+        if (focusedContentColor != other.focusedContentColor) return false
+        if (pressedContainerColor != other.pressedContainerColor) return false
+        if (pressedContentColor != other.pressedContentColor) return false
+        if (disabledContainerColor != other.disabledContainerColor) return false
+        if (disabledContentColor != other.disabledContentColor) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = containerColor.hashCode()
+        result = 31 * result + contentColor.hashCode()
+        result = 31 * result + focusedContainerColor.hashCode()
+        result = 31 * result + focusedContentColor.hashCode()
+        result = 31 * result + pressedContainerColor.hashCode()
+        result = 31 * result + pressedContentColor.hashCode()
+        result = 31 * result + disabledContainerColor.hashCode()
+        result = 31 * result + disabledContentColor.hashCode()
+        return result
+    }
+
+    override fun toString(): String {
+        return "ButtonColors(containerColor=$containerColor, contentColor=$contentColor, " +
+            "focusedContainerColor=$focusedContainerColor, " +
+            "focusedContentColor=$focusedContentColor, " +
+            "pressedContainerColor=$pressedContainerColor, " +
+            "pressedContentColor=$pressedContentColor, " +
+            "disabledContainerColor=$disabledContainerColor, " +
+            "disabledContentColor=$disabledContentColor)"
+    }
+}
+
+/**
+ * Defines the scale for all TV [Interaction] states of Button.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ButtonScale internal constructor(
+    @FloatRange(from = 0.0) internal val scale: Float,
+    @FloatRange(from = 0.0) internal val focusedScale: Float,
+    @FloatRange(from = 0.0) internal val pressedScale: Float,
+    @FloatRange(from = 0.0) internal val disabledScale: Float,
+    @FloatRange(from = 0.0) internal val focusedDisabledScale: Float
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ButtonScale
+
+        if (scale != other.scale) return false
+        if (focusedScale != other.focusedScale) return false
+        if (pressedScale != other.pressedScale) return false
+        if (disabledScale != other.disabledScale) return false
+        if (focusedDisabledScale != other.focusedDisabledScale) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = scale.hashCode()
+        result = 31 * result + focusedScale.hashCode()
+        result = 31 * result + pressedScale.hashCode()
+        result = 31 * result + disabledScale.hashCode()
+        result = 31 * result + focusedDisabledScale.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ButtonScale(scale=$scale, focusedScale=$focusedScale, pressedScale=$pressedScale," +
+            " disabledScale=$disabledScale, focusedDisabledScale=$focusedDisabledScale)"
+    }
+}
+
+/**
+ * Defines [Border] for all TV [Interaction] states of Button.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ButtonBorder internal constructor(
+    internal val border: Border,
+    internal val focusedBorder: Border,
+    internal val pressedBorder: Border,
+    internal val disabledBorder: Border,
+    internal val focusedDisabledBorder: Border
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ButtonBorder
+
+        if (border != other.border) return false
+        if (focusedBorder != other.focusedBorder) return false
+        if (pressedBorder != other.pressedBorder) return false
+        if (disabledBorder != other.disabledBorder) return false
+        if (focusedDisabledBorder != other.focusedDisabledBorder) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = border.hashCode()
+        result = 31 * result + focusedBorder.hashCode()
+        result = 31 * result + pressedBorder.hashCode()
+        result = 31 * result + disabledBorder.hashCode()
+        result = 31 * result + focusedDisabledBorder.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ButtonBorder(border=$border, focusedBorder=$focusedBorder," +
+            "pressedBorder=$pressedBorder, disabledBorder=$disabledBorder, " +
+            "focusedDisabledBorder=$focusedDisabledBorder)"
+    }
+}
+
+/**
+ * Defines [Glow] for all TV [Interaction] states of Button.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ButtonGlow internal constructor(
+    internal val glow: Glow,
+    internal val focusedGlow: Glow,
+    internal val pressedGlow: Glow
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ButtonGlow
+
+        if (glow != other.glow) return false
+        if (focusedGlow != other.focusedGlow) return false
+        if (pressedGlow != other.pressedGlow) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = glow.hashCode()
+        result = 31 * result + focusedGlow.hashCode()
+        result = 31 * result + pressedGlow.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ButtonGlow(glow=$glow, focusedGlow=$focusedGlow, pressedGlow=$pressedGlow)"
+    }
+}