Skip to content
Tech News
← Back to articles

Show HN: GolemUI – Declarative Form Engine

read original more articles
Why This Matters

GolemUI introduces a declarative form engine built with React Hook Form, simplifying form creation with dynamic validation and conditional fields. This approach enhances developer efficiency and improves user experience by making complex forms easier to manage and customize. Its flexibility benefits both developers and consumers by enabling more responsive and tailored form interactions.

Key Takeaways

import { useForm, useWatch } from 'react-hook-form' ; type FormData = { email : string ; password : string ; accountType : 'Free' | 'Pro' | 'Enterprise' ; companyName ?: string ; seats ?: number ; subscribe : boolean ; frequency ?: string ; terms : boolean ; }; export function SignupForm () { const { register , control , handleSubmit , formState : { errors } } = useForm < FormData >({ defaultValues: { accountType: 'Free' , subscribe: false , terms: false }, }); const accountType = useWatch ({ control, name: 'accountType' }); const subscribe = useWatch ({ control, name: 'subscribe' }); return ( < form onSubmit = { handleSubmit (( data ) => console. log (data))}> < label >Email < input type = "email" { ... register ( 'email' , { required: true , pattern: / ^ [ ^ \s@] + @ [ ^ \s@] + \. [ ^ \s@] +$ / , })} /> {errors.email && < span >Valid email required</ span >} </ label > < label >Password < input type = "password" { ... register ( 'password' , { required: true , minLength: 8 , })} /> {errors.password && < span >Min 8 characters</ span >} </ label > < label >Account type < select { ... register ( 'accountType' , { required: true })}> < option >Free</ option > < option >Pro</ option > < option >Enterprise</ option > </ select > </ label > {accountType !== 'Free' && ( < label >Company name < input { ... register ( 'companyName' , { required: true })} /> {errors.companyName && < span >Required</ span >} </ label > )} {accountType === 'Enterprise' && ( < label >Seats < input type = "number" { ... register ( 'seats' , { required: true , min: 5 , max: 1000 , valueAsNumber: true , })} /> {errors.seats && < span >Between 5 and 1000</ span >} </ label > )} < label > < input type = "checkbox" { ... register ( 'subscribe' )} /> Subscribe to product updates </ label > {subscribe && ( < label >Frequency < select { ... register ( 'frequency' , { required: true })}> < option >Daily</ option > < option >Weekly</ option > < option >Monthly</ option > </ select > </ label > )} < label > < input type = "checkbox" { ... register ( 'terms' , { validate : ( v ) => v === true || 'Required' , })} /> I accept the terms of service </ label > < button type = "submit" >Sign up</ button > </ form > ); }

import { Component, signal } from '@angular/core' ; import { form, Control, schema, required, email, minLength, validate, } from '@angular/forms/signals' ; interface Signup { email : string ; password : string ; accountType : 'Free' | 'Pro' | 'Enterprise' ; companyName : string ; seats : number | null ; subscribe : boolean ; frequency : 'Daily' | 'Weekly' | 'Monthly' ; terms : boolean ; } const signupSchema = schema < Signup >(( data ) => { required (data.email); email (data.email); required (data.password); minLength (data.password, 8 ); validate (data.companyName, ({ value , valueOf }) => valueOf (data.accountType) !== 'Free' && ! value () ? { kind: 'required' } : null ); validate (data.seats, ({ value , valueOf }) => { if ( valueOf (data.accountType) !== 'Enterprise' ) return null ; const v = value (); return v != null && v >= 5 && v <= 1000 ? null : { kind: 'seats' }; }); validate (data.terms, ({ value }) => value () ? null : { kind: 'mustAccept' }); }); @ Component ({ selector: 'app-signup' , imports: [Control], template: ` <form (submit)="onSubmit($event)"> <label>Email <input [control]="f.email" type="email" /> </label> <label>Password <input [control]="f.password" type="password" /> </label> <label>Account type <select [control]="f.accountType"> <option>Free</option> <option>Pro</option> <option>Enterprise</option> </select> </label> @if (data().accountType !== 'Free') { <label>Company name <input [control]="f.companyName" /> </label> } @if (data().accountType === 'Enterprise') { <label>Seats <input [control]="f.seats" type="number" min="5" max="1000" /> </label> } <label> <input [control]="f.subscribe" type="checkbox" /> Subscribe to product updates </label> @if (data().subscribe) { <label>Frequency <select [control]="f.frequency"> <option>Daily</option> <option>Weekly</option> <option>Monthly</option> </select> </label> } <label> <input [control]="f.terms" type="checkbox" /> I accept the terms of service </label> <button type="submit" [disabled]="!f().valid()">Sign up</button> </form> ` , }) export class SignupForm { data = signal < Signup >({ email: '' , password: '' , accountType: 'Free' , companyName: '' , seats: null , subscribe: false , frequency: 'Daily' , terms: false , }); f = form ( this .data, signupSchema); onSubmit ( e : Event ) { e. preventDefault (); if ( this . f (). valid ()) console. log ( this . data ()); } }

