# Button group

Several buttons acting as one control: spaced apart, or segmented into one bar with dividers.

Block: `smbk-btn-group`. Status: candidate. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|
| `type` | `default`, `segmented` | `segmented` | Default keeps buttons apart. Segmented fuses them into one bar with hairline dividers. |
| `tone` | `brand`, `secondary`, `outline` | `secondary` | Which action family the fused bar draws its dividers and border from. Buttons inside an outline bar use the ghost tone. |
| `size` | `lg`, `md`, `sm` | `md` | Matches the size of the buttons inside, md like the button; sets the gap and the bar radius. |
| `orientation` | `horizontal`, `vertical` | `horizontal` |  |



## Markup

```html
<div
  role="group"
  aria-label="Range"
  class="smbk-btn-group smbk-btn-group--type-segmented smbk-btn-group--tone-secondary smbk-btn-group--size-md smbk-btn-group--orientation-horizontal"
>
  <button type="button" class="smbk-btn smbk-btn--tone-secondary smbk-btn--size-md">
    <span class="smbk-btn__label">Day</span>
  </button>
  <button type="button" class="smbk-btn smbk-btn--tone-secondary smbk-btn--size-md">
    <span class="smbk-btn__label">Week</span>
  </button>
  <button type="button" class="smbk-btn smbk-btn--tone-secondary smbk-btn--size-md">
    <span class="smbk-btn__label">Month</span>
  </button>
</div>
```

## React

```tsx
import { ButtonGroup } from './design-system/components/button-group/button-group.js';
import { Button } from './design-system/components/button/button.js';

<ButtonGroup type="segmented" tone="secondary" size="md" orientation="horizontal" label="Range">
  <Button tone="secondary" size="md">Day</Button>
  <Button tone="secondary" size="md">Week</Button>
  <Button tone="secondary" size="md">Month</Button>
</ButtonGroup>
```

## Stylesheet

