Skip to content

Components

Button group

Groups related buttons by wrapping them with <yourElement class="button-group" role="group">.

  • Button groups should consist of 2-5 buttons.
  • Don't allow them to wrap onto a new line.
  • If an icon is used without label text make sure the button communicates clearly what it does.
Button group or Toggle button group?

If your buttons depend on state (controlled) - use Toggle button group.

If you just need to group a bunch of "dumb" (uncontrolled) buttons - use Button group.

Variants

html
<div role="group" class="button-group">
  <button class="button">Text</button>
  <button class="button">Text</button>
  <button class="button">Text</button>
</div>

With icons

html
<div role="group" class="button-group">
  <button class="button" aria-label="Label">
    <svg></svg>
  </button>
</div>

Size

html
<div role="group" class="button-group">
  <button class="button small">Small</button>
  <button class="button">Default</button>
  <button class="button large">Large</button>
</div>

Vertical orientation

You probably don't need that.

Vertical button groups are largely a legacy design pattern that can be better handled through:

  • Responsive design
  • Selects/Dropdown menus
  • More concise label writing
  • Other UI patterns that better match what you're actually trying to solve

Horizontal groups or alternative patterns altogether usually provide better UX.

Disabled

html
<div role="group" class="button-group">
  <button class="button">Enabled</button>
  <button class="button" disabled>Disabled</button>
  <button class="button">Enabled</button>
</div>

Anatomy

  1. Container: <element role="group" class="button-group">
  2. Buttons: Button

Button API

Button docs.

TypeModifiersDefaultDescription
Sizes.small, default, .large.mediumThe size of the element.
Variantsdefault, .outlined, .tonal, .filled, .elevateddefaultThe variant to use.

Installation

css
@layer components.has-deps {
  :where([role="group"].button-group) {
    --_border-radius: var(--button-border-radius);

    border-radius: var(--_border-radius);
    display: inline-flex;
    min-width: max-content;

    button {
      border-radius: 0;

      svg {
        max-inline-size: 0.7lh;
      }

      &:focus-visible {
        outline-offset: -4px;
      }

      & + & {
        border-inline-width: 1px;
        margin-inline-start: -1px;
      }

      &:first-of-type {
        border-bottom-left-radius: var(--_border-radius);
        border-top-left-radius: var(--_border-radius);
      }
      &:last-of-type {
        border-bottom-right-radius: var(--_border-radius);
        border-top-right-radius: var(--_border-radius);
      }

      /* Variants */
      /*** Text & Elevated */
      &:not(:where(.tonal, .filled, .outlined)) {
        & + button {
          border-inline-start: 1px solid var(--border-color);
        }
      }

      &:where(.tonal, .filled) {
        & + button {
          border-inline-start-color: var(--color-7);
        }
      }

      &:where(.tonal, .filled, .elevated) {
        & + &[disabled] {
          border-inline-start-color: color-mix(
            in oklch,
            var(--border-color) 90%,
            white
          );
        }
      }

      &:where(.elevated) {
        box-shadow: var(--shadow-1);

        button {
          &:not(:hover) {
            box-shadow: none;
          }
        }
      }
    }
  }
}