import { LitElement, html } from 'lit' ; import { customElement, state } from 'lit/decorators.js' ; type Account = 'Free' | 'Pro' | 'Enterprise' ; @ customElement ( 'signup-form' ) export class SignupForm extends LitElement { @ state () email = '' ; @ state () password = '' ; @ state () accountType : Account = 'Free' ; @ state () companyName = '' ; @ state () seats : number | null = null ; @ state () subscribe = false ; @ state () frequency = 'Daily' ; @ state () terms = false ; @ state () errors : Record < string , string > = {}; private bind = ( key : keyof this ) => ( e : Event ) => { const t = e.target as HTMLInputElement ; ( this as any )[key] = t.type === 'checkbox' ? t.checked : t.value; }; private validate () { const e : Record < string , string > = {}; if ( ! / ^ [ ^ \s@] + @ [ ^ \s@] + \. [ ^ \s@] +$ / . test ( this .email)) e.email = 'Valid email' ; if ( this .password. length < 8 ) e.password = 'Min 8 characters' ; if ( this .accountType !== 'Free' && ! this .companyName) e.companyName = 'Required' ; if ( this .accountType === 'Enterprise' ) { const s = this .seats; if (s == null || s < 5 || s > 1000 ) e.seats = 'Between 5 and 1000' ; } if ( this .subscribe && ! this .frequency) e.frequency = 'Required' ; if ( ! this .terms) e.terms = 'Required' ; return e; } private onSubmit ( ev : Event ) { ev. preventDefault (); this .errors = this . validate (); if ( ! Object. keys ( this .errors). length ) console. log ( this ); } render () { return html ` <form @submit=${ this . onSubmit }> <label>Email <input type="email" .value=${ this . email } @input=${ this . bind ( 'email' ) } /> ${ this . errors . email ? html `<span>${ this . errors . email }</span>` : null } </label> <label>Password <input type="password" .value=${ this . password } @input=${ this . bind ( 'password' ) } /> ${ this . errors . password ? html `<span>${ this . errors . password }</span>` : null } </label> <label>Account type <select .value=${ this . accountType } @change=${ this . bind ( 'accountType' ) }> <option>Free</option><option>Pro</option><option>Enterprise</option> </select> </label> ${ this . accountType !== 'Free' ? html ` <label>Company name <input .value=${ this . companyName } @input=${ this . bind ( 'companyName' ) } /> </label>` : null } ${ this . accountType === 'Enterprise' ? html ` <label>Seats <input type="number" .value=${ this . seats ?? ''} @input=${ this . bind ( 'seats' ) } /> </label>` : null } <label> <input type="checkbox" .checked=${ this . subscribe } @change=${ this . bind ( 'subscribe' ) } /> Subscribe to product updates </label> ${ this . subscribe ? html ` <label>Frequency <select .value=${ this . frequency } @change=${ this . bind ( 'frequency' ) }> <option>Daily</option><option>Weekly</option><option>Monthly</option> </select> </label>` : null } <label> <input type="checkbox" .checked=${ this . terms } @change=${ this . bind ( 'terms' ) } /> I accept the terms of service </label> <button type="submit">Sign up</button> </form> ` ; } }

