FS FormScript

Renderers

FormScript does not describe a page. It describes a form, and a renderer turns that form into whatever the platform wants: a web page, a phone screen, a kiosk, a printable document.

The rule that makes this work is in §15.2: FormScript emits intents, never markup. A runtime produces a tree of intents, and a renderer decides what each one becomes. The language never says "a <div>"; it says "a group".

The intents

There are fourteen of them, and they are the whole interface between a runtime and a renderer.

Intent What it is
RenderTextInput free text
RenderNumberInput a number, formatted for the locale
RenderChoice a constrained selection
RenderMultiSelect several of many
RenderDate a date
RenderToggle a boolean
RenderFile an attachment
RenderSignature a signature
RenderLocation a place
RenderGroup a labelled composite
RenderRepeat a list that can grow
RenderProgress how far through
RenderErrorSummary why the submit failed
RenderMessage an inline message

A renderer must map every intent it receives. An unmapped intent is a conformance failure, and the runtime surfaces it as a warning rather than silently dropping the field — a form that quietly loses a question is worse than one that complains. checkIntentCoverage is what checks this.

A renderer may map an intent to a different widget, as long as the mapping preserves the intent's accessibility, validation and keyboard semantics. One mapping is forbidden outright: RenderChoice must not become a control that cannot be operated by the keyboard alone.

The obligations

Spec-15.4 says a Level 2+ renderer must:

  • maintain a labelled control for every active field;
  • expose field state to assistive technology per §13;
  • never render a value in a way that omits its label;
  • handle the pending source state without trapping focus;
  • preserve field identity across re-renders, so focus is not lost mid-typing.

Spec-15.5 adds the rule that catches the usual mistake: a renderer must preserve input focus and cursor position across a state propagation. A renderer that re-renders the whole form on every keystroke and loses focus is non-conforming.

checkRenderObligations checks a rendered tree against these.

The renderers that exist

Package What it does The API
@formscript/render The intents themselves: how a field becomes one, and the checks above computeRenderIntent, findRenderNode, checkIntentCoverage, checkRenderObligations
@formscript/render-dom The browser binding. It owns the DOM, and it reconciles rather than redraws renderForm, createWidget, reconcile, setControlValue
@formscript/render-html The server-side binding: HTML for a form, and the posted form read back into a transaction renderFormHtml, renderControl, readPostedForm, postedModelPaths

The DOM binding is the one that powers the playground and the live form. The HTML binding is what makes a form work with no JavaScript at all — see server rendering.

Neither of them is "the" renderer. They are two answers to the same contract, which is the point of having a contract.

What does not exist yet

No renderer is written for PDF, for printing, for React Native, for Android, or for email. §15.1 names a web renderer and React Native as what level 2 looks like in practice, and only the web one is here.

Writing another one is a bounded job: compute the intents, map each one to a widget, and honour the obligations. The language does not change, and neither does the runtime — a renderer is a consumer of the runtime, not a part of it.

See also