Skip to content

Creating an input widget

An input widget binds to a path in the form data, supports validation, and tracks touched state. The framework helpers (useInputWidget in React, InputWidgetAdapter in Angular and Lit) handle data binding for you — your widget calls a few methods, the engine does the rest. The example we’ll walk through is productRating — a star-based rating control.

import { gui } from '@golemui/gui-shared';
gui.inputs.custom('productRating', 'product.rating', {
maxRating: 10,
validator: { type: 'number', required: true },
});

The maxRating prop controls how many stars are displayed. The second argument ('product.rating') tells the form engine where to store the selected value (a number from 1 to maxRating). The validator adds built-in number validation — in this case, making the field required.

  • useInputWidget (React) and InputWidgetAdapter (Angular / Lit) handle data binding, validation, and touched tracking automatically.
  • Call onValueChanged(newValue) to update the form data.
  • Call onBlur() to mark the field as touched and trigger validation (when validateOn includes 'blur').
  • errors is an array of validation error messages, and isTouched tracks whether the user has interacted with the field.
MethodPurpose
onValueChanged(value)Update the form data at the widget’s path.
onBlur()Mark the field as touched and trigger blur-time validation.
templateData.errorsArray of validation error messages for this field.
templateData.touchedWhether the user has interacted with the field.
injectValidationIssues(issues)Programmatically inject custom validation errors.
ProductRating.tsx
import * as Core from '@golemui/core';
import { useInputWidget } from '@golemui/react';
interface ProductRatingProps {
maxRating: number;
}
export function ProductRating(widgetInstance: Core.WithWidget) {
const widget = widgetInstance.widget as Core.InputWidget<number>;
const { uid, value, errors, isTouched, templateData, onValueChanged, onBlur } =
useInputWidget<number, ProductRatingProps>(widget);
const maxRating = templateData.maxRating || 5;
return (
<div className="product-rating" style={{ flex: templateData.size }}>
<div className="product-rating__widget" id={uid}>
<div className="product-rating__stars" onBlur={onBlur} tabIndex={0}>
{Array.from({ length: maxRating }, (_, i) => i + 1).map((star) => (
<span
key={star}
role="button"
className={`product-rating__star ${
star <= (value || 0) ? 'product-rating__star--active' : ''
}`}
onClick={() => onValueChanged(star)}
>
{star <= (value || 0) ? '' : ''}
</span>
))}
</div>
{isTouched && errors.length > 0 && (
<div className="product-rating__errors">
{errors.map((error, i) => (
<span key={i} className="product-rating__error">{error}</span>
))}
</div>
)}
</div>
</div>
);
}

useInputWidget returns:

  • value — the current value from the form data (type-safe via the generic <number>)
  • errors — validation error messages as string[]
  • isTouched — whether the user has interacted with the field
  • templateData — your custom props (maxRating) plus engine-managed properties (label, disabled, readonly, etc.)
  • onValueChanged(newValue) — updates the form data at the widget’s path
  • onBlur() — marks the field as touched and triggers validation
  • onFilter(value) — emits a filter event (useful for search/autocomplete widgets)
  • injectValidationIssues(issues) — programmatically inject custom validation errors

Try clicking the stars to select a rating, then click away to trigger validation: