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.
Attributes
Section titled “Attributes”| 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.
What happens at mount
Section titled “What happens at mount”- 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-loadedthenfe-resumed. Completed or deleted → the stored session is discarded and a fresh start follows. - Otherwise it fetches the live published version and renders the first
screen, emitting
fe-loaded. - With an
external-refunder a one-per-reference policy, a completed reference short-circuits to the “already submitted” state instead of rendering (emittingfe-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.
Navigation & validation behavior
Section titled “Navigation & validation behavior”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).
Error states
Section titled “Error states”| 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.