Schema-driven forms for machine learning applications.
MLForm gives you a predictable UI layer between users and model backends. You describe inputs and reports with a schema, MLForm renders accessible Web Components, validates values, submits structured payloads, and displays model results in the same host container.
Version 0.1.11 is the current release in this repository.
Most ML product forms drift over time:
- the frontend shape stops matching the backend contract
- validation rules end up duplicated across components
- model outputs are rendered ad hoc in each screen
- design and accessibility regress when teams move fast
MLForm solves that by centering everything on an explicit schema and a transport layer.
Use it for:
- prediction forms
- scoring and approval tools
- forecasting dashboards
- internal review consoles
- embedded model workflows inside larger apps
For application usage:
npm install mlformImport from the explicit package subpath you need:
import { mountForm } from "mlform/kit";
import { createJsonTransport } from "mlform/transport";Create a host element:
<div id="prediction-form"></div>Mount a form:
import { mountForm } from "mlform/kit";
import { createJsonTransport } from "mlform/transport";
const container = document.querySelector("#prediction-form");
if (!container) {
throw new Error("Missing #prediction-form container.");
}
const mounted = mountForm(container as HTMLElement, {
transport: createJsonTransport({ endpoint: "/api/predict" }),
schema: {
fields: [
{
id: "prompt",
kind: "text",
label: "Prompt",
required: true,
minLength: 3,
},
{
id: "threshold",
kind: "number",
label: "Confidence threshold",
min: 0,
max: 1,
step: 0.05,
defaultValue: 0.75,
},
],
reports: [
{
id: "prediction",
kind: "classifier",
label: "Prediction",
},
],
},
labels: {
submit: "Run prediction",
submitting: "Running...",
},
layout: "split",
designSystem: {
mode: "auto",
theme: "cobalt",
recipe: "soft",
},
});
window.addEventListener("beforeunload", () => mounted.unmount());The default JSON transport sends:
{
"inputs": {
"prompt": "Example text",
"threshold": 0.75
}
}Return reports keyed by report id:
{
"reports": {
"prediction": {
"label": "Approved",
"confidence": 0.91,
"probabilities": {
"Approved": 0.91,
"Rejected": 0.09
}
}
},
"meta": {
"model": "credit-risk-v2"
}
}- Schema-driven fields, reports, conditions, defaults, and serialization
- Accessible Web Components for form inputs, submit actions, and result rendering
- Headless
createFormView()API for custom layouts and app-owned rendering - Official
mountForm()helper for step-based flows - Built-in JSON transport plus composable transport middleware
- Headless engine APIs for custom orchestration and registries
- Runtime design system with themes, recipes, density, motion, and token overrides
- Extension points for custom field and report kinds
Built-in fields:
textnumberbooleancategorydatetime-series
Built-in reports:
classifierregressor
Built-in themes:
neutralcobaltgraphitesagesunset
Built-in recipes:
defaultminimalsoftcontrast
| Surface | Use it for |
|---|---|
mlform/kit |
Explicit kit entrypoint with mountForm, createFormView, labels, layout, and lifecycle utilities. |
mlform/runtime |
Headless state, validation, registries, hooks, conditions, and submission orchestration. |
mlform/primitives |
Web Component renderers and custom renderer registries. |
mlform/design |
Themes, recipes, tokens, mode resolution, and host integration. |
mlform/transport |
Transport composition, middleware, resilience policies, and orchestration helpers. |
When built-in kinds are not enough, define your own field and report kinds without rewriting the normal rendering path.
import { createMlRegistryPack } from "mlform/builtins";
import { defineFieldKind, registerDefinedFieldKind } from "mlform/kit";
import { z } from "zod";
const pack = createMlRegistryPack();
registerDefinedFieldKind(
pack.registry,
pack.descriptorRegistry,
defineFieldKind({
kind: "score",
schema: z.object({
kind: z.literal("score"),
id: z.string().optional(),
label: z.string(),
min: z.number().default(0),
max: z.number().default(100),
}),
value: {
default: () => 0,
normalize: (value) => Number(value ?? 0),
serialize: (value) => value,
},
validate: ({ value, config }) =>
value < config.min || value > config.max ? ["Score out of range."] : [],
render: {
widget: "number",
hints: ({ config }) => ({
min: config.min,
max: config.max,
unit: "%",
}),
},
}),
);Stay at the declarative define*Kind layer unless you truly need fully custom rendering or low-level primitive behavior.
- Define the schema with
fieldsandreports. - Mount the form with
mountForm. - Point the transport at your model endpoint or custom backend adapter.
- Return normalized reports from the backend.
- Customize theme, recipe, labels, or registries only where your product needs it.
Use createFormView() when you want MLForm to own state and validation, but your app to own layout:
import { createFormView } from "mlform/kit";
import { createJsonTransport } from "mlform/transport";
const view = createFormView({
transport: createJsonTransport({ endpoint: "/api/predict" }),
schema,
layout: {
kind: "wizard",
steps: [
{ title: "Profile", children: [{ kind: "field", field: "name" }] },
{ title: "Review", children: [{ kind: "field", field: "email" }] },
],
},
});If you want a built-in step UI, use mountForm() with the same layout config.
- Docs home: https://ulloasp.github.io/mlform/
- Quick start: https://ulloasp.github.io/mlform/getting-started/quick-start/
- Installation: https://ulloasp.github.io/mlform/getting-started/installation/
- Backend contract: https://ulloasp.github.io/mlform/guides/backend-contract/
- Transport guide: https://ulloasp.github.io/mlform/kit/transport/
- Headless kit: https://ulloasp.github.io/mlform/kit/headless-kit/
- Wizard layout: https://ulloasp.github.io/mlform/kit/wizard-layout/
- Design system: https://ulloasp.github.io/mlform/design/overview/
- API reference: https://ulloasp.github.io/mlform/reference/kit/
- Migration guide: https://ulloasp.github.io/mlform/migration/from-legacy-mlform/
- Versioning notes: https://ulloasp.github.io/mlform/support/versioning/
This repository uses Vite+. Do not use npm, pnpm, or yarn directly for workspace tasks in this repo.
Run the main package checks:
vp install
vp check
vp test
vp buildDocs live in docs/:
cd docs
vp install
vp run typecheck
vp run build
vp run devThe main package targets Node.js >=24.9.0.
For 0.1.11, use the repository release entry and the published docs as the source of truth:
- GitHub releases: https://github.com/UlloaSP/mlform/releases
- npm package: https://www.npmjs.com/package/mlform
MIT