

## Overview [#overview]

The **Button** component provides a versatile and customizable button system with multiple variants, appearances, sizes, and shapes. It supports polymorphic rendering with the `render` prop (Base UI pattern), as well as special features like pending states and URL state management.

Use `Button.Wrapper` to visually attach adjacent buttons for split-button layouts while keeping each inner action as a real `Button`.

## Usage [#usage]

```tsx
import { Button } from "@tilt-legal/cubitt-components/primitives";
```

```tsx
<Button>Click me</Button>
```

<Accordions type="single">
  <Accordion title="URL State Management">
    Using Cubitt's URL-state hooks, you can sync button clicks with the URL by providing `paramName` and `paramSetValue`:

    ```tsx
    // Clicking sets ?action=save in the URL
    <Button paramName="action" paramSetValue="save">
      Save Draft
    </Button>

    // Advanced options:
    <Button
      paramName="action"
      paramSetValue="publish"
      paramDebounce={300}           // Debounce URL updates
    >
      Publish
    </Button>
    ```
  </Accordion>

  <Accordion title="Polymorphic Rendering">
    Use the `render` prop to render the button as a different element while preserving button styling:

    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";
    import { Link } from "@tanstack/react-router";

    <Button render={<Link href="/home" />}>Go Home</Button>;
    ```
  </Accordion>
</Accordions>

## Examples [#examples]

### Secondary [#secondary]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="SecondaryButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";

    export default function Component() {
      return <Button variant="secondary">Secondary</Button>;
    }
    ```
  </Tab>
</Tabs>

### Frost [#frost]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="FrostButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";

    export default function Component() {
      return <Button variant="frost">Frost</Button>;
    }
    ```
  </Tab>
</Tabs>

### Destructive [#destructive]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DestructiveButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";

    export default function Component() {
      return <Button variant="destructive">Destructive</Button>;
    }
    ```
  </Tab>
</Tabs>

### Outline [#outline]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="OutlineButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";

    export default function Component() {
      return <Button variant="outline">Outline</Button>;
    }
    ```
  </Tab>
</Tabs>

### Ghost [#ghost]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="GhostButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";

    export default function Component() {
      return <Button variant="ghost">Ghost</Button>;
    }
    ```
  </Tab>
</Tabs>

### With Icon [#with-icon]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="WithIconButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";
    import {
      UserPen } from "@tilt-legal/cubitt-icons/ui/outline";

    export default function Component() {
      return (
        <Button variant="secondary">
          <UserPen />
          With Icon
        </Button>
      );
    }
    ```
  </Tab>
</Tabs>

### Icon Only [#icon-only]

<Tabs
  items="['Preview',
'Code']"
>
  <Tab value="Preview">
    <Preview name="IconOnlyButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";
    import {
      UserPen } from "@tilt-legal/cubitt-icons/ui/outline";

    export default function Component() {
      return (
        <Button variant="secondary" mode="icon">
          <UserPen />
        </Button>
      );
    }
    ```
  </Tab>
</Tabs>

### Pending [#pending]

Shows a loading spinner while an action is in progress.

<Tabs
  items="['Preview',
'Code']"
>
  <Tab value="Preview">
    <Preview name="PendingButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    "use client";

    import { Button } from "@tilt-legal/cubitt-components/primitives";
    import { useEffect, useState } from "react";

    export default function Component() {
      const [isPending, setIsPending] = useState(false);

      useEffect(() => {
        if (!isPending) {
          return;
        }

        const timeout = window.setTimeout(() => {
          setIsPending(false);
        }, 2000);

        return () => window.clearTimeout(timeout);
      }, [isPending]);

      return (
        <Button
          variant="secondary"
          onClick={() => setIsPending(true)}
          pending={isPending}
        >
          Submit
        </Button>
      );
    }
    ```
  </Tab>
</Tabs>

### With Badge [#with-badge]

Combine buttons with badges for notifications and counters.

<Tabs
  items="['Preview',
'Code']"
>
  <Tab value="Preview">
    <Preview name="BadgeButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";
    import {
      Bell,
      Inbox,
      CircleCheck } from "@tilt-legal/cubitt-icons/ui/outline";

    export default function Component() {
      return (
        <div className="flex flex-col items-center gap-10">
          <Button variant="outline" mode="icon" className="relative">
            <Bell />
            <span className="border-2 border-background rounded-full size-3 bg-primary absolute -top-1 -end-1 animate-bounce" />
          </Button>

          <Button variant="outline" mode="icon" className="relative">
            <Inbox />
            <Badge
              variant="primary"
              shape="circle"
              size="sm"
              className="absolute top-0 start-full -translate-y-1/2 -translate-x-1/2 rtl:translate-x-1/2"
            >
              5
            </Badge>
          </Button>

          <Button variant="outline" className="relative">
            <Inbox />
            Messages
            <Badge
              variant="destructive"
              shape="circle"
              size="sm"
              className="absolute top-0 start-full -translate-y-1/2 -translate-x-1/2 rtl:translate-x-1/2"
            >
              5
            </Badge>
          </Button>

          <Button variant="outline">
            <CircleCheck />
            Notifications
            <Badge variant="brand" size="sm">
              10+
            </Badge>
          </Button>
        </div>
      );
    }
    ```
  </Tab>
