{
  "name": "ButtonGroup",
  "type": "component",
  "description": "A related set of buttons sharing one tab stop, with arrow keys moving between them.",
  "export": "ButtonGroup",
  "path": "@gtmi/strix-react/molecules/button-group",
  "parts": ["ButtonGroup", "ButtonGroupItem", "ButtonGroupRow", "ButtonGroupSeparator", "ButtonGroupText"],
  "props": [
    {
      "name": "children",
      "type": "ReactNode",
      "required": true,
      "description": "ButtonGroupItem elements. A bare Button would render correctly but keep its own tab stop."
    },
    {
      "name": "orientation",
      "type": "horizontal | vertical",
      "default": "horizontal",
      "description": "Layout direction. Also sets aria-orientation and decides which arrow keys move between items."
    },
    {
      "name": "disabled",
      "type": "boolean",
      "default": "false",
      "description": "Makes every item aria-disabled. Individual items can be disabled on their own."
    },
    {
      "name": "aria-label",
      "type": "string",
      "description": "Required in practice: an unnamed role=toolbar announces as an unlabelled group. aria-labelledby works too."
    }
  ],
  "ai_tags": ["button", "group", "toolbar", "actions", "primitive"],
  "since": "0.2.0",
  "tokens_used": ["--spacing-gap-250"],
  "notes": "Ported from the Figma atom `.button-group` (scripts/figma/bindings/button-group.json), which is a layout and nothing else: two or three `.button-main`s at a 12px gap, horizontal or vertical, with no colour, shape or state of its own. Figma's `Horizontal-3buttons` is not a third arrangement, just one more child, and its `Show RTL` boolean is not a prop -- direction is a document concern, and Base UI has a DirectionProvider for it. ON THAT EVIDENCE ALONE THIS WOULD NOT BE A COMPONENT, and the first read of the atom concluded exactly that: a flex wrapper with a gap buys nothing. Base UI's Toolbar changes the arithmetic, because it implements the WAI toolbar pattern, which a div cannot. Verified against Base UI 1.6.0 by probing the DOM rather than trusting the docs: the root is <div role=\"toolbar\" aria-orientation>, items get a roving tabindex (0 on the first, -1 on the rest), ArrowRight and ArrowLeft move between them, movement LOOPS, and Tab leaves the group instead of walking into the second button. A row of six actions is one stop on the way through a form, not six. Home and End do NOT jump to the ends. Recorded as a characterised gap rather than patched with a keydown handler that would fight the next upgrade -- the same conclusion this project reached about Base UI's Accordion having no arrow-key navigation between headers. DISABLED IS aria-disabled, NOT THE NATIVE ATTRIBUTE, and that is Base UI's choice. It is the WAI behaviour: a disabled item stays reachable so a keyboard user can discover the action exists and is unavailable, which means a disabled FIRST item still holds the group's tab stop. Activation is blocked, so it is genuinely inert. Verified that the native attribute is never set -- not by passing `disabled` straight to the rendered Button, and not with focusableWhenDisabled={false}. This exposed a defect in `Button` rather than in this wrapper, now fixed there: it stated each variant's disabled ground and ink through `disabled:` variants that a missing native attribute never fires, so a disabled item was inert and correctly announced but did not look disabled -- and `Button` through its own render seam had the same gap, where a disabled Button-as-link looked enabled and stayed clickable. Every disabled treatment is now emitted for both `disabled:` and `aria-disabled:`, with activation blocked on the aria path. Figma's mixed type ramps are not copied. Its first button is 16/24 and its second 14/20, which is one composition being loose rather than a rule -- `Button` has one ramp per size, and emphasis is expressed by variant. Mutation-tested four ways: replacing Toolbar.Root with a div (14 failures), replacing Toolbar.Button with a bare Button (6), the wrong gap token, and giving the group a surface of its own. A test asserts the group emits no ink, surface or border token at all, derived from its cva output, which is why it needs no contrast test.\n\nMOVED TO src/molecules IN 0.5.0, because Figma files .button-group (22:5351) under Molecules and the dot-prefix proxy had filed it as an atom -- one of exactly two places that proxy was wrong.\n\nALL THREE FIGMA STYLES ARE NOW EXPRESSIBLE, and Horizontal-3buttons is the one that needed a new part. The binding called it 'a child count, not a variant'; Dev Mode shows a COLUMN OF TWO ROWS -- a primary and a secondary pill side by side at 12px, with the link button on its own row beneath. ButtonGroupRow wraps Base UI's Toolbar.Group for it, verified present in 1.6.0. A nested ButtonGroup would give the row its own toolbar and its own tab stop, defeating the one behaviour this component exists for; a bare div would render correctly while dropping out of the composition assistive technology reports. Three tests cover it: one toolbar and one tab stop, arrows crossing the row boundary, and the row's own axis and gap.\n\nTHE mixedTypeRamps DECLINE IS REVERSED. It read: 'Figma's first label is 16/24 and its second 14/20; Button has one ramp per size and expresses emphasis by variant.' The 14/20 item is not the same variant at another ramp -- it is .button-main in its LINK type, which Button already ships. Figma draws two VARIANTS; reading them as two ramps of one variant is what made it look like a conflict.\n\nButtonGroupRow is not in shadcn's part set (ButtonGroup, ButtonGroupSeparator, ButtonGroupText), so it is a Figma-driven addition rather than a conformance fix."
}
