# Inline Edit

> A value people change where it sits. The value is the button, and the field opens in its exact box.

- Name: `inline-edit`
- Status: beta
- Categories: forms
- File: the install writes `inline-edit.tsx` to your `ui` alias, `@/components/ui` by default.
- Gallery: https://txbrendan.dev/components/inline-edit
- Registry item: https://txbrendan.dev/r/inline-edit.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@txbrendan": "https://txbrendan.dev/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@txbrendan` in a public list that txbrendan/ui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @txbrendan/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @txbrendan/inline-edit
   ```

## Use it for

- One short value on a page or a row that people change now and then: a name, a title, a place, a number.
- Pass label as a short noun, like "location". It names the value "Edit location" and the empty state "Add location".
- Save in onSave. Return the promise from your request, and the change shows at once, says Saving…, and comes back if it fails.
- When you control value, set it to the new text before your promise resolves.

## Avoid it for

- Text that runs to several lines, like a description. Use a Textarea in a form or a dialog.
- Values that must change together, like a start and an end date. Edit them in one form with one Save.
- A form people fill in once from top to bottom. Use Input with a label.
- Adding a pencil or an Edit button beside the value. The value is already the button.

## Props

- `label`: `string`. What the value is, as a short noun. Required. It names the value and the empty state.
- `value`: `string`. The saved value. Controlled. Set it to the new text before your onSave promise resolves.
- `defaultValue`: `string`. Default `""`. The saved value, when you do not control value.
- `onSave`: `(value: string) => void | Promise<unknown>`. Called with the trimmed new text when it changed. Enter saves, and so does clicking away. Escape cancels. A promise shows Saving… until it settles. A rejection or a throw puts the old value back and shows errorText. The newest save wins.
- `size`: `"sm" | "md" | "lg"`. Default `"md"`. Text size: 11, 13, or 16px. The box is as tall as a small control, so density scales it.
- `disabled`: `boolean`. Default `false`. Shows the value as plain text. It is not a button and cannot be edited.
- `placeholder`: `string`. Shown in the empty field while editing.
- `editText`: `string`. Default `"Edit {label}"`. Words before the value in its name for screen readers. Set it to translate.
- `addText`: `string`. Default `"Add {label}"`. Shown with a plus when the value is empty. Set it to translate.
- `savingText`: `string`. Default `"Saving…"`. Shown after the value while its save runs.
- `errorText`: `string`. Default `"Could not save. Try again."`. Shown under the value when a save fails.
- `className`: `string`. Extra classes on the outer span. Later classes win, so className='text-base' replaces the size.

## Accessibility

- The value is a native \<button> named "Edit {label}: {value}", so screen readers hear what it is and what it holds. An empty value is named "Add {label}".
- Enter, Space, or a click opens a text field with the value, focused. A short value is selected, so typing replaces it.
- Enter saves and Escape cancels. Both put focus back on the value. Clicking away saves and leaves focus where you clicked.
- Escape stops at the field, so a dialog or popover around it stays open.
- While a save runs, the value button is described by the Saving… text. A failed save shows its message with role="alert" and describes the button.
- The field takes the value's exact box, so nothing on the page moves when it opens or closes.
- On touch screens the text is 16px, so iOS Safari does not zoom in when the field opens.
- A disabled value is plain text with no button, so it does not look like something to press.

## Examples

### Save to your server

```tsx
<InlineEdit
  label="location"
  defaultValue="Berlin"
  onSave={(location) => fetch("/api/profile", { method: "PATCH", body: JSON.stringify({ location }) }).then((r) => {
    if (!r.ok) throw new Error("Save failed");
  })}
/>
```

### Controlled

```tsx
const [title, setTitle] = useState("Q3 planning");

<InlineEdit
  label="title"
  size="lg"
  value={title}
  onSave={async (next) => {
    await saveTitle(next);
    setTitle(next);
  }}
/>
```

### Empty, in a table cell (needs @txbrendan/table)

```tsx
<TableCell>
  <InlineEdit label="owner" placeholder="Name or email" onSave={setOwner} />
</TableCell>
```
