pgForm/Getting started
Getting started~10 min

Getting started

A working two-field form in four steps. Declare your fields, write your input components, mount the store with usePgForm, and render with GeneratePgForm. Nothing more, nothing magic.

1. Declare your fields

Each entry in pgf.formData(...) describes a single field — its type, default value, validators, dynamic props, and free-form metadata you read from your input components.

TSXfields.ts
import { pgf } from '@westopp/pgform'

export const fields = pgf.formData({
fullName: {
  type: 'text',
  defaultValue: '',
  required: { value: true, message: 'Your name is required' },
  metadata: { label: 'Full name', placeholder: 'Ada Lovelace' },
},
email: {
  type: 'email',
  defaultValue: '',
  required: { value: true, message: 'An email is required' },
  metadata: { label: 'Email', placeholder: 'ada@example.com' },
},
})

2. Write your input components

pgForm is BYOC (bring your own components). For each field type you use, you supply a component that receives standardized props — fieldKey, value, onChange, onBlur, onFocus, fieldApi, cn, metadata.

TSXTextInput.tsx
import type { PgFormTextFieldProps } from '@westopp/pgform'

export function TextInput({ fieldKey, value, onChange, onBlur, onFocus, fieldApi, cn, metadata }: PgFormTextFieldProps) {
return (
  <label>
    <span>{metadata?.label ?? fieldKey}</span>
    <input
      type="text"
      name={fieldKey}
      value={value ?? ''}
      placeholder={metadata?.placeholder}
      onChange={(e) => onChange(e.target.value)}
      onBlur={onBlur}
      onFocus={onFocus}
      disabled={fieldApi.isDisabled}
      readOnly={fieldApi.isReadonly}
      className={cn('input')}
    />
    {fieldApi.firstError && <p>{fieldApi.firstError.message}</p>}
  </label>
)
}

3. Mount the form with usePgForm

usePgForm mounts the store entry and returns a typed formApi plus the generateFormProps you pass to the renderer.

TSXMyForm.tsx
import { GeneratePgForm, usePgForm, type PgFormFieldComponents } from '@westopp/pgform'
import { fields } from './fields'
import { TextInput, EmailInput } from './inputs'

const components: PgFormFieldComponents<typeof fields> = { text: TextInput, email: EmailInput }

export const MyForm = () => {
const form = usePgForm(fields, {
  validationMode: 'onBlur',
  revalidationMode: 'onChange',
  onSubmit: (values) => console.log(values),
})

return (
  <>
    <GeneratePgForm generateFormProps={form.generateFormProps} components={components} />
    <button type="button" onClick={() => form.formApi.submit()}>Submit</button>
  </>
)
}

Here it is mounted live. Fill in the fields and submit to see the values panel update.

Submitted values
// submit the form to see values