# Button

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

```tsx
'use client';

import { useState } from 'react';

import { Button } from '@commenda/pathfinder/button';

const ButtonBasic = () => {
  const [saved, setSaved] = useState(false);

  return (
    <Button
      completionConfig={{ isComplete: saved, label: 'Changes saved' }}
      onClick={() => {
        setSaved(true);
      }}
    >
      Save changes
    </Button>
  );
};

export default ButtonBasic;
```


## Usage [#usage]

```tsx
import { Button } from '@commenda/pathfinder/button';

<Button
  variant="primary"
  loadingConfig={{ isLoading: isSaving, label: 'Saving changes' }}
  completionConfig={{ isComplete: isSaved, label: 'Changes saved' }}
  disabled={!canSave}
  disabledReason="Administrator access required"
  onClick={onSave}
>
  Save changes
</Button>;
```

## 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]

### Variants [#variants]


```tsx
'use client';

import { Button } from '@commenda/pathfinder/button';

const ButtonVariants = () => (
  <>
    <Button variant="primary">View details</Button>
    <Button variant="secondary">View details</Button>
    <Button variant="tertiary">View details</Button>
    <Button variant="link">View details</Button>
    <Button variant="link-subtle">View details</Button>
  </>
);

export default ButtonVariants;
```


### Sizes and icons [#sizes-and-icons]


```tsx
'use client';

import { Button } from '@commenda/pathfinder/button';
import { PlusIcon } from '@commenda/pathfinder/icons/PlusIcon';

const ButtonSizes = () => (
  <>
    <Button size="sm" leadingIcon={<PlusIcon />}>
      Add entity
    </Button>
    <Button size="md" leadingIcon={<PlusIcon />}>
      Add entity
    </Button>
    <Button size="lg" leadingIcon={<PlusIcon />}>
      Add entity
    </Button>
  </>
);

export default ButtonSizes;
```


### Loading and completion [#loading-and-completion]


```tsx
'use client';

import { Button } from '@commenda/pathfinder/button';

const ButtonFeedback = () => (
  <>
    <Button loadingConfig={{ isLoading: true, label: 'Saving changes' }}>
      Save changes
    </Button>
    <Button completionConfig={{ isComplete: true, label: 'Changes saved' }}>
      Save changes
    </Button>
  </>
);

export default ButtonFeedback;
```


### Disabled reason [#disabled-reason]


```tsx
'use client';

import { Button } from '@commenda/pathfinder/button';

const ButtonDisabled = () => (
  <Button disabled disabledReason="Administrator access required">
    Save changes
  </Button>
);

export default ButtonDisabled;
```


## API reference [#api-reference]

| Prop | Type | Requirement | Behavior |
| --- | --- | --- | --- |
| `variant` | `"primary" \| "secondary" \| "tertiary" \| "link" \| "link-subtle"` | conditional | primary |
| `size` | `"sm" \| "md" \| "lg"` | optional | md; 28/32/36px minimum height |
| `destructive` | `boolean` | conditional | false; only primary/secondary/tertiary |
| `loadingConfig` | `LoadingConfig` | optional | Spinner with optional replacement label |
| `completionConfig` | `CompletionConfig` | optional | Explicit success, default check; only non-destructive primary/secondary/tertiary |
| `leadingIcon` | `ReactNode` | optional | Decorative icon slots, 16px at sm/md and 20px at lg |
| `trailingIcon` | `ReactNode` | optional | Decorative icon slots, 16px at sm/md and 20px at lg |
| `disabledReason` | `string` | optional | Prevent activation; explain unavailable actions |
| `className` | `string` | optional | Additional classes for the component root. |

### 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 |  |

`ButtonProps`, `ButtonBaseProps`, `LoadingConfig` and `CompletionConfig` are exported from root and `/button`.

## Feedback rules [#feedback-rules]

`CONTROL-STATE-01`: Loading ending does not mean success. Set `isComplete` only after application success. Loading wins if both flags are true. Disabled always blocks activation. Loading blocks repeat activation while retaining keyboard focus; the label and busy state remain accessible. Feedback replaces the normal leading icon; the trailing icon remains.

The application owns completion timing. Figma's recipe resets after 1.5 seconds; clear your timer on unmount and reset success before the next request. Errors belong in application feedback. No `status` or `onActionComplete` configuration prop exists.

## Keyboard and labels [#keyboard-and-labels]

Tab focuses the control; Enter/Space activates it. Disabled controls normally leave tab order. A disabled control with a reason remains focusable with `aria-disabled`, an accessible description and a tooltip; activation is blocked. Loading controls remain focusable. Preserve this behavior when composing custom elements.

`BUTTON-COPY-01`: Name the action and object in sentence case: “Export entities”, “Delete version”. Avoid “Submit” and “OK”. Icon-only actions use IconButton. Labels wrap at narrow widths and enlarged text sizes; minimum heights can grow to keep content readable. Unnamed/whitespace-only labels are invalid.

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

For navigation, use a real anchor or Next Link. Base UI Button composition can assign button semantics to an anchor; changing the rendered tag alone does not guarantee link semantics.

## 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]

- `BUTTON-COPY-01` — Figma: labels name the action and object in sentence case. Use one primary main action; secondary alongside it; tertiary for quiet repeated actions. Add icons only when they add meaning. Navigation uses a real link.

- `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.
