pekingme 7c66a2ef94 [ButtonGroup] Added the "wrap" overflow mode.
If the "wrap" overflow mode is in use, the button group will wrap the buttons that don't fit in one row to more rows below.

PiperOrigin-RevId: 800551513
2025-08-29 11:44:37 -07:00

21 KiB
Raw Permalink Blame History

Button groups

Button groups organize buttons and add interactions between them.There are two variants of a button group.

2 types of button groups

  1. Standard button group
  2. Connected button group

Note: Images use various dynamic color schemes.

Design & API documentation

Anatomy

Button groups are invisible containers that add padding between buttons and modify button shape. They dont contain any buttons by default.

Anatomy of a button group

  1. Container

More details on anatomy items in the component guidelines.

M3 Expressive

M3 Expressive update

Before you can use Material3Expressive component styles, follow the Material3Expressive themes setup instructions.

Standard button group in 3 of 5 available sizes, and segmented button group with just icon buttons and just common buttons.

Button groups apply shape, motion, and width changes to buttons and icon buttons to make them more interactive. More on M3 Expressive

Button groups are new components added in Expressive.

Types and naming:

  • Added standard button group
  • Added connected button group
    • Use instead of segmented button, which is deprecated

Configurations:

  • Works with all button sizes: XS, S, M, L, and XL
  • Applies default shape to all buttons: round or square

M3 Expressive styles

Default styles in the expressive themes:

  • Standard button group: Widget.Material3Expressive.MaterialButtonGroup
  • Connected button group: Widget.Material3Expressive.MaterialButtonGroup.Connected

Key properties

Shape and size attributes

Element Attribute Related method(s) Default value
Group shape (outer corners) app:shapeAppearance setShapeAppearance
getShapeAppearance
none
Size of inner corners app:innerCornerSize setInnerCornerSize
getInnerCornerSize
none
Spacing between buttons android:spacing setSpacing
getSpacing
12dp
Child size change app:childSizeChange N/A 15% in pressed,
otherwise, 0%
Child overflow mode app:overflowMode setOverflowMode
getOverflowMode
none
Overflow button icon app:overflowButtonIcon setOverflowButtonIcon abc_ic_menu_overflow_material (3 dots)

Additional attributes of child buttons

Element Attribute Related method(s) Default value
Title of the overflow menu item app:layout_overflowText N/A button's text value, if not specified or empty
Icon of the overflow menu item app:layout_overflowIcon N/A null

Styles and theme attributes

Element Style Theme Attribute
Default style Widget.Material3.MaterialButtonGroup ?attr/materialButtonGroupStyle
Overflow icon button style ?attr/materialIconButtonStyle ?attr/materialButtonGroupOverflowButtonStyle
Overflow menu style ?attr/popupMenuStyle ?attr/materialButtonGroupOverflowPopupMenuStyle

Variants of button groups

Standard button group

The standard button group contains multiple related individual buttons. The individual button's shape is preserved.

Examples of using standard button group

Button group examples

Source code:

The following example shows a button group with three buttons that have text labels.

In the layout:

<com.google.android.material.button.MaterialButtonGroup
    android:id="@+id/buttonGroup"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1"
    />
    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 2"
    />
    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 3"
    />
</com.google.android.material.button.MaterialButtonGroup>

Connected button group

In addition to standard button groups, connected button group also overrides the individual button's shape to make them visually more belong to a group with 2dp spacing, 8dp inner corners, and fully rounded outer corners.

Examples of using connected button group

Connected button group examples

Source code:

The following example shows a connected button group with three buttons that have text labels. To correctly style a button group as connected button group, the specific style needs to be set.

In the layout:

<com.google.android.material.button.MaterialButtonGroup
    android:id="@+id/buttonGroup"
    style="@style/Widget.Material3.MaterialButtonGroup.Connected"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <Button
        style="?attr/materialButtonOutlinedStyle"
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1" />
    <Button
        style="?attr/materialButtonOutlinedStyle"
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 2" />
    <Button
        style="?attr/materialButtonOutlinedStyle"
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 3" />
</com.google.android.material.button.MaterialButtonGroup>

Code implementation

Before you can use Material button groups, you need to add a dependency to the Material components for Android library. For more information, go to the Getting started page.

Note: <Button> is auto-inflated as <com.google.android.material.button.MaterialButton> via MaterialComponentsViewInflater when using a Theme.Material3.* theme.

Making button groups adaptive

MaterialButtonGroup inherits from the LinearLayout. It can be configured to achieve different child arrangements for different screen sizes or foldable screens by using layout_width and layout_weight.

Fixed button sizes

