Skip to content

The web component

The <forms-engine> element is the one renderer behind every integration path — plain HTML, React, Angular. It’s framework-agnostic (built with Lit), shadow-DOM encapsulated, and needs exactly two attributes to run:

<script type="module" src="https://unpkg.com/@forms-engine/renderer/dist/forms-engine.esm.js"></script>
<forms-engine
public-id="q_a1b2c3d4e5"
api-base="https://forms.your-domain.com">
</forms-engine>

dist/forms-engine.esm.js is a single self-contained ES module (~160 KB, dependencies inlined) — you can equally copy it next to your page and serve it yourself instead of using a CDN; the Docker stack’s editor container even serves a copy at /embed/forms-engine.esm.js. In a build setup, import '@forms-engine/renderer' registers the element as a side effect. The import touches customElements immediately, so in SSR frameworks load it client-side only.

Attribute Default Purpose
public-id — The questionnaire’s public id, from the editor’s Embed panel. Required.
api-base — The backend’s browser-facing URL. Required. Trailing slashes are tolerated.
persist-session true Stores the response id in sessionStorage so a page refresh resumes the in-progress response against its pinned version. Only the exact string "false" disables it. Survives refresh and same-tab navigation — not browser restarts or new tabs.
completion-redirect — Absolute http(s) URL to navigate to after successful completion. Fires after fe-completed, so your handlers always run first. Relative URLs and non-http(s) schemes are ignored with a console warning.
labels — JSON object overriding the built-in UI strings per key, with English fallback for every key not supplied. Full reference: Labels.
external-ref — Opaque string (1–128 chars) identifying the respondent in your terms — a user id, order number, invite code. Captured at mount; mid-session changes are ignored with a warning. See External references.

A theme attribute also exists but is reserved — it currently does nothing. Theming works through CSS custom properties: Theming.

The element also has a definition property (no attribute): assign a questionnaire definition object directly and the component runs fully in-memory with no network — this is what powers the editor’s preview mode.

Nothing loads until both public-id and api-base are set, so you can set them asynchronously.

  1. If a persisted session exists, the component fetches that response first. Still in progress → it resumes: pinned definition, saved answers, and the screen the respondent left, with earlier screens marked visited so Back works. It emits fe-loaded then fe-resumed. Completed or deleted → the stored session is discarded and a fresh start follows.
  2. Otherwise it fetches the live published version and renders the first screen, emitting fe-loaded.
  3. With an external-ref under a one-per-reference policy, a completed reference short-circuits to the “already submitted” state instead of rendering (emitting fe-already-submitted).

A response is created lazily — on the first forward navigation or first file upload, not on mount — so page views don’t mint empty responses. Answers save per screen change (latest-wins, retried on transient failures), not per keystroke, and the session storage holds only the response id — never answers.

Respondents see one screen at a time. Next is blocked while the current screen has a required unanswered question, an invalid answer, or an upload still in flight — offending questions are highlighted, the first scrolls into view, and a screen-reader announcement fires. Back and jumps to already-visited screens (via the step indicator or tab rail) are never blocked.

Format errors (a bad email, an out-of-range number) show as the respondent types; required-field errors appear when they try to continue. On the last screen, Finish runs the same checks, saves, completes the response, and shows the completion state (or redirects, if configured).

Situation Respondent sees Your page gets
Unknown public-id “This questionnaire is not available.” fe-error
Network failure on load “The questionnaire could not be loaded.” fe-error
Answers failing to save (after retries) A persistent banner: “Your answers aren’t saving right now. We’ll keep trying — please don’t close this page.” The form stays usable. fe-error
Every question hidden by rules “There is nothing to fill in right now.” —
One-per-reference without an external-ref A loud configuration error (by design — a missing ref should fail on your first test, not silently) fe-error with code EXTERNAL_REF_REQUIRED
Reference already submitted “You’ve already submitted this form.” fe-already-submitted
Unknown question type (embed older than server) A neutral placeholder for that question; the rest of the form works and gating skips it fe-error with code UNKNOWN_QUESTION_TYPE, once per type

All the strings above are overridable.