</Tabs>

### Sizes [#sizes]

<Tabs
  items="['Preview',
'Code']"
>
  <Tab value="Preview">
    <Preview name="SizeButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";
    import {
      UserPen } from "@tilt-legal/cubitt-icons/ui/outline";

    export default function Component() {
      return (
        <div className="flex flex-col items-center justify-center gap-6">
          <div className="flex items-center gap-4">
            <Button variant="outline" size="sm">
              <UserPen />
              Small
            </Button>
            <Button variant="outline">
              <UserPen />
              Medium
            </Button>
            <Button variant="outline" size="lg">
              <UserPen />
              Large
            </Button>
          </div>
          <div className="flex items-center gap-4">
            <Button variant="outline" size="sm" mode="icon">
              <UserPen />
            </Button>
            <Button variant="outline" mode="icon">
              <UserPen />
            </Button>
            <Button variant="outline" size="lg" mode="icon">
              <UserPen />
            </Button>
          </div>
        </div>
      );
    }
    ```
  </Tab>
</Tabs>

### Link [#link]

Style buttons as links using `variant="link"`.

<Tabs
  items="['Preview',
'Code']"
>
  <Tab value="Preview">
    <Preview name="LinkButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";
    import { Link } from "@tanstack/react-router";

    export default function Component() {
      return (
        <div className="flex flex-col items-center gap-6">
          <Button variant="link" size="sm" render={<Link href="#" />}>
            Link Button
          </Button>
          <Button variant="link" render={<Link href="#" />}>
            Link Button
          </Button>
          <Button variant="link" size="lg" render={<Link href="#" />}>
            Link Button
          </Button>
        </div>
      );
    }
    ```
  </Tab>
</Tabs>

### Split Button [#split-button]

Use `Button.Wrapper` when you want a primary action and an adjacent secondary trigger to read as a single control.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="SplitButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    "use client";

    import {
      Button,
      Menu,
      MenuContent,
      MenuItem,
      MenuSeparator,
      MenuTrigger,
    } from "@tilt-legal/cubitt-components/primitives";
    import { useEffect, useState } from "react";

    export default function Component() {
      const PENDING_RESET_DELAY_MS = 2000;
      const variants = [
        { label: "Default", moreLabel: "More primary actions", variant: "default" },
        { label: "Secondary", moreLabel: "More secondary actions", variant: "secondary" },
        { label: "Destructive", moreLabel: "More destructive actions", variant: "destructive" },
        { label: "Frost", moreLabel: "More frost actions", variant: "frost" },
        { label: "Outline", moreLabel: "More outline actions", variant: "outline" },
      ] as const;

      function SplitButtonItem({
        label,
        moreLabel,
        variant,
      }: (typeof variants)[number]) {
        const [isPending, setIsPending] = useState(false);

        useEffect(() => {
          if (!isPending) {
            return;
          }

          const timeout = window.setTimeout(() => {
            setIsPending(false);
          }, PENDING_RESET_DELAY_MS);

          return () => window.clearTimeout(timeout);
        }, [isPending]);

        return (
          <Menu>
            <Button.Wrapper>
              <Button
                onClick={() => setIsPending(true)}
                pending={isPending}
                variant={variant}
              >
                {label}
              </Button>
              <Button
                aria-label={moreLabel}
                mode="icon"
                render={<MenuTrigger />}
                variant={variant}
              >
                <Button.Arrow className="me-0 ms-0" />
              </Button>
            </Button.Wrapper>
            <MenuContent align="end">
              <MenuItem>Duplicate</MenuItem>
              <MenuItem>Move</MenuItem>
              <MenuSeparator />
              <MenuItem variant="destructive">Archive</MenuItem>
            </MenuContent>
          </Menu>
        );
      }

      return (
        <div className="flex flex-wrap items-center gap-4">
          {variants.map((item) => (
            <SplitButtonItem key={item.variant} {...item} />
          ))}
        </div>
      );
    }
    ```
  </Tab>
</Tabs>

### Disabled [#disabled]

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="DisabledButtonExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";

    export default function Component() {
      return (
        <div className="flex items-center gap-4">
          <Button disabled>Primary</Button>
          <Button variant="secondary" disabled>
            Secondary
          </Button>
          <Button variant="outline" disabled>
            Outline
          </Button>
        </div>
      );
    }
    ```
  </Tab>