When child buttons should not be adjusted while screen size changes, consider using layout_width on all buttons.

Button group with fixed arrangement

<com.google.android.material.button.MaterialButtonGroup
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingBottom="8dp"
    android:gravity="center_horizontal"
    android:spacing="4dp">
    <Button
        style="?attr/materialIconButtonFilledStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/cat_button_previous_icon"
        android:gravity="center"
        app:iconGravity="textStart"
        app:icon="@drawable/cat_button_previous_icon"/>
    <Button
        style="?attr/materialIconButtonFilledStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/cat_button_play_icon"
        android:gravity="center"
        app:iconGravity="textStart"
        app:icon="@drawable/cat_button_play_icon"/>
    <Button
        style="?attr/materialIconButtonFilledStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/cat_button_next_icon"
        android:gravity="center"
        app:iconGravity="textStart"
        app:icon="@drawable/cat_button_next_icon"/>
</com.google.android.material.button.MaterialButtonGroup>

Flexible button sizes

When all child buttons are equally important or their sizes are proportional to each other, consider using layout_weight on all buttons.

Button group with flexible arrangement

<com.google.android.material.button.MaterialButtonGroup
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingBottom="8dp"
    android:gravity="center_horizontal"
    android:spacing="4dp">
    <Button
        style="?attr/materialIconButtonFilledStyle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:contentDescription="@string/cat_button_previous_icon"
        android:gravity="center"
        app:iconGravity="textStart"
        app:icon="@drawable/cat_button_previous_icon"/>
    <Button
        style="?attr/materialIconButtonFilledStyle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:contentDescription="@string/cat_button_play_icon"
        android:gravity="center"
        app:iconGravity="textStart"
        app:icon="@drawable/cat_button_play_icon"/>
    <Button
        style="?attr/materialIconButtonFilledStyle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:contentDescription="@string/cat_button_next_icon"
        android:gravity="center"
        app:iconGravity="textStart"
        app:icon="@drawable/cat_button_next_icon"/>
</com.google.android.material.button.MaterialButtonGroup>

Mixed button sizes

When only some buttons are flexible for different screen sizes, consider using layout_weight on these buttons but use layout_width on the rest as below.

Button group with mixed arrangement

<com.google.android.material.button.MaterialButtonGroup
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingBottom="8dp"
    android:gravity="center_horizontal"
    android:spacing="4dp">
    <Button
        style="?attr/materialIconButtonFilledStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/cat_button_previous_icon"
        android:gravity="center"
        app:iconGravity="textStart"
        app:icon="@drawable/cat_button_previous_icon"/>
    <Button
        style="?attr/materialIconButtonFilledStyle"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:contentDescription="@string/cat_button_play_icon"
        android:gravity="center"
        app:iconGravity="textStart"
        app:icon="@drawable/cat_button_play_icon"/>
    <Button
        style="?attr/materialIconButtonFilledStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/cat_button_next_icon"
        android:gravity="center"
        app:iconGravity="textStart"
        app:icon="@drawable/cat_button_next_icon"/>
</com.google.android.material.button.MaterialButtonGroup>

Handling overflow

Overflow happens when the screen size or button group size is not enough to display all child buttons. By default (mode none), the button's text will be clipped or wrapped.

Overflow mode - menu

Button group overflow menu mode

Setting overflowMode=menu dynamically hides child buttons that don't fit in the current screen width in a popup menu. An icon button will be added automatically at the end of the button group for toggling the popup menu, once it's needed. The style of this icon button can be set via materialButtonGroupOverflowButtonStyle attribute in your theme overlay. The icon can be configured via overflowButtonIcon in the button group.

Every hidden child button will be represented by a MenuItem in the popup menu. You can specify the menu item's title via layout_overflowText and the menu item's icon via layout_overflowIcon in the specific button.

Note: Do not use the menu overflow mode to toggle button group, since the toggle button group should demonstrate all available options and the current selection.

Note: This feature assumes all child buttons should be visible in the group or as a menu item in the overflow menu. Visibility of all child buttons will be managed by the button group when setting overflowMode=menu.

Overflow mode - wrap

Button group overflow wrap mode

Setting overflowMode=wrap will cause the buttons to wrap to the next line when they don't fit on the current line. This can be useful when you want to display all buttons without hiding them in a menu, but you have a limited amount of horizontal space. The buttons will maintain their shape and size as defined in the layout.

This mode is useful in cases:

  • Responsive layouts: When you want buttons to adapt to different screen sizes by wrapping to the next line instead of being hidden.
  • Content-heavy interfaces: When you have many buttons and want to ensure they are all visible, even on smaller screens.
  • Accessibility: When you need to display all buttons but there's not enough space.
