# Controlled and uncontrolled


## Uncontrolled

The default. Pass `defaultValue` and let the panel keep the value:

```tsx
<ColorInput defaultValue="#3366cc" onChangeComplete={(c) => save(c.hex)} />
```

## Controlled

You own it. Pass `value` and update it yourself:

```tsx
const [color, setColor] = useState("#3366cc");

<ColorInput value={color} onChange={(c) => setColor(c.hex)} />;
```

<Callout variant="warning">
  Pass `value` and you must handle `onChange`, or the panel will not move.
</Callout>

## Everything else works the same way

`mode`, `open`, `collapsed`, `size` and `recentColors` all follow the same
pattern: pass the prop to control it, leave it out to let the panel manage it.

| Controlled     | Uncontrolled          | Change event                   |
| -------------- | --------------------- | ------------------------------ |
| `value`        | `defaultValue`        | `onChange`, `onChangeComplete` |
| `mode`         | `defaultMode`         | `onModeChange`                 |
| `open`         | `defaultOpen`         | `onOpenChange`                 |
| `collapsed`    | `defaultCollapsed`    | `onCollapsedChange`            |
| `size`         | `defaultSize`         | `onSizeChange`                 |
| `recentColors` | `defaultRecentColors` | `onRecentColorsChange`         |

## onChange vs onChangeComplete

`onChange` fires about 60 times a second while you drag. `onChangeComplete`
fires once, when you let go.

Use `onChange` for live preview, and `onChangeComplete` for anything that costs
something: saving, undo entries, network calls.

<Callout variant="warning" title="Do not put onChange into global state">
  It fires on every frame of a drag. Keep that state local, or save from
  `onChangeComplete` instead.
</Callout>
