Highlight required form fields

You can highlight required form fields that are empty using two approaches:

  • CSS styling — Apply styles through custom stylesheets using the :invalid pseudo-class. This approach doesn’t require the Form Creator license and provides a declarative way to style fields.
  • Programmatic styling — Update widget annotations directly using JavaScript for complete control over appearance. This approach requires the Form Creator license.

CSS styling approach

To style required form fields using CSS, follow the steps below.

  1. Set the required flag on form fields:

    // Retrieve all form fields.
    const formFields = await instance.getFormFields();
    // Set the `required` property for all form fields.
    const updatedFields = formFields.map((formField) =>
    formField.set("required", true),
    );
    // Update the form fields.
    await instance.update(updatedFields);

    When you set the required flag on NutrientViewer.FormFields.TextFormField, NutrientViewer.FormFields.ComboBoxFormField, and NutrientViewer.FormFields.ListBoxFormField, Nutrient renders them with the PSPDFKit-Annotation-Widget-Required CSS class and sets the HTML required attribute.

  2. Use a custom stylesheet to provide your custom CSS:

    const instance = await NutrientViewer.load({
    ...configuration,
    styleSheets: ["https://example.com/my-stylesheet.css"],
    });
  3. Use an :invalid pseudo-class to mark empty required fields:

    /* This matches empty required fields. */
    .PSPDFKit-Annotation-Widget-Required:invalid {
    border: 1px solid red;
    }
    /* This matches filled required fields. */
    .PSPDFKit-Annotation-Widget-Required {
    border: 1px solid green;
    }

Programmatic styling approach

To style required fields programmatically with full control over appearance, update the widget annotations directly. This approach requires the Form Creator license:

// Retrieve all form fields.
const formFields = await instance.getFormFields();
// Filter for required fields.
const requiredFields = formFields.filter((formField) => formField.required);
if (requiredFields.size === 0) {
console.warn("No required fields found in document");
return;
}
// Update widget styling for required fields.
const widgetUpdates = [];
for (const formField of requiredFields.toArray()) {
// Get all pages and find widgets for this form field.
for (let pageIndex = 0; pageIndex < instance.totalPageCount; pageIndex++) {
const annotations = await instance.getAnnotations(pageIndex);
const widgets = annotations.filter(
(ann) =>
ann instanceof NutrientViewer.Annotations.WidgetAnnotation &&
formField.annotationIds.includes(ann.id),
);
// Style each widget.
widgets.forEach((widget) => {
const updatedWidget = widget
.set("borderColor", new NutrientViewer.Color({ r: 255, g: 0, b: 0 }))
.set("borderWidth", 2);
widgetUpdates.push(updatedWidget);
});
}
}
// Apply all updates.
if (widgetUpdates.length > 0) {
await instance.update(widgetUpdates);
console.log(`Styled ${widgetUpdates.length} widget(s) for required fields`);
} else {
console.warn("No widgets found for required fields");
}

When working with documents that have many pages, consider caching annotations to improve performance. Refer to the iterate over form fields knowledge base article for an optimized caching pattern that loads all page annotations in parallel.