---
title: "Building forms — Svelte"
description: "Turn any PDF into a form: place text boxes, checkboxes, radio buttons, lists and signature fields, and change their settings."
framework: "Svelte"
source: "https://www.embedpdf.com/docs/headless/svelte/forms/building"
---

# Building forms

The form plugin can also add fields to a PDF, and change or remove the ones it has, so people can turn
any document into a form. Switch to **design mode**, pick a field tool, and click or drag on the page:

**`field-palette.svelte`**

```svelte
<script lang="ts">
  import { useInteraction, useInteractionState } from '@embedpdf/svelte/interaction';

  const FIELD_TOOLS = [
    ['form-text', 'Text'],
    ['form-checkbox', 'Checkbox'],
    ['form-radio', 'Radio button'],
    ['form-combobox', 'Dropdown'],
    ['form-listbox', 'List'],
    ['form-signature', 'Signature'],
  ] as const;

  const interaction = useInteraction();
  const state = useInteractionState();
</script>

{#each FIELD_TOOLS as [id, label] (id)}
  <button aria-pressed={state.activeToolId === id} onclick={() => interaction.activateTool(id)}>
    {label}
  </button>
{/each}
```

Building needs the [annotation plugin](../annotations) next to the form plugin: in design mode, fields
are boxes you select, move, resize and style like any annotation.

| Tool             | Places                      | A click makes |
| :--------------- | :-------------------------- | :------------ |
| `form-text`      | A text box                  | 160 × 24      |
| `form-checkbox`  | A checkbox                  | 18 × 18       |
| `form-radio`     | A radio button              | 18 × 18       |
| `form-combobox`  | A dropdown                  | 140 × 24      |
| `form-listbox`   | A list                      | 140 × 72      |
| `form-button`    | A push button               | 100 × 28      |
| `form-signature` | A field someone signs later | 160 × 48      |

A see-through preview of the field follows the pointer, drawn as the field will be, so people see
where it lands before they click. Drag instead of clicking for another size. The tool stays active, so placing ten checkboxes is ten
clicks.

**`App.svelte`**

```svelte
<script lang="ts" module>
  import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
  import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
  import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
  import { interactionPlugin } from '@embedpdf/svelte/interaction';
  import { AnnotationLayer, annotationPlugin } from '@embedpdf/svelte/annotation';
  import { FormLayer, formPlugin } from '@embedpdf/svelte/form';
  import { localEngine } from '@embedpdf/engine';
  import FieldPalette from './FieldPalette.svelte';

  import '../designer.css';

  const engine = localEngine();
  // Building forms needs the annotation plugin: in design mode, fields are boxes like any annotation.
  const plugins = [
    stagePlugin(),
    renderPlugin(),
    interactionPlugin(),
    annotationPlugin(),
    formPlugin(),
  ];

  const ebook = async (): Promise<OpenInput> => {
    const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
    return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
  };
</script>

<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
  <DocumentGate>
    {#snippet fallback()}
      <p class="loading">Loading…</p>
    {/snippet}
    <FieldPalette />
    <Stage class="stage">
      <RenderLayer />
      <AnnotationLayer />
      <FormLayer />
    </Stage>
  </DocumentGate>
</Viewer>
```

