# Forms

How the viewer fills form fields: a read-only switch, holding back Download while a required field is empty, the touch stepper, and the field highlight tint. Whether fields are flattened on download is [`export.flatten`](https://kernstra.com/docs/web-sdk/config/export/flatten) (off by default). Filling fields needs a license that includes `forms`; reading them out (`doc.forms.fields`, `getValues`) needs `extract`.

- Interface: `FormsConfig`
- Config path: `config.forms`

## Example

```ts
new PDFViewer(container, doc, {
  forms: {
    validateRequired: true,
    highlightColor: '#fff3c4', // a warmer field tint
  },
});
```

## Options (4)

### `readOnly`

- Type: `boolean`
- Default: `false`
- Page: https://kernstra.com/docs/web-sdk/config/forms/readonly

Show fields and values, fill nothing. Every field shows its value and takes no input, while the rest of the mode (tools, Download, Print) stays as it is. A signature field opens nothing. [`doc.forms.setValues()`](https://kernstra.com/docs/web-sdk/api/doc-forms/setvalues) refuses every field with `read_only` (a signature field with `unsupported_type`), as it does a field the document marks read-only.

```ts
forms: { readOnly: true }
```

### `validateRequired`

- Type: `boolean`
- Default: `false`
- Page: https://kernstra.com/docs/web-sdk/config/forms/validaterequired

Hold back Download while a required field is empty. The toolbar's Download (with or without your `onDownload`) does nothing while a required text, choice, check box or radio field is empty: a message names up to three empty fields (by their field names), then a count, and focus moves to the first. Read-only and hidden fields are skipped, and a check box or radio group counts as filled once any of its widgets is on. Signature fields are not checked. [`viewer.createExporter()`](https://kernstra.com/docs/web-sdk/api/pdfviewer/createexporter) is not held back; gate it yourself with [`doc.forms.getValues()`](https://kernstra.com/docs/web-sdk/api/doc-forms/getvalues), which carries the live input, and the fields.

```ts
import type { FormValue } from '@kernstra/pdf-web-sdk';

// One value per field; a check box or radio group reports 'Off' when nothing is on.
const values = await doc.forms.getValues();
const blank = (value: FormValue | undefined): boolean => {
  if (value === undefined || value === 'Off') return true;
  return typeof value === 'string' ? value.trim() === '' : value.length === 0;
};
const empty = new Set(
  (await doc.forms.fields())
    .filter((field) => field.required && !field.readOnly && blank(values[field.name]))
    .map((field) => field.name),
);
```

### `stepper`

- Type: `boolean`
- Default: `true`
- Page: https://kernstra.com/docs/web-sdk/config/forms/stepper

On a touch screen, a bar with Previous, Next and Done replaces the Tab key while a field has focus (a centered pill on a tablet). `false` leaves only the fields. See [Layout](https://kernstra.com/docs/web-sdk/config/layout) for how the stepper follows the keyboard.

```ts
forms: { stepper: false }
```

### `highlightColor`

- Type: `string`
- Default: `none given (the token's)`
- Page: https://kernstra.com/docs/web-sdk/config/forms/highlightcolor

Sets `--ks-field-bg`, the tint drawn over fillable fields; the hover shade is derived from it. A hex, a name or one color function, like [`theme.accentColor`](https://kernstra.com/docs/web-sdk/config/theme/accentcolor); anything else is ignored with a warning. Leave it out to keep the SDK's own tint, or set `--ks-field-bg` (and `--ks-field-bg-hover`, which the option derives for you) in CSS instead.

```ts
forms: { highlightColor: '#fff3c4' } // a warmer field tint
```

## Notes

**Saved appearance.** A filled value is saved as the field's appearance, in the font the form names for it. A form that names a font it never embeds and that no reader has (an Acrobat-made form often names `HelveticaLTStd-Bold` or `ArialMT`) is saved naming the standard face it stands for instead — `Helvetica-Bold`, `Times-Roman`, `Courier` — so Acrobat and every other reader draw the value in a real face with the right spacing.

**Date fields** are the ones Acrobat treats as dates: a text field whose format script is `AFDate_FormatEx` / `AFDate_Format`. Such a field with a whole date (no time) gets the date picker, and the date is stored in the field's own format (`dateFormat` on `FormFieldInfo`); `doc.forms.setValues` takes it in that format or as `YYYY-MM-DD`. A field named "Date" without the script is an ordinary text field. A date with a time, or without a day or year, is typed as text and stored in the field's format once entered (`3/4/26 2:05 pm` → `03/04/2026 14:05`).

**Formats, ranges and calculations.** A field that uses Acrobat's standard form functions behaves as it does in Acrobat: a number, currency or percentage (`AFNumber_Format`, `AFPercent_Format`) shows formatted — `$1,234.50`, `15.0%` — and as stored while it is edited; a percentage is typed as one (`15` or `15%`) and stored as Acrobat stores it, the fraction `0.15` (Acrobat itself would read a typed 15 as 1,500 %), which `doc.forms.setValues` takes too; a zip code, phone or social security number (`AFSpecial_Format`) and a masked field take only what fits; a time (`AFTime_Format`) is stored in its format; a value out of the field's range (`AFRange_Validate`) is refused with a message, and the field keeps what it held.

**Calculated fields.** A calculated field (`AFSimple_Calculate` — sum, product, average, minimum, maximum — Acrobat's simplified field notation, `Qty * Price`, or a hand-written calculation of the common shape, such as `var s = Number(this.getField("subtotal").value); event.value = s * 0.1;`) is calculated as the form opens, as Acrobat does, and recalculated whenever a field changes, each after those it reads, reporting [`formFieldChanged`](https://kernstra.com/docs/web-sdk/api/events#ev-formfieldchanged). A hand-written script is read only when it is nothing but variables set from field values, numbers and `+ - * /`, then the result; anything else in it and the field is not calculated. A download saves a formatted field as Acrobat does: its value as entered (`1234.5`), its appearance formatted (an encrypted file shows the value as entered). `FormFieldInfo` says what a field does (`format`, `range`, `calculation`) and how its widget draws its box (`look`): the viewer draws that box under the field's editor, in place of the widget's own appearance, which would show the value twice.

**The SDK runs no script.** These calls are recognized by their exact form and done by the SDK itself; a field with any other JavaScript (custom code) behaves as a plain field, and `doc.forms.setValues` takes a value only as typing could have entered it (a percentage as the fraction it is stored as).

**XFA forms.** A static XFA form ([`doc.forms.type()`](https://kernstra.com/docs/web-sdk/api/doc-forms/type) resolves to `'xfa-foreground'`) is filled through the AcroForm it is laid over; when a download changes a value, the XFA is removed so every reader, Acrobat included, shows what was entered, as Acrobat does when it saves a static form it filled. A dynamic XFA form (`'xfa'`) cannot lose its XFA: what is filled here goes into its AcroForm fallback, which other readers show, while Acrobat draws the XFA and its own values.


Web page: https://kernstra.com/docs/web-sdk/config/forms
