# Button
URL: https://remocn.dev/docs/ui/components/button

A button whose visual state is a pure function of the timeline

- Install: `npx shadcn@latest add @remocn/button`
- Vibe: clean
- Natural length: 120f @ 30fps

A state atom whose appearance (idle, hover, press, loading, success) is a pure function of the `state` prop. Pass a state directly or drive it from the Remotion timeline via `useCurrentState`. The component reads no frame — only the state value you give it.

## Installation

```bash
npx shadcn@latest add @remocn/button
```

> **Note**
>
> Installing `button` automatically installs `spinner` and the shared `remocn-ui` core (`lib/remocn-ui/`) via `registryDependencies`. You do not need to install either separately.

## States

`ButtonState` is:

```ts
type ButtonState =
  | "idle"     // resting
  | "hover"    // lifted + background shift
  | "press"    // scale down + darken
  | "loading"  // renders <Spinner />
  | "success"  // renders a static checkmark, background fills primary
```

`loading` composes `` in place of the label. `success` renders a static checkmark SVG. Both `loading` and `success` hide the label text.

## Snap usage (simple path)

Pass `state` directly — the component snaps instantly to that visual. Useful for static previews or when you drive state from your own logic:

```tsx
import { Button } from "@/components/remocn/button";

export const Scene = () => <Button state="loading" />;
```

To drive state from the timeline, use `useCurrentState`:

```tsx
import { useCurrentState } from "@/lib/remocn-ui";
import { Button } from "@/components/remocn/button";

export const Scene = () => {
  const state = useCurrentState(
    [
      { at: 8,  state: "hover" },
      { at: 20, state: "press" },
      { at: 35, state: "loading" },
      { at: 70, state: "success" },
    ],
    "idle",
  );

  return <Button label="Continue" state={state} />;
};
```

Each `at` is a **Sequence-local authored frame**. State persists between steps: `hover` at frame 8 keeps the button lifted until `press` fires at frame 20. See [Concepts](/docs/ui/concepts) for the full `useCurrentState` API.

## Smooth transitions (opt-in)

State changes via `state` snap with no cross-fade. For animated transitions, use `useButtonTransition` from the copied `use-button-transition.ts` file. It reads the frame, interpolates between state presets, and returns a resolved `ButtonStyle` — pass it to the `style` prop:

```tsx
import { Button } from "@/components/remocn/button";
import { useButtonTransition } from "@/components/remocn/use-button-transition";

export const Scene = () => {
  const style = useButtonTransition([
    { at: 12, state: "hover" },
    { at: 30, state: "press" },
    { at: 48, state: "loading", duration: 6 },
    { at: 96, state: "success", duration: 16 },
  ]);
  return <Button label="Continue" style={style} />;
};
```

The `duration` field on each step overrides the file's `DEFAULT_DURATION` for that specific transition. To globally tune timing and easing, edit `use-button-transition.ts` directly in your project — that file is yours (shadcn "own your code" philosophy).

`style` takes precedence over `state` when both are provided.


## Props

| Prop | Type | Default | Description |
|---|---|---|---|
| `state` | `"idle" \| "hover" \| "press" \| "loading" \| "success"` | `"idle"` | Current visual state (snap path). State changes snap — no automatic cross-fade. |
| `style` | `ButtonStyle` | — | Resolved animated visual (smooth path). Pass an interpolated ButtonStyle from useButtonTransition. Takes precedence over state when provided. |
| `label` | `string` | `"Continue"` | Button label text. Hidden during loading and success states. |
| `variant` | `"default" \| "secondary" \| "destructive" \| "outline" \| "ghost"` | `"default"` | Visual variant. Matches shadcn button variant names. |
| `size` | `"sm" \| "default" \| "lg"` | `"default"` | Button height and padding. sm=32px, default=40px, lg=48px. |
| `theme` | `Partial<RemocnTheme>` | — | Per-component theme override. Merges with the active RemocnUIProvider theme and the mode defaults. Must be concrete oklch/hex/rgb values — not CSS custom properties. |
| `mode` | `"light" \| "dark"` | `"light"` | Sets the base palette. Overrides the mode on RemocnUIProvider when both are present. |
| `primary` | `string` | — | Convenience override for the primary theme token. Merged into theme — saves a theme object for single-token changes. |
| `speed` | `number` | `1` | Forwarded to the loading Spinner. Scales the spinner playhead — speed=2 spins twice as fast without changing authored step times. |
| `className` | `string` | — | Optional className passed to the inner <button> element. |

## Use when
- Showing the full interactive lifecycle of a CTA — hover → press → loading → success — on the timeline.
- A composition needs a trigger visual for a modal-layer primitive (`dialog`, `alert-dialog`, `drawer`).
- Demonstrating a loading/submit state with a spinner inline inside the button.

## Don't use when
- You need a selectable row inside a menu — use `dropdown-menu-item` or `command-menu-item` instead.
- The button must open a searchable dropdown — use `combobox` (which uses an input trigger) instead.
- You only need a static label with no interactive state — render plain text in the composition.