Frameworks
MLForm renders standard Web Components into an element you own, so every framework integration has
the same three steps: render an empty element, call mountForm once it is in the document, and call
unmount() when the component goes away.
import { mountForm } from "mlform";import { schema } from "./forecast-schema";import { transport } from "./transport";
const host = document.querySelector<HTMLElement>("#forecast-form");if (!host) throw new Error("Missing #forecast-form.");
const mounted = mountForm(host, { schema, transport });// When this view is removed, for example by your router:// mounted.unmount();import { useEffect, useRef } from "react";import { mountForm } from "mlform";import { schema } from "./forecast-schema";import { transport } from "./transport";
export function ForecastForm() { const host = useRef<HTMLDivElement>(null);
useEffect(() => { if (!host.current) return; const mounted = mountForm(host.current, { schema, transport }); return () => mounted.unmount(); }, []);
return <div ref={host} />;}Keep schema and transport stable (module constants or useMemo). If they are effect
dependencies, a new object on every render remounts the form and discards the user’s input.
<script setup lang="ts">import { onBeforeUnmount, onMounted, ref } from "vue";import { mountForm, type MountedForm } from "mlform";import { schema } from "./forecast-schema";import { transport } from "./transport";
const host = ref<HTMLElement | null>(null);let mounted: MountedForm | undefined;
onMounted(() => { if (host.value) mounted = mountForm(host.value, { schema, transport });});onBeforeUnmount(() => mounted?.unmount());</script>
<template> <div ref="host" /></template>import { AfterViewInit, Component, ElementRef, OnDestroy, ViewChild } from "@angular/core";import { mountForm, type MountedForm } from "mlform";import { schema } from "./forecast-schema";import { transport } from "./transport";
@Component({ selector: "app-forecast-form", template: `<div #host></div>`,})export class ForecastFormComponent implements AfterViewInit, OnDestroy { @ViewChild("host", { static: true }) host!: ElementRef<HTMLElement>; private mounted?: MountedForm;
ngAfterViewInit() { this.mounted = mountForm(this.host.nativeElement, { schema, transport }); }
ngOnDestroy() { this.mounted?.unmount(); }}<div id="forecast-form"></div>
<script> import { mountForm } from "mlform"; import { schema } from "../forms/forecast-schema"; import { transport } from "../forms/transport";
const host = document.querySelector<HTMLElement>("#forecast-form"); if (host) mountForm(host, { schema, transport });</script>Astro runs <script> tags in the browser once per page. Inside a framework island, use that
framework’s lifecycle instead.
Rules for every framework
Section titled “Rules for every framework”- Mount in the browser. Importing MLForm on the server is safe; mounting needs a DOM. Call
mountFormfrom a client-side lifecycle hook. - Hand MLForm an empty element and do not render children into it from the framework. To
mount over existing content intentionally, pass
containerStrategy: "replace"; the content returns onunmount(). - Mount once per element. Mounting again into the same element replaces the previous form once the new one is ready.
- Always unmount.
unmount()aborts pending work, disconnects the design system, and removes the host. Without it, listeners outlive the component. - Read state through subscriptions, not by remounting:
mounted.form.subscribeSelector(...)feeds framework state. See Live preview.
For pages where the framework should own every element, use the
headless view instead of mountForm.
Behind a router or in tabs
Section titled “Behind a router or in tabs”When the framework keeps a hidden view alive instead of unmounting it, call mounted.suspend()
on hide and mounted.resume() on show; pending work stops and the draft stays. For pages that only
care about browser visibility, mount with hostLifecycle: "document" to do this automatically on
visibilitychange and pagehide. See Lifecycle and hooks.
Iframes
Section titled “Iframes”Load MLForm inside the iframe first so its Web Components are registered there. A same-origin parent
can then call mountForm with a container from the iframe; theme media queries and focus use the
iframe’s window.
