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 |
Render every control disabled and hide 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. |
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.
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-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