< script setup lang = "ts" > import { useForm, useField } from 'vee-validate' ; import * as yup from 'yup' ; type Account = 'Free' | 'Pro' | 'Enterprise' ; const schema = yup. object ({ email: yup. string (). email (). required (), password: yup. string (). min ( 8 ). required (), accountType: yup. mixed < Account >(). oneOf ([ 'Free' , 'Pro' , 'Enterprise' ]). required (), companyName: yup. string (). when ( 'accountType' , { is : ( t : Account ) => t !== 'Free' , then : ( s ) => s. required (), }), seats: yup. number (). when ( 'accountType' , { is: 'Enterprise' , then : ( s ) => s. required (). min ( 5 ). max ( 1000 ), }), subscribe: yup. boolean (), frequency: yup. string (). when ( 'subscribe' , { is: true , then : ( s ) => s. required (), }), terms: yup. boolean (). oneOf ([ true ], 'Required' ), }); const { handleSubmit , errors } = useForm ({ validationSchema: schema, initialValues: { accountType: 'Free' , subscribe: false , terms: false }, }); const { value : email } = useField < string >( 'email' ); const { value : password } = useField < string >( 'password' ); const { value : accountType } = useField < Account >( 'accountType' ); const { value : companyName } = useField < string >( 'companyName' ); const { value : seats } = useField < number | null >( 'seats' ); const { value : subscribe } = useField < boolean >( 'subscribe' ); const { value : frequency } = useField < string >( 'frequency' ); const { value : terms } = useField < boolean >( 'terms' ); const onSubmit = handleSubmit (( vals ) => console. log (vals)); </ script > < template > < form @ submit = " onSubmit " > < label >Email < input v-model = " email " type = "email" /> < span v-if = " errors.email " >{{ errors.email }}</ span > </ label > < label >Password < input v-model = " password " type = "password" /> < span v-if = " errors.password " >{{ errors.password }}</ span > </ label > < label >Account type < select v-model = " accountType " > < option >Free</ option >< option >Pro</ option >< option >Enterprise</ option > </ select > </ label > < label v-if = " accountType !== 'Free'" >Company name < input v-model = " companyName " /> < span v-if = " errors.companyName " >{{ errors.companyName }}</ span > </ label > < label v-if = " accountType === 'Enterprise'" >Seats < input v-model . number = " seats " type = "number" min = "5" max = "1000" /> < span v-if = " errors.seats " >{{ errors.seats }}</ span > </ label > < label > < input v-model = " subscribe " type = "checkbox" /> Subscribe to product updates </ label > < label v-if = " subscribe " >Frequency < select v-model = " frequency " > < option >Daily</ option >< option >Weekly</ option >< option >Monthly</ option > </ select > </ label > < label > < input v-model = " terms " type = "checkbox" /> I accept the terms of service </ label > < button type = "submit" >Sign up</ button > </ form > </ template >

< form id = "signup" > < label >Email < input name = "email" type = "email" required /> </ label > < label >Password < input name = "password" type = "password" required minlength = "8" /> </ label > < label >Account type < select name = "accountType" required > < option >Free</ option >< option >Pro</ option >< option >Enterprise</ option > </ select > </ label > < label data-show = "companyName" hidden >Company name < input name = "companyName" /> </ label > < label data-show = "seats" hidden >Seats < input name = "seats" type = "number" min = "5" max = "1000" /> </ label > < label > < input name = "subscribe" type = "checkbox" /> Subscribe to product updates </ label > < label data-show = "frequency" hidden >Frequency < select name = "frequency" > < option >Daily</ option >< option >Weekly</ option >< option >Monthly</ option > </ select > </ label > < label > < input name = "terms" type = "checkbox" /> I accept the terms of service </ label > < button type = "submit" >Sign up</ button > </ form > < script > const f = document. querySelector ( '#signup' ); const show = ( key , on , req ) => { const el = f. querySelector ( `[data-show="${ key }"]` ); el.hidden = ! on; f.elements[key].required = on && req; }; function sync () { const t = f.accountType.value; show ( 'companyName' , t !== 'Free' , true ); show ( 'seats' , t === 'Enterprise' , true ); show ( 'frequency' , f.subscribe.checked, true ); } f.accountType. addEventListener ( 'change' , sync); f.subscribe. addEventListener ( 'change' , sync); sync (); f. addEventListener ( 'submit' , ( e ) => { e. preventDefault (); if ( ! f.terms.checked) { f.terms. setCustomValidity ( 'Required' ); } else { f.terms. setCustomValidity ( '' ); } if ( ! f. checkValidity ()) return f. reportValidity (); console. log (Object. fromEntries ( new FormData (f))); }); </ script >

import { gui } from '@golemui/gui-shared' ; import { GuiForm } from '@golemui/gui-react' ; const signupForm = [ gui.inputs. textInput ( 'email' , { label: 'Email' , validator: { required: true , format: 'email' }, }), gui.inputs. password ( 'password' , { label: 'Password' , validator: { required: true , minLength: 8 }, }), gui.inputs. dropdown ( 'accountType' , { label: 'Account type' , items: [ 'Free' , 'Pro' , 'Enterprise' ], defaultValue: 'Free' , validator: { type: 'string' , required: true }, }), gui.inputs. textInput ( 'companyName' , { label: 'Company name' , validator: { required: true }, include: { when: '$form.accountType !== "Free"' }, }), gui.inputs. numberInput ( 'seats' , { label: 'Seats' , validator: { required: true , minimum: 5 , maximum: 1000 }, include: { when: '$form.accountType === "Enterprise"' }, }), gui.inputs. checkbox ( 'subscribe' , { label: 'Subscribe to product updates' , }), gui.inputs. dropdown ( 'frequency' , { label: 'Frequency' , items: [ 'Daily' , 'Weekly' , 'Monthly' ], validator: { type: 'string' , required: true }, include: { when: '$form.subscribe === true' }, }), gui.inputs. checkbox ( 'terms' , { label: 'I accept the terms of service' , validator: { const: true }, }), gui.actions. button ({ label: 'Sign up' , actionType: 'submit' }), ]; export function SignupForm () { return ( < GuiForm config = {{ formDef: signupForm }} formSubmit = {( data ) => console. log (data)} /> ); }

import { Component } from '@angular/core' ; import { CommonModule } from '@angular/common' ; import { gui } from '@golemui/gui-shared' ; import { FormComponent } from '@golemui/gui-angular' ; const signupForm = [ gui.inputs. textInput ( 'email' , { label: 'Email' , validator: { required: true , format: 'email' }, }), gui.inputs. password ( 'password' , { label: 'Password' , validator: { required: true , minLength: 8 }, }), gui.inputs. dropdown ( 'accountType' , { label: 'Account type' , items: [ 'Free' , 'Pro' , 'Enterprise' ], defaultValue: 'Free' , validator: { type: 'string' , required: true }, }), gui.inputs. textInput ( 'companyName' , { label: 'Company name' , validator: { required: true }, include: { when: '$form.accountType !== "Free"' }, }), gui.inputs. numberInput ( 'seats' , { label: 'Seats' , validator: { required: true , minimum: 5 , maximum: 1000 }, include: { when: '$form.accountType === "Enterprise"' }, }), gui.inputs. checkbox ( 'subscribe' , { label: 'Subscribe to product updates' , }), gui.inputs. dropdown ( 'frequency' , { label: 'Frequency' , items: [ 'Daily' , 'Weekly' , 'Monthly' ], validator: { type: 'string' , required: true }, include: { when: '$form.subscribe === true' }, }), gui.inputs. checkbox ( 'terms' , { label: 'I accept the terms of service' , validator: { const: true }, }), gui.actions. button ({ label: 'Sign up' , actionType: 'submit' }), ]; @ Component ({ selector: 'app-signup' , imports: [CommonModule, FormComponent], template: `<gui-form [config]="config" (formSubmit)="onSubmit($event)"></gui-form>` , }) export class SignupForm { protected config = { formDef: signupForm }; onSubmit ( data : unknown ) { console. log (data); } }

