Reference
Components
Two components carry the whole public UI surface: the builder that produces a spec, and the renderer that turns a spec into a working form. Everything else in the package is an internal brick they compose.
Naming across the three integrations
Section titled “Naming across the three integrations”The same component is exposed three ways. Props are identical; only the event syntax changes:
<FkFormRender spec={spec} onFormSubmit={(e) => save(e.detail.data)} />Props are camelCase, events are onXxx handlers.
<FkFormRender :spec="spec" @form-submit="save($event.detail.data)" />Vue 3 camelizes event names in templates, so @form-submit and
@formSubmit both resolve to the emitted formSubmit.
el.spec = specel.addEventListener('formSubmit', (e) => save(e.detail.data))Events keep their raw camelCase name on the DOM element.
<fk-form-render> stable
Section titled “<fk-form-render> ”Renders a spec as a working form.
| Prop | Type | Default | Purpose |
|---|---|---|---|
spec |
BrickSpec |
— | Required. The form to render. |
data |
Record<string, unknown> |
{} |
Initial values, keyed by brick key. |
context |
Record<string, unknown> |
— | Runtime values for rules and URL interpolation. Never a field value, never emitted. |
readOnly |
boolean |
false |
Values cannot be changed: readonly on text controls, disabled elsewhere; hides the built-in action. |
disabled |
boolean |
false |
Renders every control unavailable — the disabled attribute throughout. |
showSubmit |
boolean |
false |
Render a built-in submit button. |
submitLabel |
string |
— | Overrides that button’s label. |
editable |
boolean |
false |
Render in builder mode (selection outlines, drop zones). Used by the builder itself. |
selectedPath |
string |
— | Which brick shows as selected in editable mode, by path. |
locale |
string |
— | Locale used to resolve localized labels and validation messages. |
annotations |
FieldAnnotations |
— | Reviewer notes, keyed by brick key: { incorrect?, comment? }. Shown under the matching fields. |
editableKeys |
string[] |
— | When set, only these keys stay editable; every other field renders read-only. |
notes |
FieldAnnotations |
— | An earlier review’s verdicts, shown as muted reminders under the fields, in any mode. |
Guided correction
Section titled “Guided correction”A reviewer’s verdict (see <fk-form-review> below) comes back as a map of
annotations. Hand it to the renderer and lock everything that was not flagged:
import { flaggedKeys } from '@streamline-pulse/formkrafter-core'
<FkFormRender spec={spec} data={previous} annotations={reviews} editableKeys={flaggedKeys(reviews)} />Flagged fields show a “To correct” badge and the comment under the control.
editableKeys applies to inputs and collections; the rows of an editable
collection stay fully editable.
Runtime context
Section titled “Runtime context”Forms often need values the host knows and the user must not supply: an API
host, a tenant plan, an auth token. Pass them as context — a bag kept
separate from form data:
<FkFormRender spec={spec} data={values} context={{ apiBase, tenant }} />Rules and optionsUrl / optionsHeaders interpolation read context and
data as one map, context winning on a name clash. Nothing in context is
ever validated, written by a value effect, or present in formDataChange /
formSubmit. The same prop exists on fk-form-builder, feeding its live
preview.
Template variables
Section titled “Template variables”Text and text-area fields can suggest template variables while typing. Pass
the list through the form context as _variables; typing {{ opens the
list, the characters after it filter by name or label, Enter or a click
inserts {{name}}. Set variables: false in a brick’s configs to opt one
field out. Password fields never suggest.
render.context = { _variables: [ { name: 'S1$email', label: 'Request · Email' }, { name: 'RECORD_ID', label: 'Case reference' }, ],};Events
Section titled “Events”| Event | Detail | Fires when |
|---|---|---|
formDataChange |
{ data, isValid, errors } |
Any field changes, and after validate() |
formSubmit |
{ data, isValid, errors } |
The form is submitted and valid. An invalid form never emits it, so isValid is always true and errors always empty. |
validityChange |
{ valid, errors } |
On first render and whenever the verdict changes |
interface DataChangeDetail { data: Record<string, unknown> isValid: boolean errors: Record<string, string> // keyed by brick key, e.g. "contacts[0].email"}Methods
Section titled “Methods”await element.validate(): Promise<ValidationResult>await element.submit(): Promise<ValidationResult>submit() validates, emits formSubmit only if the form is valid, and returns
the verdict either way.
Without a ref
Section titled “Without a ref”fk-form-render is a form-associated custom element and reports its validity
continuously, so a complete screen needs neither a ref nor a validate() call:
<form id="checkout"> <fk-form-render id="form"></fk-form-render></form><button type="submit" form="checkout">Send</button>The external button drives the element through the platform, and
checkValidity() on the form reflects the spec’s rules. Binding a button’s
disabled to validityChange works the same way with no form at all. Where
ElementInternals is unavailable the element behaves as it did before.
validate() marks every keyed brick as touched (so errors become visible),
validates the flat data, awaits row-level validation on every data grid, merges
the results and emits formDataChange. It returns { valid, errors }.
import { useRef } from 'react'import { FkFormRender } from '@streamline-pulse/formkrafter-react'
const ref = useRef<HTMLFkFormRenderElement>(null)const verdict = await ref.current?.validate()<script setup lang="ts">import { ref } from 'vue'const form = ref()// A template ref yields the Vue instance, not the custom element —// the component's methods live on $el.const check = async () => (await form.value.$el.validate()).valid</script>
<template> <FkFormRender ref="form" :spec="spec" /></template>const verdict = await document.querySelector('fk-form-render').validate()<fk-form-review> new
Section titled “<fk-form-review> ”Renders a filled form read-only and lets a reviewer flag each field as
incorrect and comment it. It delegates rendering to <fk-form-render>, so
nested forms, rules and localized labels behave the same.
| Prop | Type | Default | Purpose |
|---|---|---|---|
spec |
BrickSpec |
— | Required. The form that was filled. |
data |
Record<string, unknown> |
{} |
The values under review. |
context |
Record<string, unknown> |
— | Same as on the renderer. |
reviews |
FieldAnnotations |
{} |
Annotations to start from, for instance a draft of this pass. |
notes |
FieldAnnotations |
— | The previous pass’s verdicts, shown as reminders so the reviewer looks at those fields again; the new review starts empty. |
locale |
string |
— | Locale for labels. |
Events
Section titled “Events”| Event | Detail | Fires when |
|---|---|---|
reviewChange |
{ reviews, flagged } |
A flag or a comment changes. reviews is the full map, flagged the keys marked incorrect. |
<FkFormReview spec={spec} data={submitted} onReviewChange={(e) => setReviews(e.detail.reviews)} />Inside a data grid every cell is reviewed on its own: keys read
contacts[0].email, the same scheme as validation errors, and the grid itself
can also be flagged as a whole. editableKeys accepts those cell keys too;
when only some cells are listed, the row actions (add, remove, reorder) are
locked. The native renderer offers the same through FormReview and the
annotations, notes and editableKeys props of FormRenderer.
<fk-form-builder> stable
Section titled “<fk-form-builder> ”The drag & drop editor. It renders its own palette, canvas and property panel.
| Prop | Type | Default | Purpose |
|---|---|---|---|
spec |
BrickSpec |
— | Spec to load into the canvas. Omit to start from an empty form. |
data |
Record<string, unknown> |
{} |
Preview values used while editing. |
context |
Record<string, unknown> |
— | Runtime values for the live preview, same contract as on the renderer. |
locales |
string[] |
[] |
Enables the edit-language selector; panel fields then read and write per locale. |
locale |
string |
— | The builder chrome’s own language: pair it with setFkTranslations and the toolbar, palette and panel re-render on the fly — no remount needed. Distinct from the edit language. |
Events
Section titled “Events”| Event | Detail | Fires when |
|---|---|---|
specChange |
{ spec, patches, inverse } |
Any edit: drop, reorder, config change, undo, redo, import |
interface SpecChangeDetail { spec?: BrickSpec patches: Operation[] // RFC 6902, forward inverse: Operation[] // RFC 6902, backward}On undo, redo and full-spec replacement, patches and inverse are emitted as
empty arrays — the spec itself is authoritative in those cases.
Wiring both together
Section titled “Wiring both together”import { useState } from 'react'import { FkFormBuilder, FkFormRender } from '@streamline-pulse/formkrafter-react'import '@streamline-pulse/formkrafter-wc/styles.css'import type { BrickSpec } from '@streamline-pulse/formkrafter-core'
export function Studio() { const [spec, setSpec] = useState<BrickSpec>()
return ( <> <FkFormBuilder onSpecChange={(e) => setSpec(e.detail.spec)} /> {spec && ( <FkFormRender spec={spec} onFormSubmit={(e) => console.log(e.detail.data)} /> )} </> )}<script setup lang="ts">import { ref } from 'vue'import { FkFormBuilder, FkFormRender } from '@streamline-pulse/formkrafter-vue'import '@streamline-pulse/formkrafter-wc/styles.css'import type { BrickSpec } from '@streamline-pulse/formkrafter-core'
const spec = ref<BrickSpec>()</script>
<template> <FkFormBuilder @spec-change="spec = $event.detail.spec" /> <FkFormRender v-if="spec" :spec="spec" @form-submit="onSubmit($event.detail.data)" /></template><fk-form-builder></fk-form-builder><fk-form-render></fk-form-render>
<script type="module"> import '@streamline-pulse/formkrafter-wc/dist/formkrafter-wc/formkrafter-wc.esm.js' import '@streamline-pulse/formkrafter-wc/styles.css'
const builder = document.querySelector('fk-form-builder') const preview = document.querySelector('fk-form-render')
builder.addEventListener('specChange', (e) => { preview.spec = e.detail.spec }) preview.addEventListener('formSubmit', (e) => console.log(e.detail.data))</script>Next steps
Section titled “Next steps”A project by Streamline Pulse