Configuration

Do not use the wrap overflow mode with layout_width="wrap_content", orientation="vertical", or layout_weight in child buttons, due to undefined expected behaviors. The layout_width can be set to a fixed dp size or match_parent. The height of the group is determined by the number of wrapped rows and spacing. So the value of layout_height will be ignored.

Wrapping gravity

In order to make the child buttons wrapped to the correct position, they must have layout_gravity set. When start|top is used, the android:gravity in MaterialButtonGroup can be ignored. If end|top is used, the android:gravity in MaterialButtonGroup must be set to end as well.

Making buttons accessible

Buttons support content labeling for accessibility and are readable by most screen readers, such as TalkBack. Text rendered in buttons is automatically provided to accessibility services. Additional content labels are usually unnecessary.

For more information on content labels, go to the Android accessibility help guide.

Customizing button groups

Theming buttons

Buttons support the customization of color, typography, and shape.

Button theming example

API and source code:

The following example shows text, outlined and filled button types with Material theming.

"Button theming with three buttons - text, outlined and filled - with pink
color theming and cut corners."

Implementing button theming

Use theme attributes and styles in res/values/styles.xml to add the theme to all buttons. This affects other components:

<style name="Theme.App" parent="Theme.Material3.*">
    ...
    <item name="colorPrimary">@color/shrine_pink_100</item>
    <item name="colorOnPrimary">@color/shrine_pink_900</item>
    <item name="textAppearanceLabelLarge">@style/TextAppearance.App.Button</item>
    <item name="shapeCornerFamily">cut</item>
</style>

<style name="TextAppearance.App.Button" parent="TextAppearance.Material3.LabelLarge">
    <item name="fontFamily">@font/rubik</item>
    <item name="android:fontFamily">@font/rubik</item>
</style>

Use default style theme attributes, styles and theme overlays. This adds the theme to all buttons but does not affect other components:

<style name="Theme.App" parent="Theme.Material3.*">
    ...
    <item name="borderlessButtonStyle">@style/Widget.App.Button.TextButton</item>
    <item name="materialButtonOutlinedStyle">@style/Widget.App.Button.OutlinedButton</item>
    <item name="materialButtonStyle">@style/Widget.App.Button</item>
</style>

<style name="Widget.App.Button.TextButton" parent="Widget.Material3.Button.TextButton">
    <item name="materialThemeOverlay">@style/ThemeOverlay.App.Button.TextButton</item>
    <item name="android:textAppearance">@style/TextAppearance.App.Button</item>
    <item name="shapeAppearance">@style/ShapeAppearance.App.Button</item>
</style>

<style name="Widget.App.Button.OutlinedButton" parent="Widget.Material3.Button.OutlinedButton">
    <item name="materialThemeOverlay">@style/ThemeOverlay.App.Button.TextButton</item>
    <item name="android:textAppearance">@style/TextAppearance.App.Button</item>
    <item name="shapeAppearance">@style/ShapeAppearance.App.Button</item>
</style>

<style name="Widget.App.Button" parent="Widget.Material3.Button">
    <item name="materialThemeOverlay">@style/ThemeOverlay.App.Button</item>
    <item name="android:textAppearance">@style/TextAppearance.App.Button</item>
    <item name="shapeAppearance">@style/ShapeAppearance.App.Button</item>
</style>

<style name="ThemeOverlay.App.Button.TextButton" parent="ThemeOverlay.Material3.Button.TextButton">
    <item name="colorOnContainer">@color/shrine_pink_900</item>
</style>

<style name="ThemeOverlay.App.Button" parent="ThemeOverlay.Material3.Button">
    <item name="colorContainer">@color/shrine_pink_100</item>
    <item name="colorOnContainer">@color/shrine_pink_900</item>
</style>

<style name="ShapeAppearance.App.Button" parent="">
    <item name="cornerFamily">cut</item>
    <item name="cornerSize">4dp</item>
</style>

Use one of the styles in the layout. That will affect only this button:


<Button style="@style/Widget.App.Button".../>

Optical centering

Optical centering means to offset the MaterialButtons contents (icon and/or label) when the shape is asymmetric. Before optical centering, we only provided centering with horizontally asymmetrical shapes.

To turn on optical centering for a given button, use setOpticalCenterEnabled(true). Optical centering is disabled by default. When enabled, the shift amount of the icon and/or text is calculated as a value with the fixed ratio to the difference between left corner size in dp and right corner size in dp. The shift amount is applied to the padding start and padding end.