**`designer.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 13px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.button[aria-pressed='true'] {
  color: var(--accent);
  background: var(--tint);
  border-color: var(--line-strong);
}

/* Fill in or design: one of the two */
.segments {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  background: var(--tint);
  border: 1px solid var(--line);
  border-radius: 10px;
}

.segment {
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1;
  color: var(--muted);
  background: transparent;
  border: 0;
  border-radius: 7px;
  padding: 7px 12px;
  cursor: pointer;
}

.segment:hover {
  color: var(--ink);
}

.segment[aria-pressed='true'] {
  color: var(--accent);
  background: var(--surface);
  box-shadow: 0 1px 3px rgb(7 32 76 / 0.12);
}

.segment:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 1px;
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
}

/* The pages */
.stage {
  height: 440px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`FieldPalette.svelte`**

```svelte
<script lang="ts">
  import { untrack } from 'svelte';
  import { usePageList } from '@embedpdf/svelte/runtime';
  import { useStage } from '@embedpdf/svelte/stage';
  import { useInteraction, useInteractionState } from '@embedpdf/svelte/interaction';
  import { useForm, useFormState } from '@embedpdf/svelte/form';

  const FIELD_TOOLS = [
    ['form-text', 'Text'],
    ['form-checkbox', 'Checkbox'],
    ['form-radio', 'Radio button'],
    ['form-combobox', 'Dropdown'],
    ['form-listbox', 'List'],
    ['form-signature', 'Signature'],
  ] as const;

  const interaction = useInteraction();
  const interactionState = useInteractionState();
  const form = useForm();
  const formState = useFormState();
  const stage = useStage();
  const pages = usePageList();
  // The pointer fills the form in; every other tool here designs it.
  const filling = $derived(interactionState.activeToolId === 'pointer');

  // Start on the ebook's last page, which has room for a form, with the text tool picked.
  let started = false;
  $effect(() => {
    const page = pages.current.at(-1)?.ref;
    if (!page || started) return;
    started = true;
    untrack(() => {
      stage.goToPage(page);
      interaction.activateTool('form-text');
    });
  });
</script>

