# ButtonGroup

A cluster of buttons that assistive technology announces as one thing before it reads them.

```slint
import { ButtonGroup, ButtonGroupColumn } from "@glint/components/button-group.slint";
import { Button, ButtonVariant, ButtonJoin } from "@glint/components/button.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 620px;
    height: 260px;
    background: Tokens.color-background;

    HorizontalLayout {
        padding: 24px;
        spacing: 48px;
        alignment: center;

        VerticalLayout {
            alignment: center;

            // The row: a cluster with no gap, each button keeping its corners.
            ButtonGroup {
                accessible-label: "Text style";
                Button { variant: ButtonVariant.outline; text: "Bold"; }
                Button { variant: ButtonVariant.outline; text: "Italic"; }
                Button { variant: ButtonVariant.outline; text: "Underline"; }
            }
        }

        VerticalLayout {
            alignment: center;

            // The column: welded, so the corners that meet are squared.
            ButtonGroupColumn {
                accessible-label: "Zoom";
                Button {
                    variant: ButtonVariant.outline;
                    text: "Zoom in";
                    join: ButtonJoin.first;
                }
                Button {
                    variant: ButtonVariant.outline;
                    text: "Reset";
                    join: ButtonJoin.middle;
                }
                Button {
                    variant: ButtonVariant.outline;
                    text: "Zoom out";
                    join: ButtonJoin.last;
                }
            }
        }
    }
}
```

## Usage

```slint
import { ButtonGroup } from "@glint/components/button-group.slint";
import { Button, ButtonVariant } from "@glint/components/button.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component AppWindow inherits Window {
    width: 420px;
    height: 140px;
    background: Tokens.color-background;

    VerticalLayout {
        padding: 24px;
        alignment: center;

        HorizontalLayout {
            alignment: center;

            ButtonGroup {
                accessible-label: "Alignment";
                Button { variant: ButtonVariant.outline; text: "Left"; }
                Button { variant: ButtonVariant.outline; text: "Center"; }
                Button { variant: ButtonVariant.outline; text: "Right"; }
            }
        }
    }
}
```

Drop two or more `Button`s into the group’s slot. The group is layout and a name: it lays them out in a single row with no gap between them, and publishes one node for the cluster so a screen reader says what the buttons belong to before it reads them. Naming it is the call site’s — `accessible-label`, the way every Glint control is named.

Each button keeps its own `variant` and its own rounded corners — the group is not a style override. Pair the cluster with `variant: ButtonVariant.outline` for the classic segmented look.

`ButtonGroup` is not a selection. It is a row of independent actions, each with its own `clicked`. Reach for [ToggleGroup](/docs/components/toggle-group) when one of the buttons has to stay pressed, and for [Tabs](/docs/components/tabs) when picking one switches what is on screen.

## Examples

### The stacked cluster, and the weld

`ButtonGroupColumn` is the same cluster standing on end. It is a component of its own rather than an `orientation` on `ButtonGroup`, because a `@children` slot is written once and never inside an `if` (ADR-0027) — a group cannot pick between two layouts at runtime, so the axis is structural. `Sheet` and `Drawer` make the same move for the same reason.

**The column is what welds.** Stacked buttons overlap by the hairline they share, so two bordered neighbours draw one line instead of two, and the corners where they meet are squared. Squaring them is each button’s own `join` — Slint gives a parent no way to reach into its `@children`, so the column cannot do it for you (the wall ADR-0015 and ADR-0021 hit). `ButtonJoin.first` squares the bottom corners, `last` the top ones, `middle` all four, and `none` — the default — squares nothing. The plain `ButtonGroup` row stays unwelded.

```slint
import { ButtonGroupColumn } from "@glint/components/button-group.slint";
import { Button, ButtonVariant, ButtonJoin } from "@glint/components/button.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 620px;
    height: 280px;
    background: Tokens.color-background;

    HorizontalLayout {
        padding: 24px;
        spacing: 64px;
        alignment: center;

        VerticalLayout {
            spacing: 8px;
            alignment: center;

            Text {
                text: "Welded";
                color: Tokens.color-muted-foreground;
                font-size: Tokens.typography-body-sm-size;
                horizontal-alignment: center;
            }
            ButtonGroupColumn {
                accessible-label: "Welded stack";
                Button { variant: ButtonVariant.outline; text: "One"; join: ButtonJoin.first; }
                Button { variant: ButtonVariant.outline; text: "Two"; join: ButtonJoin.middle; }
                Button { variant: ButtonVariant.outline; text: "Three"; join: ButtonJoin.last; }
            }
        }

        VerticalLayout {
            spacing: 8px;
            alignment: center;

            Text {
                text: "Every join left at none";
                color: Tokens.color-muted-foreground;
                font-size: Tokens.typography-body-sm-size;
                horizontal-alignment: center;
            }
            ButtonGroupColumn {
                accessible-label: "Unwelded stack";
                Button { variant: ButtonVariant.outline; text: "One"; join: ButtonJoin.none; }
                Button { variant: ButtonVariant.outline; text: "Two"; join: ButtonJoin.none; }
                Button { variant: ButtonVariant.outline; text: "Three"; join: ButtonJoin.none; }
            }
        }
    }
}
```