import { LitElement, html } from 'lit' ; import { customElement } from 'lit/decorators.js' ; import { gui } from '@golemui/gui-shared' ; import '@golemui/gui-lit' ; const signupForm = [ gui.inputs. textInput ( 'email' , { label: 'Email' , validator: { required: true , format: 'email' }, }), gui.inputs. password ( 'password' , { label: 'Password' , validator: { required: true , minLength: 8 }, }), gui.inputs. dropdown ( 'accountType' , { label: 'Account type' , items: [ 'Free' , 'Pro' , 'Enterprise' ], defaultValue: 'Free' , validator: { type: 'string' , required: true }, }), gui.inputs. textInput ( 'companyName' , { label: 'Company name' , validator: { required: true }, include: { when: '$form.accountType !== "Free"' }, }), gui.inputs. numberInput ( 'seats' , { label: 'Seats' , validator: { required: true , minimum: 5 , maximum: 1000 }, include: { when: '$form.accountType === "Enterprise"' }, }), gui.inputs. checkbox ( 'subscribe' , { label: 'Subscribe to product updates' , }), gui.inputs. dropdown ( 'frequency' , { label: 'Frequency' , items: [ 'Daily' , 'Weekly' , 'Monthly' ], validator: { type: 'string' , required: true }, include: { when: '$form.subscribe === true' }, }), gui.inputs. checkbox ( 'terms' , { label: 'I accept the terms of service' , validator: { const: true }, }), gui.actions. button ({ label: 'Sign up' , actionType: 'submit' }), ]; @ customElement ( 'signup-form' ) export class SignupForm extends LitElement { private config = { formDef: signupForm }; override createRenderRoot () { return this ; } render () { return html ` <gui-form .config=${ this . config } @formSubmit=${ ( e : CustomEvent ) => console . log ( e . detail ) } ></gui-form> ` ; } }

< script setup lang = "ts" > import { gui } from '@golemui/gui-shared' ; import { GuiForm } from '@golemui/gui-vue' ; const signupForm = [ gui.inputs. textInput ( 'email' , { label: 'Email' , validator: { required: true , format: 'email' }, }), gui.inputs. password ( 'password' , { label: 'Password' , validator: { required: true , minLength: 8 }, }), gui.inputs. dropdown ( 'accountType' , { label: 'Account type' , items: [ 'Free' , 'Pro' , 'Enterprise' ], defaultValue: 'Free' , validator: { type: 'string' , required: true }, }), gui.inputs. textInput ( 'companyName' , { label: 'Company name' , validator: { required: true }, include: { when: '$form.accountType !== "Free"' }, }), gui.inputs. numberInput ( 'seats' , { label: 'Seats' , validator: { required: true , minimum: 5 , maximum: 1000 }, include: { when: '$form.accountType === "Enterprise"' }, }), gui.inputs. checkbox ( 'subscribe' , { label: 'Subscribe to product updates' , }), gui.inputs. dropdown ( 'frequency' , { label: 'Frequency' , items: [ 'Daily' , 'Weekly' , 'Monthly' ], validator: { type: 'string' , required: true }, include: { when: '$form.subscribe === true' }, }), gui.inputs. checkbox ( 'terms' , { label: 'I accept the terms of service' , validator: { const: true }, }), gui.actions. button ({ label: 'Sign up' , actionType: 'submit' }), ]; const config = { formDef: signupForm }; const onSubmit = ( data : unknown ) => console. log (data); </ script > < template > < GuiForm : config = " config " @ form-submit = " onSubmit " /> </ template >