Skip to content

Creating an action widget

An action widget fires events. It has no data binding — instead, it triggers named events via the on / onClick property that your application handles. The example we’ll walk through is productShare — a widget that renders social-network buttons (with brand icons) and fires a shareEvent when clicked, passing the chosen network as the event detail.

The widget accepts a networks prop so callers decide which buttons to render and which icons to use. Each entry has a stable id (sent as the click detail), a human label, and an optional icon — either an inline SVG string or a URL to an SVG/PNG.

import { gui } from '@golemui/gui-shared';
gui.actions.custom('productShare', {
onClick: 'shareEvent',
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',
},
],
});
{
"kind": "action",
"type": "productShare",
"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" }
}

When the widget calls click('twitter'), the form engine looks up onClick, finds 'shareEvent', and emits it through the formEvent callback with detail: 'twitter'. Your application code catches it there.

Key concepts (React)

  • useActionWidget returns uid, the merged templateData, and an onClick(detail?) callback that emits the wired event.
  • The event name ('shareEvent') lives in the widget definition, not in the component — so the same widget reuses across forms.
  • Pass the network id as onClick(network.id) so the consumer’s formEvent handler reads event.detail to know which button was clicked.
  • style={{ flex: templateData.size }} lets the action participate in flex parents — see Sizing custom widgets for the rationale.
ProductShare.tsx
import type { ActionWidget, WithWidget } from '@golemui/core';
import { useActionWidget } from '@golemui/react';
interface ShareNetwork {
id: string;
label: string;
icon?: string;
}
interface ProductShareProps {
networks?: ShareNetwork[];
}
const DEFAULT_NETWORKS: ShareNetwork[] = [
{ id: 'twitter', label: 'X' },
{ id: 'facebook', label: 'Facebook' },
{ id: 'reddit', label: 'Reddit' },
];
function isInlineSvg(icon: string) {
return icon.trim().startsWith('<svg');
}
export function ProductShare(widgetInstance: WithWidget) {
const widget = widgetInstance.widget as ActionWidget;
const { uid, templateData, onClick } =
useActionWidget<ProductShareProps>(widget);
const networks = templateData.networks ?? DEFAULT_NETWORKS;
return (
<div className="product-share" style={{ flex: templateData.size }}>
<div className="product-share__widget" id={uid}>
<span className="product-share__label">Share this product:</span>
<div className="product-share__buttons">
{networks.map((n) => (
<button
key={n.id}
type="button"
className="product-share__button"
onClick={() => onClick(n.id)}
title={n.label}
aria-label={n.label}
>
{n.icon ? (
isInlineSvg(n.icon)
? <span
className="product-share__icon"
dangerouslySetInnerHTML={{ __html: n.icon }}
/>
: <img className="product-share__icon" src={n.icon} alt="" aria-hidden />
) : (
<span className="product-share__network">{n.label}</span>
)}
</button>
))}
</div>
</div>
</div>
);
}

Click any of the share buttons to fire the shareEvent. The icons come from https://cdn.simpleicons.org, but you can pass any SVG string or URL via the networks prop.