Widget Loaders
Widget loaders are a Record<string, () => Promise<unknown>> map that tells the form engine how to lazy-load each widget kind. The map key is the widget type (e.g. 'textinput', 'dropdown') and the value is an async loader that returns the widget implementation.
Each framework adapter ships the GolemUI widget set as a default widgetLoaders export. You don’t need to pass them yourself — <GuiForm> includes the framework defaults automatically. You only wire widgetLoaders (DX path) or customWidgetLoaders (JSON path) when you have your own custom widgets to register.
Default loaders
Section titled “Default loaders”The built-in widget set covers every input, layout, display, and action widget. A form that uses only built-in widgets needs nothing but formDef:
import { gui } from '@golemui/gui-shared';import { GuiForm } from '@golemui/gui-react';
const formDef = [gui.inputs.textInput('username')];
export function MyForm() { return <GuiForm config={{ formDef }} />;}import { Component } from '@angular/core';import { CommonModule } from '@angular/common';import { gui } from '@golemui/gui-shared';import { FormComponent } from '@golemui/gui-angular';
@Component({ imports: [CommonModule, FormComponent], selector: 'app-my-form', template: `<gui-form [config]="config"></gui-form>`,})export class MyForm { protected config = { formDef: [gui.inputs.textInput('username')] };}import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import { gui } from '@golemui/gui-shared';import '@golemui/gui-lit';
@customElement('my-form')export class MyForm extends LitElement { config = { formDef: [gui.inputs.textInput('username')] };
override createRenderRoot() { return this; }
override render() { return html`<gui-form .config=${this.config}></gui-form>`; }}<script setup lang="ts">import { gui } from '@golemui/gui-shared';import { GuiForm } from '@golemui/gui-vue';
const config = { formDef: [gui.inputs.textInput('username')] };</script>
<template> <GuiForm :config="config" /></template>import '@golemui/gui-components/index.css';import '@golemui/gui-lit';import { gui } from '@golemui/gui-shared';
const form = document.getElementById('app-form');form.config = { formDef: [gui.inputs.textInput('username')] };import { GuiForm } from '@golemui/gui-react';import formDef from './my-form.json';
export function MyForm() { return <GuiForm config={{ formDef }} />;}import { Component } from '@angular/core';import { CommonModule } from '@angular/common';import { FormComponent } from '@golemui/gui-angular';import formDef from './my-form.json';
@Component({ imports: [CommonModule, FormComponent], selector: 'app-my-form', template: `<gui-form [config]="config"></gui-form>`,})export class MyForm { protected config = { formDef };}import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-lit';import formDef from './my-form.json';
@customElement('my-form')export class MyForm extends LitElement { config = { formDef };
override createRenderRoot() { return this; }
override render() { return html`<gui-form .config=${this.config}></gui-form>`; }}<script setup lang="ts">import { GuiForm } from '@golemui/gui-vue';import formDef from './my-form.json';
const config = { formDef };</script>
<template> <GuiForm :config="config" /></template>import '@golemui/gui-components/index.css';import '@golemui/gui-lit';import formDef from './my-form.json';
const form = document.getElementById('app-form');form.config = { formDef };Adding custom widgets
Section titled “Adding custom widgets”Pass only your own loader entries — <GuiForm> merges them with the built-in defaults internally. The key (e.g. productCard) must match the type field on the widget instances in your formDef.
In the Programmatic API your loaders go into formConfig.widgetLoaders.
import { gui } from '@golemui/gui-shared';import { GuiForm } from '@golemui/gui-react';
const config = { formDef: [ gui.layouts.custom('productCard', [/* children */], { title: 'My product' }), ], formConfig: { widgetLoaders: { productCard: async () => (await import('./widgets/ProductCard')).ProductCard, }, },};
export function MyForm() { return <GuiForm config={config} />;}import { Component } from '@angular/core';import { CommonModule } from '@angular/common';import { gui } from '@golemui/gui-shared';import { FormComponent } from '@golemui/gui-angular';
@Component({ imports: [CommonModule, FormComponent], selector: 'app-my-form', template: `<gui-form [config]="config"></gui-form>`,})export class MyForm { protected config = { formDef: [ gui.layouts.custom('productCard', [/* children */], { title: 'My product' }), ], formConfig: { widgetLoaders: { productCard: async () => (await import('./widgets/product-card.component')).ProductCardComponent, }, }, };}import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import { gui } from '@golemui/gui-shared';import '@golemui/gui-lit';
@customElement('my-form')export class MyForm extends LitElement { config = { formDef: [ gui.layouts.custom('productCard', [/* children */], { title: 'My product' }), ], formConfig: { widgetLoaders: { productCard: async () => (await import('./widgets/product-card.element')).ProductCardElement, }, }, };
override createRenderRoot() { return this; }
override render() { return html`<gui-form .config=${this.config}></gui-form>`; }}<script setup lang="ts">import { gui } from '@golemui/gui-shared';import { GuiForm } from '@golemui/gui-vue';
const config = { formDef: [ gui.layouts.custom('productCard', [/* children */], { title: 'My product' }), ], formConfig: { widgetLoaders: { productCard: async () => (await import('./widgets/ProductCard.vue')).default, }, },};</script>
<template> <GuiForm :config="config" /></template>import '@golemui/gui-components/index.css';import '@golemui/gui-lit';import { gui } from '@golemui/gui-shared';
const form = document.getElementById('app-form');form.config = { formDef: [ gui.layouts.custom('productCard', [/* children */], { title: 'My product' }), ], formConfig: { widgetLoaders: { productCard: async () => (await import('./widgets/product-card.element')).ProductCardElement, }, },};In the JSON path your loaders go into customWidgetLoaders on the config object. The JSON itself references the widget by "type": "productCard".
{ "form": [ { "kind": "layout", "type": "productCard", "props": { "title": "My product" }, "items": [] } ]}import { GuiForm } from '@golemui/gui-react';import formDef from './my-form.json';
const config = { formDef, customWidgetLoaders: { productCard: async () => (await import('./widgets/ProductCard')).ProductCard, },};
export function MyForm() { return <GuiForm config={config} />;}import { Component } from '@angular/core';import { CommonModule } from '@angular/common';import { FormComponent } from '@golemui/gui-angular';import formDef from './my-form.json';
@Component({ imports: [CommonModule, FormComponent], selector: 'app-my-form', template: `<gui-form [config]="config"></gui-form>`,})export class MyForm { protected config = { formDef, customWidgetLoaders: { productCard: async () => (await import('./widgets/product-card.component')).ProductCardComponent, }, };}import { LitElement, html } from 'lit';import { customElement } from 'lit/decorators.js';import '@golemui/gui-lit';import formDef from './my-form.json';
@customElement('my-form')export class MyForm extends LitElement { config = { formDef, customWidgetLoaders: { productCard: async () => (await import('./widgets/product-card.element')).ProductCardElement, }, };
override createRenderRoot() { return this; }
override render() { return html`<gui-form .config=${this.config}></gui-form>`; }}<script setup lang="ts">import { GuiForm } from '@golemui/gui-vue';import formDef from './my-form.json';
const config = { formDef, customWidgetLoaders: { productCard: async () => (await import('./widgets/ProductCard.vue')).default, },};</script>
<template> <GuiForm :config="config" /></template>import '@golemui/gui-components/index.css';import '@golemui/gui-lit';import formDef from './my-form.json';
const form = document.getElementById('app-form');form.config = { formDef, customWidgetLoaders: { productCard: async () => (await import('./widgets/product-card.element')).ProductCardElement, },};Widget loaders use dynamic import() so custom widgets are lazy-loaded — they’re only fetched when the form engine encounters them.
See also
Section titled “See also”- Extending GolemUI / Widgets — building your own widget implementations.
- Form Definition API / Custom Widgets — declaring custom widget instances in the form.