```css
/* smbk-btn-group — Button group. Figma "ButtonGroup" 4163:5325.
 * Children are smbk-btn elements. The default type keeps them apart and, in a
 * row, wraps onto further lines when it runs out of room. The segmented type
 * fuses them into one bar on one line: inner corners are squared, a hairline
 * divider separates neighbors, and an outline group carries the border itself
 * with ghost buttons inside. */

.smbk-btn-group {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  max-width: 100%;
  gap: var(--smbk-space-stack-xs) var(--smbk-space-inline-md);
}

.smbk-btn-group--size-lg {
  column-gap: var(--smbk-space-inline-lg);
}

.smbk-btn-group--orientation-vertical {
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: stretch;
  gap: var(--smbk-space-stack-sm);
}

.smbk-btn-group--orientation-vertical.smbk-btn-group--size-lg {
  gap: var(--smbk-space-stack-md);
}

/* ---- segmented: one bar ---- */

.smbk-btn-group--type-segmented,
.smbk-btn-group--type-segmented.smbk-btn-group--size-lg {
  flex-wrap: nowrap;
  max-width: none;
  gap: 0;
  border-radius: var(--smbk-radius-control-md);
}

/* A focused button sits above its neighbors, so the fused edge never covers its ring. */
.smbk-btn-group--type-segmented > .smbk-btn:focus-visible,
.smbk-btn-group--type-segmented > .smbk-dropdown__anchor > .smbk-btn:focus-visible {
  position: relative;
  z-index: 1;
}

.smbk-btn-group--type-segmented.smbk-btn-group--size-md,
.smbk-btn-group--type-segmented.smbk-btn-group--size-sm {
  border-radius: var(--smbk-radius-control-sm);
}

.smbk-btn-group--type-segmented > .smbk-btn {
  border-radius: 0;
}

.smbk-btn-group--type-segmented > .smbk-btn:first-child {
  border-start-start-radius: inherit;
  border-end-start-radius: inherit;
}

.smbk-btn-group--type-segmented > .smbk-btn:last-child {
  border-start-end-radius: inherit;
  border-end-end-radius: inherit;
}

.smbk-btn-group--orientation-vertical.smbk-btn-group--type-segmented > .smbk-btn:first-child {
  border-radius: 0;
  border-start-start-radius: inherit;
  border-start-end-radius: inherit;
}

.smbk-btn-group--orientation-vertical.smbk-btn-group--type-segmented > .smbk-btn:last-child {
  border-radius: 0;
  border-end-start-radius: inherit;
  border-end-end-radius: inherit;
}

/* Divider between neighbors: the strong border on secondary bars, the subtle border on outline bars, the brand action's own edge on the brand bar. */
.smbk-btn-group--type-segmented > .smbk-btn + .smbk-btn {
  border-inline-start: var(--smbk-border-width-hairline) solid var(--smbk-color-border-strong);
}

.smbk-btn-group--orientation-vertical.smbk-btn-group--type-segmented > .smbk-btn + .smbk-btn {
  border-inline-start: 0;
  border-block-start: var(--smbk-border-width-hairline) solid var(--smbk-color-border-strong);
}

.smbk-btn-group--tone-outline.smbk-btn-group--type-segmented > .smbk-btn + .smbk-btn {
  border-inline-start-color: var(--smbk-color-border-subtle);
}

.smbk-btn-group--tone-outline.smbk-btn-group--orientation-vertical.smbk-btn-group--type-segmented > .smbk-btn + .smbk-btn {
  border-block-start-color: var(--smbk-color-border-subtle);
}

.smbk-btn-group--tone-brand.smbk-btn-group--type-segmented > .smbk-btn + .smbk-btn {
  border-inline-start-color: var(--smbk-color-action-brand-border);
}

.smbk-btn-group--tone-brand.smbk-btn-group--orientation-vertical.smbk-btn-group--type-segmented > .smbk-btn + .smbk-btn {
  border-block-start-color: var(--smbk-color-action-brand-border);
}

/* The outline bar draws the border itself; buttons inside are ghost, or outline buttons that give up their own edge. */
.smbk-btn-group--tone-outline.smbk-btn-group--type-segmented > .smbk-btn--tone-outline {
  border-color: transparent;
}

.smbk-btn-group--tone-outline.smbk-btn-group--type-segmented > .smbk-btn--tone-outline + .smbk-btn--tone-outline {
  border-inline-start-color: var(--smbk-color-border-subtle);
}

.smbk-btn-group--tone-outline.smbk-btn-group--orientation-vertical.smbk-btn-group--type-segmented > .smbk-btn--tone-outline + .smbk-btn--tone-outline {
  border-inline-start-color: transparent;
  border-block-start-color: var(--smbk-color-border-subtle);
}

/* The bar's edge is drawn inside it, over its buttons' own transparent edges, so it adds nothing: the bar is
   as tall as its buttons, the control height of its size. An outline is painted above the buttons, so a
   pressed or hovered button's fill never covers it. */
.smbk-btn-group--tone-outline.smbk-btn-group--type-segmented {
  outline: var(--smbk-border-width-hairline) solid var(--smbk-color-action-outline-border);
  outline-offset: calc(-1 * var(--smbk-border-width-hairline));
}

/* ---- a button that opens a dropdown ----
   The dropdown's anchor holds its opener, so the opener is not a child of the bar. In a merged or
   segmented row the anchor passes the bar's corners on to its button, and the button draws the same
   divider and edge as a button standing in the bar. */

.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-dropdown__anchor {
  border-radius: 0;
}

.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-dropdown__anchor:first-child {
  border-start-start-radius: inherit;
  border-end-start-radius: inherit;
}

.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-dropdown__anchor:last-child {
  border-start-end-radius: inherit;
  border-end-end-radius: inherit;
}

.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-dropdown__anchor > .smbk-btn {
  border-radius: inherit;
}

.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-btn + .smbk-dropdown__anchor > .smbk-btn {
  border-inline-start: var(--smbk-border-width-hairline) solid var(--smbk-color-border-strong);
}

.smbk-btn-group--tone-outline.smbk-btn-group--type-segmented > .smbk-dropdown__anchor > .smbk-btn--tone-outline {
  border-color: transparent;
}

.smbk-btn-group--tone-outline.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-btn + .smbk-dropdown__anchor > .smbk-btn {
  border-inline-start-color: var(--smbk-color-border-subtle);
}

.smbk-btn-group--tone-brand.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > .smbk-btn + .smbk-dropdown__anchor > .smbk-btn {
  border-inline-start-color: var(--smbk-color-action-brand-border);
}

/* ---- touch: the buttons stop their press areas halfway to each other ----
   Every button takes an invisible press area on a touch screen. In a group, whose buttons stand closer than that and
   wrap, each area reaches only halfway into the group's gap, side to side and up and down; in a column, only
   halfway to the button above or below. In a segmented bar the buttons touch, so between two of them each keeps its
   own edge. Nothing drawn moves. */

@media (pointer: coarse) {
  .smbk-btn-group .smbk-btn::after {
    inset-inline: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-md) / -2);
    inset-block: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-xs) / -2);
  }

  .smbk-btn-group--size-lg .smbk-btn::after {
    inset-inline: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-lg) / -2);
  }

  .smbk-btn-group--orientation-vertical .smbk-btn::after {
    inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
  }

  .smbk-btn-group--orientation-vertical > * + * .smbk-btn::after,
  .smbk-btn-group--orientation-vertical > .smbk-btn + .smbk-btn::after {
    inset-block-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-sm) / -2);
  }

  .smbk-btn-group--orientation-vertical > *:has(+ *) .smbk-btn::after,
  .smbk-btn-group--orientation-vertical > .smbk-btn:has(+ *)::after {
    inset-block-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-sm) / -2);
  }

  .smbk-btn-group--orientation-vertical.smbk-btn-group--size-lg > * + * .smbk-btn::after,
  .smbk-btn-group--orientation-vertical.smbk-btn-group--size-lg > .smbk-btn + .smbk-btn::after {
    inset-block-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-md) / -2);
  }

  .smbk-btn-group--orientation-vertical.smbk-btn-group--size-lg > *:has(+ *) .smbk-btn::after,
  .smbk-btn-group--orientation-vertical.smbk-btn-group--size-lg > .smbk-btn:has(+ *)::after {
    inset-block-end: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-md) / -2);
  }

  .smbk-btn-group.smbk-btn-group--type-segmented .smbk-btn::after {
    inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
  }

  .smbk-btn-group.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > :not(:first-child)::after,
  .smbk-btn-group.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > :not(:first-child) > .smbk-btn::after {
    inset-inline-start: 0;
  }

  .smbk-btn-group.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > :not(:last-child)::after,
  .smbk-btn-group.smbk-btn-group--type-segmented:not(.smbk-btn-group--orientation-vertical) > :not(:last-child) > .smbk-btn::after {
    inset-inline-end: 0;
  }

  .smbk-btn-group.smbk-btn-group--type-segmented.smbk-btn-group--orientation-vertical > :not(:first-child)::after,
  .smbk-btn-group.smbk-btn-group--type-segmented.smbk-btn-group--orientation-vertical > :not(:first-child) > .smbk-btn::after {
    inset-block-start: 0;
  }

  .smbk-btn-group.smbk-btn-group--type-segmented.smbk-btn-group--orientation-vertical > :not(:last-child)::after,
  .smbk-btn-group.smbk-btn-group--type-segmented.smbk-btn-group--orientation-vertical > :not(:last-child) > .smbk-btn::after {
    inset-block-end: 0;
  }
}
```

