# Split button

Package: @commenda/pathfinder@0.1.0
Page: /docs/components/split-button
Figma: https://www.figma.com/design/dn7BkGYEfIyP0o2zfesfVh/The-System?node-id=51-2

```tsx
import { SplitButton } from '@commenda/pathfinder/split-button';

<SplitButton
  label="Export entities"
  onClick={onExport}
  menuConfig={{ groups: exportGroups, label: 'More export options' }}
/>;
```


## Usage [#usage]

```tsx
import { SplitButton } from '@commenda/pathfinder/split-button';

<SplitButton
  label="Export entities"
  onClick={onExport}
  loadingConfig={{ isLoading: isExporting }}
  menuConfig={{
    label: 'More export options',
    groups: [
      {
        id: 'formats',
        items: [
          {
            id: 'csv',
            type: 'action',
            label: 'Export CSV',
            onClick: onExportCsv,
          },
          {
            id: 'pdf',
            type: 'action',
            label: 'Export PDF',
            onClick: onExportPdf,
          },
        ],
      },
    ],
  }}
/>;
```

## Installation [#installation]

```sh
pnpm --filter your-app add "@commenda/pathfinder@workspace:*"
```

The package currently lives in the Commenda workspace. External installation requires a published version or packed tarball. Import the precompiled stylesheet once; no application Tailwind setup is required.

## Examples [#examples]

### Secondary [#secondary]


```tsx
<SplitButton
  variant="secondary"
  label="Export entities"
  onClick={onExport}
  menuConfig={{ groups: exportGroups }}
/>;
```


### Loading the main action [#loading-the-main-action]


```tsx
<SplitButton
  label="Export entities"
  loadingConfig={{ isLoading: true }}
  menuConfig={{ groups: exportGroups }}
/>;
```


## API reference [#api-reference]

| Prop | Type | Requirement | Behavior |
| --- | --- | --- | --- |
| `className` | `string` | optional | Additional classes for the component root. |
| `size` | `"sm" \| "md" \| "lg"` | optional |  |
| `loadingConfig` | `LoadingConfig` | optional |  |
| `leadingIcon` | `ReactNode` | optional |  |
| `disabledReason` | `string` | optional |  |
| `open` | `boolean` | conditional |  |
| `onOpenChange` | `((open: boolean) => void)` | conditional |  |
| `defaultOpen` | `boolean` | optional |  |
| `label` | `string` | required | Visible field label. Supply an accessible name when it is omitted. |
| `variant` | `"primary" \| "secondary"` | optional |  |
| `completionConfig` | `CompletionConfig` | optional |  |
| `menuConfig` | `SplitButtonMenuConfig` | required |  |

### loadingConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `isLoading` | `boolean` | Yes |  |
| `label` | `string` | No |  |

### completionConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `isComplete` | `boolean` | Yes |  |
| `label` | `string` | Yes |  |
| `icon` | `ReactNode` | No |  |

### menuConfig fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `groups` | `DropdownGroup[]` | Yes |  |
| `label` | `string` | No |  |
| `disabled` | `boolean` | No |  |
| `disabledReason` | `string` | No |  |
| `side` | `Side` | No |  |
| `align` | `Align` | No |  |
| `sideOffset` | `number` | No |  |
| `portalContainer` | `HTMLElement \| ShadowRoot \| React.RefObject<HTMLElement \| ShadowRoot \| null> \| null` | No |  |

`SplitButtonMenuConfig`: required `groups: DropdownGroup[]`; optional `label`, `disabled`, `disabledReason`, `side`, `align`, `sideOffset`, `portalContainer`. Label defaults to `More options for ${label}`. Menu configuration disables only alternatives; component disabled disables both halves. There are no public Root/Trigger/Content parts or top-level menuLabel/menuDisabled props.

`SPLIT-ACTION-01`: Use one obvious default with two to five related alternatives. The main half acts immediately; the chevron opens the menu. Each is a separate keyboard stop with independent hover/focus. Loading blocks only the main action; disable the menu explicitly when alternatives conflict with ongoing work. Escape closes the menu and restores trigger focus. The menu trigger never submits a form.

No tertiary or destructive default. Destructive alternatives belong last in the menu and generally require confirmation. Without a default action use Dropdown with a normal Button. Do not put two SplitButtons beside each other in a row. Inner corners are square; outer corners follow Button radius. Recursive menus use the same model as Dropdown.

Import `@commenda/pathfinder/styles.css` once at the app root. Event handlers require a client component in Next.js App Router.

## Design rules [#design-rules]

Figma guidance and reviewed implementation decisions for this component. Rule IDs remain stable for source comments and AI references.

### Action rules [#action-rules]

- `CONTROL-STATE-01` — Pathfinder: loadingConfig and completionConfig are explicit application signals. Loading wins over completion; disabled blocks activation. Ending loading never implies success. Applications own the 1.5-second completion reset recipe from Figma.

- `CONTROL-SIZE-01` — Figma: Button and SplitButton sm/md/lg have 28/32/36px visual heights; md default. IconButton adds xs 24. Button icons are 16 through md and 20 at lg; pass size to generated icons to select optical artwork. Link styles have text-sized geometry. Long content can increase minimum height for accessibility.

- `CONTROL-FOCUS-01` — Figma: show focus from keyboard, not pointer click. Button family uses a blue ring separated by a white gap. Open triggers retain hover styling. Disabled reason is a Pathfinder extension: retain action name, expose description, and allow focus without activation where the native wrapper supports it.

- `SPLIT-ACTION-01` — Figma: one obvious default and two to five related alternatives; no tertiary/destructive default. Each half reacts independently. Main-only loading; trigger-only open styling. No default means a normal dropdown trigger. Avoid two SplitButtons side by side. Square inner corners, 1px join, rounded outer corners.