{#if !form.canDesign()}
  <p class="readout">This document's form can't be changed.</p>
{:else}
  <div class="toolbar">
    <div class="segments">
      <button
        type="button"
        class="segment"
        aria-pressed={filling}
        onclick={() => interaction.activateTool('pointer')}
      >
        Fill in
      </button>
      <button
        type="button"
        class="segment"
        aria-pressed={!filling}
        onclick={() => interaction.activateTool('form-edit')}
      >
        Design
      </button>
    </div>
    {#each FIELD_TOOLS as [id, label] (id)}
      <button
        type="button"
        class="button"
        aria-pressed={interactionState.activeToolId === id}
        onclick={() => interaction.activateTool(id)}
      >
        {label}
      </button>
    {/each}
    <output class="readout">
      {formState.fields.length === 0
        ? 'Click the page to place a field'
        : `${formState.fields.length} fields`}
    </output>
  </div>
{/if}
```

## Design mode

The `form-edit` tool is design mode: fields stop being fillable and become boxes you can select, move
and resize. The `pointer` tool makes them fillable again:

```ts
interaction.activateTool('form-edit'); // design the form
interaction.activateTool('pointer'); // fill it in
```

In design mode the form lends its boxes to the annotation plugin, which selects, moves, resizes,
restyles, reorders and deletes them with its own tools, next to the annotations. Every edit of a box
is still the form's: its place or look is `updateWidget()`, and deleting every box of a field
deletes the field. A drag of two fields and a stamp is one change: one request, and one step to
undo. Like every change, it shows at once and the engine's answer follows.

A field just placed is selected at once, before the engine answers, and the tool goes back to
`form-edit` rather than the default pointer, so design mode goes on. Without `canDesign()` nothing is
lent: the fields stay fillable boxes the annotation tools leave alone.

## Change a field's settings

`useFormState().selectedField` is the field of the selected box, for a settings panel:

**`field-settings.svelte`**

```svelte
<script lang="ts">
  import { useForm, useFormState } from '@embedpdf/svelte/form';

  const form = useForm();
  const state = useFormState();
</script>

{#if state.selectedField}
  {@const field = state.selectedField}
  <input value={field.name} onblur={(e) => form.update(field.ref, { name: e.currentTarget.value })} />
  <label>
    <input
      type="checkbox"
      checked={field.required}
      onchange={(e) => form.update(field.ref, { required: e.currentTarget.checked })}
    />
    Required
  </label>
{/if}
```

`update()` takes only what you change, with the same settings a field reads:

| Setting                                      | What it is                                                                            |
| :------------------------------------------- | :------------------------------------------------------------------------------------ |
| `name`                                       | Its own part of its name: renaming `customer.email` to `phone` makes `customer.phone` |
| `required`                                   | PDF apps ask for a value before the form is submitted                                 |
| `readOnly`                                   | People can't change the value                                                         |
| `alternateName`                              | The tooltip, also read by screen readers                                              |
| `defaultValue`                               | What a reset puts back                                                                |
| `options`                                    | For a dropdown or list: `{ label, value }` pairs                                      |
| `maxLength`, `multiline`, `password`, `comb` | For a text box                                                                        |

`getFieldSettings(ref)` describes a field's settings for a panel you draw without knowing the
field's family: `{ properties, values }`, each property a `key` (what `update()` takes), a `control`
(`text`, `flag`, `number`, `choice`, `choices` or `options`), and a `label` in English with its
translation key, `labelKey` (`form.settings.tooltip`), in display order. A `choice`'s options are
the field's own values, shown as they are.

A field's look, such as its border, background and font, belongs to its box: change it like any
annotation's, with the [style panel](../annotations/selecting#a-style-panel), or from code with
`form.updateWidget(widget.ref, { color: '#1d4ed8' })`.

**`App.svelte`**

```svelte
<script lang="ts" module>
  import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
  import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
  import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
  import { interactionPlugin } from '@embedpdf/svelte/interaction';
  import { AnnotationLayer, annotationPlugin } from '@embedpdf/svelte/annotation';
  import { FormLayer, formPlugin } from '@embedpdf/svelte/form';
  import { localEngine } from '@embedpdf/engine';
  import FieldSettings from './FieldSettings.svelte';

  import '../field-settings.css';

  const engine = localEngine();
  const plugins = [
    stagePlugin(),
    renderPlugin(),
    interactionPlugin(),
    annotationPlugin(),
    formPlugin(),
  ];

  const ebook = async (): Promise<OpenInput> => {
    const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
    return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
  };
</script>

<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
  <DocumentGate>
    {#snippet fallback()}
      <p class="loading">Loading…</p>
    {/snippet}
    <div class="viewer">
      <Stage class="stage">
        <RenderLayer />
        <AnnotationLayer />
        <FormLayer />
      </Stage>
      <FieldSettings />
    </div>
  </DocumentGate>
</Viewer>
```

**`field-settings.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --accent: #054fb3;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The pages and the settings panel: stacked, and side by side where there's room */
.viewer {
  container-type: inline-size;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.stage,
.panel {
  flex: 1 1 100%;
  box-sizing: border-box;
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.stage {
  height: 340px;
  background: var(--canvas);
}

.panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 0;
  padding: 14px;
  background: var(--surface);
}

@container (min-width: 620px) {
  .stage {
    flex: 1 1 0;
    min-width: 0;
    height: 440px;
  }

  .panel {
    flex: 0 0 220px;
    align-self: flex-start;
  }
}

.hint {
  font-size: 13px;
  color: var(--muted);
}

/* One setting: a label over its input, or a checkbox beside its label */
.setting {
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}

.input {
  font: inherit;
  font-size: 13px;
  font-weight: 400;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 7px 9px;
}

.input:focus-visible {
  outline: none;
  border-color: var(--focus);
  box-shadow: 0 0 0 3px rgb(125 182 255 / 0.35);
}

.check {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--ink);
}

.check input {
  accent-color: var(--accent);
}

.button {
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 13px;
  cursor: pointer;
  box-shadow: 0 1px 2px var(--shadow);
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`FieldSettings.svelte`**

```svelte
<!-- What a settings panel shows for the selected field, and changes with `update()`. -->
<script lang="ts">
  import { untrack } from 'svelte';
  import { usePageList } from '@embedpdf/svelte/runtime';
  import { useStage } from '@embedpdf/svelte/stage';
  import { useInteraction } from '@embedpdf/svelte/interaction';
  import { useAnnotation } from '@embedpdf/svelte/annotation';
  import { useForm, useFormState } from '@embedpdf/svelte/form';

  // How the new fields look: part of the PDF, like the rest of the page.
  const look = { color: '#94a3b8', interiorColor: '#f8fafc', strokeWidth: 1, fontSize: 11 };

  const form = useForm();
  const annotation = useAnnotation();
  const interaction = useInteraction();
  const stage = useStage();
  const formState = useFormState();
  const pages = usePageList();

  // The ebook has no form: add two fields to its last page, and select the first in design mode.
  let added = false;
  $effect(() => {
    const page = pages.current.at(-1)?.ref;
    if (formState.status !== 'ready' || !page || added) return;
    added = true;
    const at = (y: number) => ({ page, rect: { x: 72, y, width: 240, height: 24 }, ...look });
    untrack(async () => {
      const { field } = await form.create({ family: 'text', name: 'name', widgets: [at(540)] });
      await form.create({ family: 'text', name: 'email', widgets: [at(576)] });
      stage.goToPage(page);
      interaction.activateTool('form-edit');
      const widget = field.widgets[0]?.ref;
      if (widget) annotation.selection.set([widget]);
    });
  });
</script>

{#if formState.selectedField}
  {@const field = formState.selectedField}
  <div class="panel">
    <label class="setting">
      Name
      <input
        class="input"
        value={field.name}
        onblur={(event) => {
          const name = event.currentTarget.value.trim();
          if (name && name !== field.name) void form.update(field.ref, { name });
        }}
      />
    </label>
    <label class="setting">
      Tooltip
      <input
        class="input"
        value={field.alternateName ?? ''}
        onblur={(event) =>
          void form.update(field.ref, { alternateName: event.currentTarget.value || null })}
      />
    </label>
    <label class="check">
      <input
        type="checkbox"
        checked={field.required}
        onchange={(event) => void form.update(field.ref, { required: event.currentTarget.checked })}
      />
      Required
    </label>
    <label class="check">
      <input
        type="checkbox"
        checked={field.readOnly}
        onchange={(event) => void form.update(field.ref, { readOnly: event.currentTarget.checked })}
      />
      Read-only
    </label>
    <button type="button" class="button" onclick={() => void form.delete(field.ref)}>
      Remove the field
    </button>
  </div>
{:else}
  <p class="panel hint">Select a field on the page.</p>
{/if}
```

## Add fields from code

`create()` takes the same settings the engine takes, with the page and the box for each place the
field shows:

```ts
const { field } = await form.create({
  family: 'combobox',
  name: 'country',
  options: [
    { label: 'Netherlands', value: 'NL' },
    { label: 'Belgium', value: 'BE' },
  ],
  widgets: [{ page: page.ref, rect: { x: 72, y: 252, width: 148, height: 20 } }],
});
```

A radio group is one field with a box per button, each with the value it stands for:

```ts
await form.create({
  family: 'radio',
  name: 'plan',
  widgets: [
    { page: page.ref, rect: { x: 72, y: 136, width: 16, height: 16 }, exportValue: 'monthly' },
    { page: page.ref, rect: { x: 72, y: 166, width: 16, height: 16 }, exportValue: 'yearly' },
  ],
});
```

A field placed with a tool gets a name like `text_1`; rename it in your settings panel. Every
family's settings are on [Creating form fields](https://www.embedpdf.com/docs/engine/forms/authoring).

**`App.svelte`**

```svelte
<script lang="ts" module>
  import { DocumentGate, Viewer, type OpenInput } from '@embedpdf/svelte/runtime';
  import { Stage, stagePlugin } from '@embedpdf/svelte/stage';
  import { RenderLayer, renderPlugin } from '@embedpdf/svelte/render';
  import { interactionPlugin } from '@embedpdf/svelte/interaction';
  import { FormLayer, formPlugin } from '@embedpdf/svelte/form';
  import { localEngine } from '@embedpdf/engine';
  import BuildToolbar from './BuildToolbar.svelte';

  import '../create.css';

  const engine = localEngine();
  const plugins = [stagePlugin(), renderPlugin(), interactionPlugin(), formPlugin()];

  const ebook = async (): Promise<OpenInput> => {
    const response = await fetch('https://snippet.embedpdf.com/ebook.pdf');
    return { kind: 'bytes', id: 'ebook', bytes: new Uint8Array(await response.arrayBuffer()) };
  };
</script>

<Viewer {engine} {plugins} initialDocuments={[{ source: ebook }]}>
  <DocumentGate>
    {#snippet fallback()}
      <p class="loading">Loading…</p>
    {/snippet}
    <BuildToolbar />
    <Stage class="stage">
      <RenderLayer />
      <FormLayer />
    </Stage>
  </DocumentGate>
</Viewer>
```

**`create.css`**

```css
/* Colors */
:root {
  --ink: #1a2748;
  --muted: #5a6b92;
  --line: #e6eaf2;
  --line-strong: #dce8fc;
  --surface: #ffffff;
  --tint: #f3f7fe;
  --canvas: #f7f9fc;
  --focus: #7db6ff;
  --shadow: rgb(7 32 76 / 0.04);
}

/* The controls, above the pages */
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}

.button {
  font: inherit;
  font-size: 13px;
  font-weight: 550;
  line-height: 1;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  padding: 8px 13px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 1px 2px var(--shadow);
  transition:
    background-color 0.15s ease,
    border-color 0.15s ease;
}

.button:hover {
  background: var(--tint);
  border-color: var(--line-strong);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.readout {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* The pages */
.stage {
  height: 440px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--canvas);
  box-shadow:
    0 1px 2px var(--shadow),
    0 18px 40px -28px rgb(7 32 76 / 0.28);
}

.loading {
  margin: 0;
  padding: 24px 0;
  text-align: center;
  font-size: 13px;
  color: var(--muted);
}
```

**`BuildToolbar.svelte`**

```svelte
<script lang="ts">
  import { untrack } from 'svelte';
  import { usePageList, type PageRef } from '@embedpdf/svelte/runtime';
  import { useStage } from '@embedpdf/svelte/stage';
  import { useForm, useFormState, type FormCapability } from '@embedpdf/svelte/form';

  // How the new fields look: part of the PDF, like the rest of the page.
  const look = { color: '#94a3b8', interiorColor: '#f8fafc', strokeWidth: 1, fontSize: 11 };

  /** A dropdown, the next row down the page. */
  function addDropdown(form: FormCapability, page: PageRef, row: number) {
    return form.create({
      family: 'combobox',
      name: `country_${row + 1}`,
      options: [
        { label: 'Netherlands', value: 'NL' },
        { label: 'Belgium', value: 'BE' },
      ],
      widgets: [{ page, rect: { x: 72, y: 520 + row * 36, width: 160, height: 24 }, ...look }],
    });
  }

  /** A radio group: one field with a widget per button, each with the value it stands for. */
  function addRadioGroup(form: FormCapability, page: PageRef, row: number) {
    const y = 524 + row * 36;
    return form.create({
      family: 'radio',
      name: `plan_${row + 1}`,
      widgets: [
        { page, rect: { x: 72, y, width: 16, height: 16 }, exportValue: 'monthly', ...look },
        { page, rect: { x: 112, y, width: 16, height: 16 }, exportValue: 'yearly', ...look },
      ],
    });
  }

  const form = useForm();
  const stage = useStage();
  const formState = useFormState();
  const pages = usePageList();
  const page = $derived(pages.current.at(-1)?.ref);

  // The ebook has no form: start with a dropdown on its last page, which has room.
  let added = false;
  $effect(() => {
    const last = page;
    if (formState.status !== 'ready' || !last || added) return;
    added = true;
    untrack(() => {
      void addDropdown(form, last, 0).then(({ field }) =>
        stage.reveal(last, { rect: form.getWidget(field.widgets[0])!.rect }),
      );
    });
  });

  // Each new field goes a row further down the page: bring it into view.
  function add(addField: typeof addDropdown) {
    const last = page;
    if (!last) return;
    void addField(form, last, formState.fields.length).then(({ field }) =>
      stage.reveal(last, { rect: form.getWidget(field.widgets[0])!.rect }),
    );
  }

  const lastField = $derived(formState.fields.at(-1));
  const full = $derived(formState.fields.length >= 5);
</script>

<div class="toolbar">
  <button type="button" class="button" disabled={!page || full} onclick={() => add(addDropdown)}>
    Add a dropdown
  </button>
  <button type="button" class="button" disabled={!page || full} onclick={() => add(addRadioGroup)}>
    Add a radio group
  </button>
  <button
    type="button"
    class="button"
    disabled={!lastField}
    onclick={() => lastField && void form.delete(lastField.ref)}
  >
    Remove the last
  </button>
  <output class="readout">
    {formState.fields.map((field) => field.name).join(', ') || 'No fields'}
  </output>
</div>
```

### One more button for a group

`addWidget(ref)` shows a field in one more place: for a radio group a new choice, for a checkbox a
box that checks together with the others.

```ts
const { widget } = await form.addWidget(plan.ref);
annotation.selection.set([widget.ref]); // in design mode, so it can be dragged
```

Without a place, it goes next in line: below the last button if they stand in a column, beside it
if in a row, at the same size and the same spacing, starting a new column where the page ends. It
looks like the field's last button, and a radio group's new choice gets the next free `Choice<n>`.
Pass what you want instead: `form.addWidget(plan.ref, { rect, exportValue: 'yearly' })` takes
everything an entry of `create()`'s `widgets` takes.

## Scripts and calculations

A field's scripts go in `actions` when you create or change it: a calculated total, a formatted
amount, a check on what the user typed. The viewer runs them while the user fills the form in.

```ts
await form.update(total.ref, {
  actions: {
    calculate: {
      type: 'javascript',
      script: 'event.value = this.getField("subtotal").value * 1.21;',
    },
  },
});
await form.reorderCalculations([tax.ref], { before: total.ref });
```

A calculate script puts the field at the end of the order calculations run in;
`reorderCalculations()` moves it. Writing a script needs `canWriteScripts()`. See
[Scripts and calculations](https://www.embedpdf.com/docs/engine/forms/authoring#scripts-and-calculations).

A box can do something when it's clicked, too, such as reset the form: give its entry of
`widgets` in `create()` its `actions`. A script, a submit or a web link there needs
`canWriteScripts()` as well. See [Widget actions](https://www.embedpdf.com/docs/engine/forms/authoring#widget-actions).
A push button (`family: 'pushbutton'`) is a box that only does that, with a `caption`; see
[A push button](https://www.embedpdf.com/docs/engine/forms/authoring#a-push-button).

## Remove a field

```ts
await form.delete(field.ref);
```

This removes the field and every box it has. To remove one box of a field that shows in several places,
such as a reference number on every page, use `form.deleteWidget(widget.ref)`. It resolves the field
as it is now. In design mode, deleting the selection does the same: one box goes from its field, and
a field whose every box is selected goes whole.

Every building method shows its change at once, is one step to undo, and resolves once the engine
answered.

## Methods

What `useForm()` gives you for building forms. Reading, filling and the form's state are on
[Filling forms](../forms#methods).

| Method                                  | What it does                                                                                                                                                                                                                  |
| :-------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create(draft)`                         | Adds a field, with a box for each place it shows. Resolves `{ field }`. [Add fields from code](#add-fields-from-code)                                                                                                         |
| `update(ref, patch)`                    | Changes a field's settings; pass only what changes. Resolves `{ field }`. [Change a field's settings](#change-a-fields-settings)                                                                                              |
| `addWidget(ref, placement?)`            | Shows a field in one more place: a radio group's next choice, a checkbox's next box. Resolves `{ field, widget }`. [One more button for a group](#one-more-button-for-a-group)                                                |
| `delete(ref)`                           | Removes a field and every box it has. [Remove a field](#remove-a-field)                                                                                                                                                       |
| `updateWidget(widget, patch)`           | Changes one box's place or look; pass only what changes. Resolves `{ widget }`. [Change a field's settings](#change-a-fields-settings)                                                                                        |
| `getFieldSettings(ref)`                 | A field's settings described for a panel: `{ properties, values }`, or `null`. [Change a field's settings](#change-a-fields-settings)                                                                                         |
| `deleteWidget(widget)`                  | Removes one box from its page, and from its field when it has one. Resolves `{ field }`, or `{ field: null }` for a box in no field. [Remove a field](#remove-a-field)                                                        |
| `removeWidget(ref, widget)`             | Takes one box out of its field; it stays on the page, in no field, until `deleteWidget()` removes it                                                                                                                          |
| `reorderCalculations(fields, position)` | Moves fields in the order calculate scripts run in. Resolves `{ calculationOrder }`. [Scripts and calculations](#scripts-and-calculations)                                                                                    |
| `repair(options?)`                      | Fixes a form other PDF apps read differently, such as fields missing from the form's list, and resolves what it fixed. See [Repair a broken form](https://www.embedpdf.com/docs/engine/forms/authoring#repair-a-broken-form). |
| `canDesign()`                           | Whether adding, changing and removing fields is allowed. [Permissions](#permissions)                                                                                                                                          |
| `canRestoreAttribution()`               | Whether an import may keep who created and filled in its fields, instead of naming the importing user. [Permissions](#permissions)                                                                                            |
| `canWriteScripts()`                     | Whether scripts, submit buttons and links may be written into fields and their boxes. [Permissions](#permissions)                                                                                                             |

Every method that returns a promise also takes a `signal` in its options, to cancel it.

## Events

**`select-new-field.svelte`**

```svelte
<script lang="ts">
  import { useFormEvent } from '@embedpdf/svelte/form';
  import { selectField } from './editor';

  useFormEvent(
    (form) => form.onFieldCreated,
    ({ field }) => selectField(field),
  );
</script>
```

| Event            | Fires when                 | Carries           |
| :--------------- | :------------------------- | :---------------- |
| `onFieldCreated` | A field was added          | `field`, `origin` |
| `onFieldUpdated` | A field's settings changed | `field`, `origin` |
| `onFieldDeleted` | A field was removed        | `ref`, `origin`   |

## Permissions

| Permission         | What it allows                                                  | Check                          |
| :----------------- | :-------------------------------------------------------------- | :----------------------------- |
| `doc.forms.modify` | Adding, changing, removing and repairing fields                 | `form.canDesign()`             |
| `doc.forms.import` | Importing fields and values with who created and filled them in | `form.canRestoreAttribution()` |
| `doc.forms.script` | Writing scripts, submit buttons and links into fields and boxes | `form.canWriteScripts()`       |

Hide the field tools when `canDesign()` is false, and the script, submit and link options of a
field's or a box's actions when `canWriteScripts()` is false. Scripts already in the form run either way.

## Good to know

### Fields and boxes

A field is the data: its name, its value, its settings. A **widget** is a box where the field shows on
a page. Most fields have one; a radio group has one per button, and a field can show on several pages,
all with the same value.

### Signature fields

A signature field is a place for someone to sign later. Signing it is on [Signatures](signatures).