## Tokens it reads

```json
{
  "space": {
    "stack": {
      "xs": {
        "$type": "dimension",
        "$value": "{{space.4}}",
        "$description": "Gap between things stacked one above another, extra small."
      },
      "sm": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Gap between things stacked one above another, small."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Gap between things stacked one above another, medium."
      }
    },
    "inline": {
      "md": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Gap between things side by side, medium."
      },
      "lg": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Gap between things side by side, large."
      }
    }
  },
  "radius": {
    "control": {
      "md": {
        "$type": "dimension",
        "$value": "{{radius.6}}",
        "$description": "Corners of controls, medium."
      },
      "sm": {
        "$type": "dimension",
        "$value": "{{radius.4}}",
        "$description": "Corners of controls, small."
      }
    }
  },
  "border-width": {
    "hairline": {
      "$type": "dimension",
      "$value": "{{border-width.1}}",
      "$description": "Thin lines: dividers and almost every edge."
    }
  },
  "color": {
    "border": {
      "strong": {
        "$type": "color",
        "$value": "{{color.grey.400}}",
        "$description": "Stronger edges and separators."
      },
      "subtle": {
        "$type": "color",
        "$value": "{{color.grey.300}}",
        "$description": "Dividers and quiet edges."
      }
    },
    "action": {
      "brand": {
        "border": {
          "$type": "color",
          "$value": "{{color.alpha.white.10}}",
          "$description": "Brand button and chip: edge at rest."
        }
      },
      "outline": {
        "border": {
          "$type": "color",
          "$value": "{{color.grey.300}}",
          "$description": "Outline button: edge at rest."
        }
      }
    }
  },
  "size": {
    "target": {
      "comfortable": {
        "$type": "dimension",
        "$value": "{{size.44}}",
        "$description": "Comfortable touch target (44)."
      }
    }
  }
}
```

