Framework form components
Once your widget set has an authoring pipeline, one factory call per framework turns it into the equivalent of <GuiForm> / <gui-form>. React, Vue, and Lit expose createFormComponent; Angular uses dependency injection instead, with provideWidgetSet and a generic host component. The shipped gui components are built with exactly these calls, so every example below is the real source of a published package.
The widget set definition
Section titled “The widget set definition”All four factories take the same contract:
interface WidgetSetDefinition { /** type -> async component loader. Must include the reserved `flex` and `repeater` entries. */ widgetLoaders: WidgetLoaders; /** validator factory; receives the form's customValidators */ validators: (customValidators?) => ValidatorFn; /** produced by createImplementation; omit for a JSON-only widget set */ resolveFormInput?: ResolveFormInputFn; /** widget set defaults, lowest merge precedence */ dependencies?: Dependencies;}The framework factories keep only framework concerns (refs, emits, signals, custom element wiring). Everything else, resolving the form input and the config merge rules, is shared, so the four hosts cannot drift apart:
| Config | Merge order (last wins) |
|---|---|
| widget loaders | widget set built-ins, then DX-provided loaders, then the form’s customWidgetLoaders |
| dependencies | widget set defaults, then DX-provided, then the form’s |
| functions, item renderers | DX-provided, then the form’s |
validateOn | the form’s setting, then DX-provided, then 'eager' |
Creating the component
Section titled “Creating the component”createFormComponent returns a forwardRef component with the usual props (config, autocomplete, formEvent, formSubmit, formHealth, formHealthBoundary) and an imperative handle (setData, setMeta). This is the complete source of GuiForm in @golemui/gui-react:
import { createFormComponent } from '@golemui/react';import { type GuiFormInitConfig } from '@golemui/gui-shared';import { initValidators } from '@golemui/gui-validators';import { widgetLoaders } from './widget.loaders';
export const GuiForm = createFormComponent<GuiFormInitConfig>({ widgetLoaders, validators: initValidators, resolveFormInput,});GuiForm.displayName = 'GuiForm';The form bundle is memoized on the config reference, and handlers registered through the DX definitions are chained with your formEvent prop so both fire.
Angular components cannot be returned from a factory function, so the widget set arrives through dependency injection instead. @golemui/angular ships a generic <gui-widget-set-form> component (WidgetSetFormComponent) that injects the set via the GOLEMUI_WIDGET_SET token; provideWidgetSet(widgetSet) registers it at component, route, or application level.
The idiomatic move is a thin wrapper that pins the provider and the config type, which is exactly what @golemui/gui-angular does for <gui-form>:
import { Component, input, output, viewChild } from '@angular/core';import { provideWidgetSet, WidgetSetFormComponent, type AngularWidgetSet } from '@golemui/angular';import { type GuiFormInitConfig } from '@golemui/gui-shared';import { initValidators } from '@golemui/gui-validators';import { widgetLoaders } from '../widget.loaders';
const guiWidgetSet: AngularWidgetSet<GuiFormInitConfig> = { widgetLoaders, validators: initValidators, resolveFormInput,};
@Component({ imports: [WidgetSetFormComponent], selector: 'gui-form', providers: [provideWidgetSet(guiWidgetSet)], template: `<gui-widget-set-form [config]="config()" (formSubmit)="formSubmit.emit($event)" />`,})export class FormComponent { config = input.required<GuiFormInitConfig>(); formSubmit = output<FormSubmitEvent>();}The generic component exposes config, autocomplete, and formHealthBoundary inputs, the formEvent / formSubmit / formHealth outputs, and setData / setMeta methods. Its config input is intentionally untyped (WidgetSetFormInitConfig<any>); the typed config comes from your wrapper.
The Lit factory returns an element class that is not registered as a custom element: your package picks the tag name. This is the complete source of <gui-form> in @golemui/gui-lit:
import { customElement } from 'lit/decorators.js';import { createFormComponent } from '@golemui/lit';import { type GuiFormInitConfig } from '@golemui/gui-shared';import { initValidators } from '@golemui/gui-validators';import { widgetLoaders } from '../widget.loaders';
@customElement('gui-form')export class FormElement extends createFormComponent<GuiFormInitConfig>({ widgetLoaders, validators: initValidators, resolveFormInput,}) {}The element carries config, autocomplete, and formHealthBoundary properties plus setData / setMeta, renders into light DOM so your global styles apply, and bubbles the formEvent / formSubmit / formHealth events.
createFormComponent returns a Vue component with a config prop and the kebab-case form-event / form-submit / form-health emits, plus setData / setMeta on the exposed handle. This is the complete source of GuiForm in @golemui/gui-vue:
import { createFormComponent, type WidgetSetFormComponent } from '@golemui/vue';import { type GuiFormInitConfig } from '@golemui/gui-shared';import { initValidators } from '@golemui/gui-validators';import { widgetLoaders } from './widget.loaders';
export const GuiForm: WidgetSetFormComponent<GuiFormInitConfig> = createFormComponent<GuiFormInitConfig>({ widgetLoaders, validators: initValidators, resolveFormInput, });export default GuiForm;Vanilla JS rides the Lit factory: build the element class with createFormComponent from @golemui/lit, register it under your tag, and use it like any custom element.
import { createFormComponent } from '@golemui/lit';import { widgetLoaders } from './widget.loaders';
const MyForm = createFormComponent({ widgetLoaders, validators: initValidators, resolveFormInput,});customElements.define('my-form', MyForm);
const el = document.querySelector('my-form');el.config = { formDef };el.addEventListener('formSubmit', (e) => console.log(e.detail.data));Reusing the gui parts
Section titled “Reusing the gui parts”The contract is granular, so you can mix: widgetLoaders is a public export of each @golemui/gui-react / gui-vue / gui-lit / gui-angular package and initValidators of @golemui/gui-validators. A widget set that only swaps validators, or only adds loaders on top of the gui widgets, is a three-line definition rather than a fork.
A JSON-only widget set (no TS authoring namespace) simply omits resolveFormInput: JSON definitions pass through the decoder without the DX pipeline. For such a set the schema tree is the whole authoring story, so it is worth publishing one.
See also
Section titled “See also”- Authoring pipeline: produce the
resolveFormInputthese factories consume - JSON schemas: the other authoring surface, and the reserved contracts as they appear in JSON
- Widget Sets overview: the concept and how
guiis built - Configuration reference: everything the
configobject accepts at runtime