SpectreSkills
← All skills

form-web-component

v1.0.0

Publish a form as framework-free custom elements that fetch their configuration and post submissions. Trigger when embedding a form on a page the builder does not control — a storefront, a CMS page, or any other host.

Formsformsweb-componentsembed
Install
npx @spectre-apps/skills add form-web-component

Writes .claude/skills/form-web-component/SKILL.md. Add --user to install globally.

What it does

Form web components

The published form is a pair of custom elements with no framework runtime and no admin UI kit. The host page supplies a form id and an API origin. The same script is what form-preview mounts inside the builder.

<script src="https://forms.example.com/form-embed.js" defer></script>
<link rel="stylesheet" href="https://forms.example.com/form-embed.css" />

<form-embed form-id="frm_123" api-base="https://forms.example.com/api/forms"></form-embed>

<form-embed-button
  form-id="frm_123"
  api-base="https://forms.example.com/api/forms"
  button-text="Contact us"
></form-embed-button>

Prefix the tag names per product (spectre-form, and so on). The attributes and the API below stay the same across hosts.

Elements

  • <form-embed> renders the form inline. Attributes: form-id (required), api-base, locale.
  • <form-embed-button> renders a button that opens the same form in a dialog. Extra attributes are presentation only: button-text, button-background, button-text-color. The dialog contains one <form-embed>.

Resolve api-base in this order: the attribute, then a global the host page sets (window.FORM_API_BASE), then same-origin /api/forms. A preview iframe, a CMS page, and a storefront theme can each point at a different origin without rebuilding the script.

form-id missing: render a short error inside the element and stop. Fetch failure: the same, with a retry. Leave the host page alone.

API

Public, unauthenticated, CORS open to arbitrary embed origins.

CallBodyReturns
GET {apiBase}/{id}the published FormDefinition for the requested locale
POST {apiBase}/{id}/submit{ fields, locale }{ ok: true } or { ok: false, fieldErrors }

GET serves LIVE forms only. Drafts 404 here; the builder uses its own preview read (see form-preview).

fields is a map of submission key → string, string array (multi-select, checkboxes), or file id. The server checks required fields, option membership, and conditional visibility (a hidden required field is not required). On success it persists the submission, then hands off to form-integrations. The response the element shows is successMessage from the definition.

Behavior inside the element

The element owns drawing: rows from the width packer, controls from each field's control, conditional visibility, and the stepper when steps has more than one entry. Previous / next move steps; submit exists on the last step.

On submit, POST once, disable the button while in flight, and render either field errors beside their controls or the success message in place of the form. A second submit after success starts over only if the product wants that; the default is to leave the success state up.

File fields request POST {apiBase}/{id}/uploads for a presigned PUT, upload the bytes to object storage, and submit the returned file id. The submit body never contains the file.

View and interaction beacons (view, started, field_focus, step_change) are optional POSTs to a sibling path. They never block render or submit.

Styles and build

The element carries its own stylesheet, class names prefixed, so the host page cannot restyle the form by accident and the form cannot leak into the host. Theme values from the definition (colors, radius, custom CSS) are applied as variables on the element.

Ship the script and CSS as build artifacts at a stable public URL. Edit the source and rebuild. A host that hotlinks the URL picks up the new renderer without a redeploy of the page.

The elements are the only public runtime. The builder stays in whatever framework the admin app uses.