</Tabs>

### URL State [#url-state]

Sync button clicks with URL parameters. Click a button to see the URL update with the corresponding action.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="ButtonURLStateExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { Button } from "@tilt-legal/cubitt-components/primitives";

    export default function Component() {
      return (
        <div className="flex items-center gap-4">
          <Button paramName="demo-action" paramSetValue="save" variant="secondary">
            Save Draft
          </Button>
          <Button paramName="demo-action" paramSetValue="publish">
            Publish
          </Button>
          <Button
            paramName="demo-action"
            paramSetValue="archive"
            variant="destructive"
          >
            Archive
          </Button>
        </div>
      );
    }
    ```
  </Tab>
</Tabs>

## API Reference [#api-reference]

### Button [#button]

The root component for creating buttons with multiple variants, appearances, sizes, and shapes. Supports polymorphic rendering via the `render` prop and URL state management.

<TypeTable
  type="{
  variant: {
    description: &#x22;Visual style variant of the button.&#x22;,
    type: '&#x22;default&#x22; | &#x22;secondary&#x22; | &#x22;mono&#x22; | &#x22;inverse&#x22; | &#x22;destructive&#x22; | &#x22;frost&#x22; | &#x22;outline&#x22; | &#x22;ghost&#x22; | &#x22;link&#x22;',
    default: '&#x22;default&#x22;',
  },
  size: {
    description: &#x22;Size of the button.&#x22;,
    type: '&#x22;sm&#x22; | &#x22;md&#x22; | &#x22;lg&#x22;',
    default: '&#x22;md&#x22;',
  },
  mode: {
    description: &#x22;Layout mode. Use 'icon' for square icon-only buttons, 'input' for form trigger styling.&#x22;,
    type: '&#x22;icon&#x22; | &#x22;input&#x22;',
  },
  unstyled: {
    description: &#x22;Render as a completely unstyled button. Bypasses all variant styling.&#x22;,
    type: &#x22;boolean&#x22;,
    default: &#x22;false&#x22;,
  },
  pending: {
    description:
      &#x22;Show a loading spinner. Leading icons swap immediately; other layouts keep the animated inline indicator.&#x22;,
    type: &#x22;boolean&#x22;,
    default: &#x22;false&#x22;,
  },
  selected: {
    description: &#x22;Mark button as selected (for toggle buttons).&#x22;,
    type: &#x22;boolean&#x22;,
    default: &#x22;false&#x22;,
  },
  disabled: {
    description: &#x22;Disable the button.&#x22;,
    type: &#x22;boolean&#x22;,
    default: &#x22;false&#x22;,
  },
  reduceMotion: {
    description: &#x22;Disable animations for this button.&#x22;,
    type: &#x22;boolean&#x22;,
    default: &#x22;false&#x22;,
  },
  render: {
    description:
      &#x22;Polymorphic rendering - render button as another element (e.g., Link).&#x22;,
    type: &#x22;React.ReactElement | function&#x22;,
  },
  className: {
    description: &#x22;Additional CSS classes for the button.&#x22;,
    type: &#x22;string&#x22;,
  },
}"
/>

#### URL State Props [#url-state-props]

When provided with a `paramName`, the button will set URL parameters when clicked.

<TypeTable
  type="{
  paramName: {
    description: &#x22;URL parameter name. Enables URL state management.&#x22;,
    type: &#x22;string&#x22;,
  },
  paramSetValue: {
    description: &#x22;Value to set in URL when button is clicked.&#x22;,
    type: &#x22;string&#x22;,
  },
  paramThrottle: {
    description: &#x22;Throttle URL updates in milliseconds.&#x22;,
    type: &#x22;number&#x22;,
  },
  paramDebounce: {
    description: &#x22;Debounce URL updates in milliseconds.&#x22;,
    type: &#x22;number&#x22;,
  },
}"
/>
