# Working with forms

Source: https://sdk.pdf.net/docs/v1/guides/working-with-forms



PDF forms carry a lot of format complexity: field hierarchies with inherited attributes, export values, appearance states, and values stored apart from the widgets that display them. The SDK hides this behind a widget-centric approach: you work with the widgets you see on the pages, and each value is shaped like the value of the matching HTML form control, such as a string for a text field and a boolean for a checkbox. The underlying PDF form records stay behind the API.

## Widgets and fields [#widgets-and-fields]

A form consists of two related things. A **widget** is a block on a page: the visible box a person interacts with. A **field** is a named value of the form, and it belongs to the document, not to a widget. Field names aren't unique: every widget with the same field name shows and synchronizes the same value, the way radio widgets of one group do by design.

The widget kinds are `text`, `choice` (single-select), `multiChoice`, `checkbox`, `radio`, `pushButton`, and `signature`. Each has its own value shape:

* `text` and `choice`: a string;
* `checkbox` and `radio`: a boolean, whether the widget is checked;
* `multiChoice`: the selected option indices (zero-based, in the widget's `options`);
* `pushButton` and `signature`: no value.

## Filling fields [#filling-fields]

When the content policy allows interacting with form fields, users fill fields right in the main view, with nothing to build. The hooks below are for form UI outside of it, such as a sidebar listing every field.

`useWidgets` returns all document widgets in reading order, and `useWidgetValue` binds one widget's value to your component, like a `useState` backed by the document:

```tsx
import {
  CheckboxWidget,
  TextWidget,
  useWidgets,
  useWidgetValue,
  Widget,
} from '@sdk-pdf/react'

function FormSidebar() {
  const { widgets, isLoaded } = useWidgets()

  // The list is partial until every page containing widgets has been loaded.
  if (!isLoaded) return <p>Loading the form…</p>

  return (
    <form>
      {widgets.map((widget) => (
        <FormField key={widget.id} widget={widget} />
      ))}
    </form>
  )
}

function FormField({ widget }: { widget: Widget }) {
  switch (widget.widgetType) {
    case 'text':
      return <TextField widget={widget} />
    case 'checkbox':
      return <CheckboxField widget={widget} />
    default:
      return null
  }
}

function TextField({ widget }: { widget: TextWidget }) {
  const [value, setValue] = useWidgetValue(widget)

  return (
    <label>
      {widget.fieldName}
      <input value={value} onChange={(event) => setValue(event.currentTarget.value)} />
    </label>
  )
}

function CheckboxField({ widget }: { widget: CheckboxWidget }) {
  const [isChecked, setIsChecked] = useWidgetValue(widget)

  return (
    <label>
      <input
        type="checkbox"
        checked={isChecked}
        onChange={(event) => setIsChecked(event.currentTarget.checked)}
      />
      {widget.fieldName}
    </label>
  )
}
```

The value returned by `useWidgetValue` follows the document: filling in the main view and undo/redo are reflected as they happen. The exact semantics of missing widgets and empty values are described in [useWidgetValue](https://sdk.pdf.net/docs/v1/reference/useWidgetValue).

## Creating widgets [#creating-widgets]

`useWidgetsCreate` returns a function that creates widgets programmatically and resolves with their snapshots. The widget kind, position, and size are required; everything else falls back to defaults, including a generated unique field name:

```tsx
const createWidgets = useWidgetsCreate()

async function addNameField(pageId: string) {
  const [widget] = await createWidgets({
    pageId,
    widgetType: 'text',
    // User-space units: the origin is at the bottom-left corner of the page.
    x: 72,
    y: 700,
    width: 180,
    height: 24,
    fieldName: 'customer.name',
  })

  return widget
}
```

To make a radio group, create several `radio` widgets with the same `fieldName`. To let users place widgets by clicking the page instead, switch the interaction tool to `{ type: 'addWidget', widgetProps }`, as described in [Building a PDF application](https://sdk.pdf.net/docs/v1/guides/building-a-pdf-application).

## Deleting widgets [#deleting-widgets]

A widget is a block, so it is deleted like any other block: `useBlockDelete` returns a function that deletes blocks by id, several at once as a single undo step. Deleting a widget doesn't erase the stored field value: another widget with the same field name keeps showing it.

Since widgets sharing a field name act as one field, deleting usually means deleting them together, such as removing a whole radio group:

```tsx
import { useBlockDelete, useWidgetsGetter } from '@sdk-pdf/react'

function useDeleteWidgetsByFieldName() {
  const getWidgets = useWidgetsGetter()
  const deleteBlocks = useBlockDelete()

  return async (fieldName: string) => {
    const widgets = await getWidgets()
    const widgetIds = widgets
      .filter((widget) => widget.fieldName === fieldName)
      .map((widget) => widget.id)

    // A single call, so deleting the whole group is one undo step.
    deleteBlocks(widgetIds)
  }
}
```

## Replacing widgets [#replacing-widgets]

A widget's kind can't be changed through its properties, so there is no dedicated replace command: create the new widget and delete the old one, in any order. For example, turning a choice widget (a dropdown) into a free-form text field:

```tsx
const createWidgets = useWidgetsCreate()
const deleteBlocks = useBlockDelete()

async function replaceChoiceWithText(widget: ChoiceWidget) {
  // The same field name makes the replacement pick up the stored field value.
  const [replacement] = await createWidgets([
    {
      pageId: widget.pageId,
      widgetType: 'text',
      x: widget.x,
      y: widget.y,
      width: widget.width,
      height: widget.height,
      fieldName: widget.fieldName,
    },
  ])

  deleteBlocks([widget.id])

  // The replacement is a new block: it has a new id.
  return replacement
}
```

Although the creation and the deletion are separate commands, edits made within 500 ms of each other merge into a single undo step, so the replacement normally undoes as one action. To keep such an operation apart from the user's own edits around it, mark its boundaries with [useUndoBoundary](https://sdk.pdf.net/docs/v1/reference/useUndoBoundary).

## Editing widget properties [#editing-widget-properties]

Widget properties define how a widget looks and behaves: the frame (border and fill), the text style, and flags such as `isReadOnly`. Changing them programmatically drives flows like restyling fields to highlight validation errors or locking a form once it is submitted. `useSetWidgetProps` updates properties of many widgets in one call, which is also one undo step; properties a widget's kind doesn't support are ignored, so the call can span mixed kinds.

For example, locking the whole form after submission:

```tsx
import { useSetWidgetProps, useWidgetsGetter } from '@sdk-pdf/react'

function useLockForm() {
  const getWidgets = useWidgetsGetter()
  const setWidgetProps = useSetWidgetProps()

  return async () => {
    const widgets = await getWidgets()

    // `isReadOnly` restricts filling the field, not authoring the widget.
    // `excludeFromHistory` keeps undo from unlocking the submitted form.
    await setWidgetProps(
      widgets.map((widget) => widget.id),
      { isReadOnly: true, fillColor: 'rgba(245,245,245,1)' },
      { excludeFromHistory: true }
    )
  }
}
```

See [useSetWidgetProps](https://sdk.pdf.net/docs/v1/reference/useSetWidgetProps) for the property lists of each widget kind.

## Datetime formatting [#datetime-formatting]

PDF has no date or time field type: a date field is a text field with a declared value format. The SDK recognizes the format and exposes it as the text widget's `format` prop, so you can render the right input UI, such as a date picker instead of a plain text box. `null` means a plain text field:

```tsx
if (widget.widgetType === 'text' && widget.format?.type === FormatType.DateTime) {
  // An Acrobat date format string, such as 'mm/dd/yyyy' or 'h:MM tt'.
  const dateFormat = widget.format.format
}
```

