Components · Actions
Button group
BetaGroups related buttons, such as Previous and Next week.
- Source
components/ui/button-group.tsx - shadcn/ui Button group
- Figma: not linked yet (Q13)
When to use
- To join two to four related actions that act on the same thing: Previous week and Next week, Edit and Duplicate on a program.
- For a split button: a main action with a menu of other ways to do it ("Send check-in" plus "Schedule for later").
- To attach a button to a field, such as a search box with its search button.
When not to use
- To choose between options that stay selected (kg or lb, list or cards): use a Toggle group. A button group only starts actions.
- For more than four actions, or actions of mixed importance: show the primary Button and put the rest in a Dropdown menu.
- For a form's Save and Cancel: those are separate buttons with a gap, not a group, so the primary action stands out.
Anatomy
- 1. Group: a row (or column) with role="group"; inner corners are squared and shared borders collapse
- 2. Button: any Button variant, usually outline
- 3. Text segment (optional): ButtonGroupText, a non-interactive label such as the current week
Variants
Horizontal
Vertical
Split button, with separator
With an input
| Variant | Use for |
|---|---|
horizontal (default) | Most groups: week navigation, program actions in a card header. |
vertical | Narrow columns, such as moving an exercise up or down in a workout. |
With ButtonGroupSeparator | Filled buttons (secondary, default) that have no border of their own, so the split shows. Outline buttons don't need it. |
| With an input | A field and its action. The input grows to fill the group. |
The group has no sizes of its own: give every button in it the same size. Nest groups (a ButtonGroup inside a ButtonGroup) to put a gap between sets of actions in one toolbar.
States
States belong to each button; the group only makes sure the focused one is drawn on top of its neighbors.
| State | What changes | Token |
|---|---|---|
| Hover | The hovered button's own hover fill | hover:bg-accent (outline) |
| Focus-visible | The focused button is raised so its ring isn't hidden by the next button | z-(--z-raised), ring-ring/50 |
| Disabled | Only that button: half opacity, no pointer events. Keep it in place so the group doesn't jump | opacity-(--opacity-disabled) |
| Text segment | Muted fill, so it doesn't look clickable | bg-muted |
Usage guidelines
- Keep a destructive action out of the group, or behind a confirmation in a menu.
- Disable a button that can't act right now (Next week on the last week) instead of removing it.
- Give the group an
aria-labelwhen its purpose isn't clear from the buttons alone.
Content guidelines
- Short labels, verb first, sentence case: "Edit", "Duplicate", "Previous week".
- Icon-only buttons are fine when the icons are well known (arrows, trash), but each needs an
aria-label. - A text segment states where you are, with Western digits (Q22): "Week 4 of 12", "الأسبوع 4 من 12".
Accessibility
| Key | Does |
|---|---|
| Tab / Shift+Tab | Moves to the next or previous button; each button is its own tab stop |
| Enter or Space | Activates the focused button |
- The group renders
role="group". With anaria-label, a screen reader announces it on entry: "Program week, group, Previous week, button". ButtonGroupTextis plain text, not focusable, and is read in order between the buttons.- Arrow keys don't move between buttons. For a set of options with arrow-key navigation, use a Toggle group.
- Coach primary buttons fail contrast today (DECISIONS #3).
RTL
- The group's order follows the reading direction: "Previous week" is at the right in RTL. The squared and rounded corners use logical classes (
rounded-s-none,rounded-e-none,border-s-0), so the outer corners stay rounded in both directions. - Directional icons in the buttons need
rtl:-scale-x-100, as in the example: the "previous" chevron points right in RTL. A trash or copy icon doesn't mirror. - Vertical groups are the same in both directions.
Tokens used
Read from components/ui/button-group.tsx at build time, so this list changes when the component does. Opacity modifiers such as /90 are shadcn hover and focus tints on top of the token.
| Token | Utilities | SuperCoach | Coach |
|---|---|---|---|
| --input | bg-input | neutral-200 | neutral-200 |
| --muted | bg-muted | neutral-100 | neutral-100 |
- Layer
z-(--z-raised) - Radius
rounded-e-md rounded-md - Radius
rounded-s-none rounded-e-none rounded-t-none rounded-b-none - Shadow
shadow-xs - Type
font-medium - Type
text-sm
Props
<ButtonGroup>
Also takes every prop of <div>
| Prop | Type | Default |
|---|---|---|
| orientation | "horizontal" | "vertical" | "horizontal" |
<ButtonGroupText>
Also takes every prop of <div>
| Prop | Type | Default |
|---|---|---|
| asChild? | boolean | false |
<ButtonGroupSeparator>
Also takes every prop of <Separator>
No props of its own.
Replaces in coach-admin
From the read-only audit of coach-admin: 4 style values, 4 uses.
coach-admin has no shared button-group component. Buttons are placed side by side in one-off containers such as .selectWeekBtnContainer (the program week switcher), which also needs a separate margin fix in rtl.css. The logical classes here remove that fix.
Classes and selectors
.formBtnsContainer2.selectWeekBtnContainer2