### A chunk of text in the cluster

`ButtonGroupText` is the piece of the cluster that is not a button — the `https://` ahead of a host field, the page count between two steppers. It wears the buttons’ chrome so the cluster reads as one, and nothing else: no press, no focus, no tab stop. It is announced as text, because a node that says “button” offers an action that does not exist.

```slint
import { ButtonGroup, ButtonGroupText } from "@glint/components/button-group.slint";
import { Button, ButtonVariant, ButtonSize } from "@glint/components/button.slint";
import { Tokens } from "@glint/theme/tokens.slint";
import { IconSet } from "@lucide";

export component Demo inherits Window {
    width: 620px;
    height: 200px;
    background: Tokens.color-background;

    in-out property <int> page: 3;

    VerticalLayout {
        padding: 24px;
        alignment: center;

        HorizontalLayout {
            alignment: center;

            ButtonGroup {
                accessible-label: "Page";
                Button {
                    variant: ButtonVariant.outline;
                    size: ButtonSize.icon;
                    leading-icon: IconSet.ChevronLeft;
                    accessible-label: "Previous page";
                    clicked => { root.page = max(1, root.page - 1); }
                }
                ButtonGroupText { text: root.page + " of 12"; }
                Button {
                    variant: ButtonVariant.outline;
                    size: ButtonSize.icon;
                    leading-icon: IconSet.ChevronRight;
                    accessible-label: "Next page";
                    clicked => { root.page = min(12, root.page + 1); }
                }
            }
        }
    }
}
```

It takes the same `join` its neighbours do, derived the same way, so a chunk welds into a stack exactly as a button does.

```slint
import { ButtonGroupColumn, ButtonGroupText } from "@glint/components/button-group.slint";
import { Button, ButtonVariant, ButtonJoin } from "@glint/components/button.slint";
import { Tokens } from "@glint/theme/tokens.slint";

export component Demo inherits Window {
    width: 620px;
    height: 240px;
    background: Tokens.color-background;

    VerticalLayout {
        padding: 24px;
        alignment: center;

        HorizontalLayout {
            alignment: center;

            ButtonGroupColumn {
                accessible-label: "Quantity";
                Button {
                    variant: ButtonVariant.outline;
                    text: "More";
                    join: ButtonJoin.first;
                }
                ButtonGroupText {
                    text: "12 in stock";
                    join: ButtonJoin.middle;
                }
                Button {
                    variant: ButtonVariant.outline;
                    text: "Fewer";
                    join: ButtonJoin.last;
                }
            }
        }
    }
}
```

A chunk stands a default-size button tall. Beside another `ButtonSize`, set `height:` on it at the call site — it is a plain Slint property, and a second size vocabulary here would be [Button](/docs/components/button)’s own cascade written twice.

## API Reference

`ButtonGroup` publishes no properties, callbacks or functions of its own. What a call site can set on it is the Slint `Rectangle` it inherits.

### ButtonGroupColumn

The stacked cluster, and the one that welds.

`ButtonGroupColumn` publishes no properties, callbacks or functions of its own. What a call site can set on it is the Slint `Rectangle` it inherits.

### ButtonGroupText

The non-interactive chunk inside a cluster.

### Properties

| Property | Type            | Default           | Description                                                                                                |
| -------- | --------------- | ----------------- | ---------------------------------------------------------------------------------------------------------- |
| `text`   | `in string`     | no default        | Text rendered inside the chunk.                                                                            |
| `join`   | `in ButtonJoin` | `ButtonJoin.none` | Where this chunk sits in a welded group — the same knob its neighbours take, so a stack of them welds too. |

### Enums

| Enum         | Values                            |
| ------------ | --------------------------------- |
| `ButtonJoin` | `none`, `first`, `middle`, `last` |

## Accessibility

- **One node for the cluster.** Both groups carry `accessible-role: groupbox`, so assistive technology announces what the buttons belong to before reading them. Slint 1.17 has no plain `group` role and `groupbox` is the nearest; [GroupBox](/docs/components/group-box) publishes the same role for a titled cluster.
- **The name is yours.** The groups declare the role and no label, so a call site’s own `accessible-label` lands on that node. A group with no label is a container a screen reader announces without saying what it holds.
- **The buttons speak for themselves.** Each keeps its own name, its own enabled state and its own tab stop — the cluster adds a node above them and takes nothing away. An icon-only button in a cluster still needs its `accessible-label`.
- **A chunk of text is text.** `ButtonGroupText` announces `accessible-role: text` with its own content as the name, the way [Kbd](/docs/components/kbd)’s cap does. It takes no focus and answers no activation, so nothing in the cluster invites a press that does nothing.
- **`join` is paint.** Squaring a corner changes no announcement: a welded button is announced exactly as a standalone one.