## Purpose

Several buttons that belong together, presented as one control. Kept apart, they are simply a row with the right gap that wraps when it runs out of room. Segmented, they fuse into one bar with shared edges and hairline dividers, which is how a set of mutually exclusive choices reads as a single thing.

## Anatomy

The block is the container. Its children are buttons, unchanged; the group squares their inner corners, rounds the bar's outer corners, and draws the divider between neighbors. The outline tone draws the bar's border itself, so the buttons inside use the ghost tone.

A button that opens more options is a dropdown whose opener is that button, placed in the group where the button would stand. The dropdown holds its opener and its panel together, and the group passes its corners, its divider and its edge on to the opener, so it looks like any other button in the bar while the dropdown does the menu's keyboard and focus work.

## When to use

- A small set of exclusive choices where a segmented control would be too small.
- A row of actions that must stay together on a narrow page: the default type wraps onto a second line rather than running off the edge.
- Two or three related actions of equal weight, kept apart with the default type.

## When not to use

- For a main action with a menu of related ones. Use the [split button](split-button.md).
- For a single toggle between views. Use the [segmented control](segmented-control.md).
- For unrelated actions that happen to sit side by side. Give them their own spacing.

## Behavior

None of its own. Each button behaves as a button. The group is a layout and a shared edge.

The default type, in a row, never grows wider than the space it is given: when its buttons do not fit on one line, the next ones wrap onto a second line, with a small gap between the lines. A segmented bar and a vertical group stay as they are.

## Accessibility

### What it does for you

- The container carries the group role, and the wrapper requires an accessible name for it.
- Focus moves through the buttons in order.
- Because the fused bar hides the buttons' own borders, focus rings are what separates the focused button from its neighbors. They are never suppressed, and a focused button sits above its neighbors so the shared edge never covers its ring.

### What you must do

- Give the group an accessible name that says what the group is for.

### How to test

- Tab through the group: focus moves from button to button in order, and each focus ring shows whole, in both themes and in a forced-color mode.
- Press Enter, then Space on a button: each runs its action once.
- With a screen reader, the group is read by its name, and each button by its label.
- With text at 200% and the page 320 pixels wide, the labels stay whole.

### Keyboard

| Key | What it does |
|---|---|
| Tab | Moves focus to the next button in the group. |
| Shift + Tab | Moves focus to the previous button. |
| Enter or Space | Presses the focused button. |

## Usage rules

### Do

- Give every button in a fused bar the same size as the group.
- Use ghost buttons inside an outline group; the group draws the border.

### Don't

- Mix tones inside a segmented bar. The dividers are tuned to one fill.
- Build a main action with a menu from a segmented bar and a dropdown. Use the split button.
- Put a link styled as a button next to real buttons in a fused bar without checking focus order.
