Skip to content

Custom Widgets

Each shortcut group exposes a .custom() factory for widgets you’ve implemented yourself. The shape mirrors the built-in shortcuts but takes the widget type as the first argument.

import { gui } from '@golemui/gui-shared';
// Custom layout
gui.layouts.custom(
'productCard',
[
/* children */
],
{ title: 'My Product' },
);
// Custom input
gui.inputs.custom('productRating', 'product.rating', {
maxRating: 10,
validator: { type: 'number', required: true },
});
// Custom display
gui.displays.custom('productDescription', {
img: 'assets/product.png',
description: '### Cool product',
});
// Custom action
gui.actions.custom('productShare', { onClick: 'shareEvent' });

The first arg is the widget type string — it must match the key you register in formConfig.widgetLoaders.

Custom inputs accept a validator prop typed as the union of all built-in validator types. Use { type: 'string'/'number'/'boolean'/'array'/'custom', ... }.

Custom widgets really shine when you compose them. The Product Card below — built throughout the Extending GolemUI tutorial — combines all four kinds in a single form: a custom layout (productCard) wrapping a custom display (productDescription), a custom input (productRating), and a custom action (productShare). It also mixes in built-in widgets (textarea, button) without any special wiring.

Here’s the full form definition that produces the demo:

import { gui } from '@golemui/gui-shared';
const formDef = [
gui.layouts.custom(
'productCard',
[
gui.displays.custom('productDescription', {
img: 'https://picsum.photos/seed/golemui/240/240',
description: '### GolemUI\nThe declarative form engine.',
}),
gui.inputs.custom('productRating', 'product.rating', {
label: 'Your rating',
maxRating: 10,
validator: {
type: 'number',
required: true,
messages: { required: 'Please rate the product before submitting' },
},
}),
gui.inputs.textarea('product.comment', {
label: 'Your comment',
hint: 'Enter your comment (maximum 500 characters)',
placeholder: 'Let us know why you love our product',
counterMode: 'current',
validator: {
required: true,
maxLength: 500,
messages: {
required: 'Please leave a comment with your rating',
maxLength:
'Your comment is too long — keep it under 500 characters',
},
},
}),
gui.actions.custom('productShare', {
label: 'Share',
networks: [
{
id: 'twitter',
label: 'X',
icon: 'https://cdn.simpleicons.org/x/333',
},
{
id: 'facebook',
label: 'Facebook',
icon: 'https://cdn.simpleicons.org/facebook/1877F2',
},
{
id: 'reddit',
label: 'Reddit',
icon: 'https://cdn.simpleicons.org/reddit/FF4500',
},
],
onClick: 'shareEvent',
}),
gui.actions.button({
label: 'Submit',
icon: 'save',
iconPosition: 'right',
actionType: 'submit',
}),
],
{ title: 'Rate Our Product' },
),
];
{
"kind": "layout",
"type": "productCard",
"props": { "title": "Rate Our Product" },
"children": [
{
"kind": "display",
"type": "productDescription",
"props": {
"img": "https://picsum.photos/seed/golemui/240/240",
"description": "### GolemUI\nThe declarative form engine."
}
},
{
"kind": "input",
"type": "productRating",
"path": "product.rating",
"label": "Your rating",
"props": { "maxRating": 10 },
"validator": {
"type": "number",
"required": true,
"messages": { "required": "Please rate the product before submitting" }
}
},
{
"kind": "input",
"type": "textarea",
"path": "product.comment",
"label": "Your comment",
"props": {
"hint": "Enter your comment (maximum 500 characters)",
"placeholder": "Let us know why you love our product",
"counterMode": "current"
},
"validator": {
"type": "string",
"required": true,
"maxLength": 500,
"messages": {
"required": "Please leave a comment with your rating",
"maxLength": "Your comment is too long — keep it under 500 characters"
}
}
},
{
"kind": "action",
"type": "productShare",
"label": "Share",
"props": {
"networks": [
{
"id": "twitter",
"label": "X",
"icon": "https://cdn.simpleicons.org/x/333"
},
{
"id": "facebook",
"label": "Facebook",
"icon": "https://cdn.simpleicons.org/facebook/1877F2"
},
{
"id": "reddit",
"label": "Reddit",
"icon": "https://cdn.simpleicons.org/reddit/FF4500"
}
]
},
"on": { "click": "shareEvent" }
},
{
"kind": "action",
"type": "button",
"label": "Submit",
"props": { "icon": "save", "iconPosition": "right" },
"on": { "click": "submit" }
}
]
}

And the result — fully interactive:

The full per-framework implementation — React hooks, Angular adapters, Lit elements, plus how to register and event-handle each kind — lives in Extending GolemUI / Widgets Overview and the four kind-specific chapters under it.