Skip to content

Custom element

<formbuilder-form form="YOUR-FORM-ID"></formbuilder-form>
<script type="module" src="https://forms.justinjongstra.nl/formbuilder/asset/element.js"></script>

The tag is a custom element, so the browser owns its lifecycle. That is why there is no @formbuilder/react, /vue or /svelte package: four wrapper packages would mean four build artifacts and four version matrices to keep in step, and the browser already does the job.

Attribute Default What it does
form required The form id
lang the form’s own Render a specific translation
api the script’s origin Point at a different host
isolate auto shadow to seal the form off from page CSS

Object values go on the property rather than an attribute, because an attribute can only carry a string:

document.querySelector('formbuilder-form').prefill = { email: 'ada@example.com' };

element.js is type="module" so the browser can fetch the renderer’s chunks in parallel and cache them independently. That also means it is fetched with CORS. The host serves it with Access-Control-Allow-Origin, which is worth knowing if you ever self-host the bundle behind a proxy that strips headers.

The form renders in your page’s DOM, which is what lets it inherit your fonts and colours. The price is that a broad enough selector on your side can reach inside it.

This documentation hit exactly that. Starlight styles prose with a rule that adds a top margin to any element following a sibling:

.sl-markdown-content :not(a, strong, em, …) + :not(a, strong, em, …) {
margin-top: var(--sl-content-gap-y);
}

Aimed at paragraphs, but the form’s labels and inputs are descendants of that container too, so one field in a row picked up a content gap its neighbour did not, and sat a line lower. It looked like a bug in the form and was not.

Two ways out, depending on whose CSS it is.

If you control the page, exempt the form from the offending rule. Starlight has not-content for precisely this, and most systems have an equivalent:

<div class="not-content">
<formbuilder-form form="YOUR-FORM-ID"></formbuilder-form>
</div>

If you do not (a CMS theme, a page builder, anything you cannot edit), seal the form off instead:

<formbuilder-form form="YOUR-FORM-ID" isolate="shadow"></formbuilder-form>

isolate="shadow" renders the form in a shadow root. No selector on the page can reach it, and nothing it does leaks out.