Form Module

Complete form infrastructure for GoFastr — HTML primitives, framework UI components, form patterns, validation, and accessibility.

Components

HTML Primitives (core-ui/html)

ComponentFunctionDescription
Checkboxhtml.Checkbox(CheckboxConfig)Native <input type="checkbox"> with label
Radiohtml.Radio(RadioConfig)Native <input type="radio"> with label

Framework UI Components (framework/ui)

ComponentFunctionSelf-labeledRuntime JS
PasswordInputui.PasswordInput(PasswordInputConfig)✗ (use FormField)✓ toggle
SearchInputui.SearchInput(SearchInputConfig)✗ (use FormField)✓ clear
InputGroupui.InputGroup(InputGroupConfig)✗ (use FormField)
NumberInputui.NumberInput(NumberInputConfig)✓ stepper
ColorPickerui.ColorPicker(ColorPickerConfig)
RatingInputui.RatingInput(RatingConfig)
TextAreaui.TextArea(TextAreaConfig)✓ autogrow
RadioGroupui.RadioGroup(RadioGroupConfig)✓ (fieldset)
CheckboxGroupui.CheckboxGroup(CheckboxGroupConfig)✓ (fieldset)
ValidationSummaryui.ValidationSummary(ValidationSummaryConfig)
ConditionalFieldui.ConditionalField(ConditionalFieldConfig)✓ toggle
StepWizardui.StepWizard(StepWizardConfig)
FormRepeaterui.FormRepeater(FormRepeaterConfig)

Self-labeled = component renders its own <label>. Don't wrap in FormField.
Runtime JS = requires a runtime module (auto-registered).

Form Containers

ComponentFunctionPurpose
Formui.Form(FormConfig)<form> with action, method, optional FieldErrors
FormSectionui.FormSection(FormSectionConfig)Fieldset grouping with heading
FormFieldui.FormField(FormFieldConfig)Label + input + help/error wrapper
FormFieldForui.FormFieldFor(errs, name, config)FormField with per-field error from FieldErrors

Validation

Server-side validation uses ui.FieldErrors (a map[string]string of field-name → error-message).

13 lines
errs := ui.FieldErrors{    "email": "Please enter a valid email address.",    "name":  "Name is required.",}// Pass to Form for the error calloutui.Form(ui.FormConfig{Action: "/submit", Method: "POST", Errors: errs}, ...)// Per-field error displayui.FormFieldFor(errs, "email", ui.FormFieldConfig{    Label: "Email", For: "f-email",    Input: html.Input(html.InputConfig{Type: "email", Name: "email", ID: "f-email"}),})

ValidationSummary

Standalone component rendering <div role="alert"><ul> with anchor links per error:

4 lines
ui.ValidationSummary(ui.ValidationSummaryConfig{    Errors: errs,    FieldLabels: map[string]string{"email": "Email", "name": "Name"},})

Form Patterns

Conditional Fields

Show/hide fields based on another field's value. Runtime JS listens for change/input on the parent form and toggles hidden+aria-hidden.

11 lines
ui.RadioGroup(ui.RadioGroupConfig{    Name: "type", Legend: "Account type",    Options: []ui.RadioGroupOption{        {Label: "Personal", Value: "personal"},        {Label: "Business", Value: "business"},    },})ui.ConditionalField(ui.ConditionalFieldConfig{    WhenName: "type", WhenValue: "business",    Children: []render.HTML{...},})

Step Wizard

Multi-step form with progress indicator. Pure server-driven — each Continue/Back click is a form POST.

8 lines
ui.StepWizard(ui.StepWizardConfig{    Action: "/wizard",    Steps: []ui.StepWizardStep{        {Heading: "Personal info", Fields: []render.HTML{...}},        {Heading: "Preferences",   Fields: []render.HTML{...}},        {Heading: "Review",        Fields: []render.HTML{...}},    },})

Form Repeater

Dynamic repeating field groups. Server-driven add/remove via name_add/name_remove POST fields.

5 lines
ui.FormRepeater(ui.FormRepeaterConfig{    Name: "members", MinItems: 1, MaxItems: 5,    AddLabel: "Add member", RemoveLabel: "Remove",    Items: [][]render.HTML{...},})

Accessibility

All components pass axe-core 4.10 with zero violations:

  • Labels: Every input has an associated <label> via for/id or aria-label
  • Error states: aria-invalid="true" + aria-describedby linking to error message
  • Keyboard: All interactive elements focusable and operable via keyboard
  • Contrast: All text meets WCAG 2.1 AA minimum (4.5:1 for normal text)
  • Roles: Proper ARIA roles (role="alert", role="list", role="listitem", etc.)
  • Hidden state: Conditional fields use hidden + aria-hidden="true" when inactive

Runtime JS Modules

ModuleFilePurpose
passwordinputcore-ui/runtime/src/passwordinput.jsToggle password visibility
searchinputcore-ui/runtime/src/searchinput.jsClear button + auto-show/hide
conditionalfieldcore-ui/runtime/src/conditionalfield.jsShow/hide based on parent field value

Demo Page

/components/forms — comprehensive demo showcasing every form component with live examples, validation round-trip, and accessible markup.

Common mistakes

  • Wrapping a self-labeled component in FormField. Components
    marked "Self-labeled" in the table above (NumberInput, TextArea,
    RadioGroup, CheckboxGroup, ColorPicker, RatingInput) already
    render their own <label> element. Wrapping them in FormField
    produces a double-label, breaks for/id linking, and fails axe
    validation.
  • Using FormField without setting For + ID. The
    label-to-input association is <label for="X"> + <input id="X">.
    If FormField.For and the inner input's ID don't match, screen
    readers can't pair them and axe will report a violation.
  • Passing ui.FieldErrors with camelCase keys. FieldErrors keys
    must match the HTML field name attribute exactly (typically
    snake_case or the CRUD entity field name). A key mismatch causes the
    error to silently not render next to the intended field.
  • Expecting ConditionalField to hide server-side. The visibility
    toggle runs in the browser. On first load, ConditionalField renders
    with the hidden attribute when the condition is false — but the
    server does not skip the field from the HTML. Server-side logic
    must independently ignore values from hidden fields.
  • Relying on StepWizard to prevent multi-step submission. Each
    Continue/Back click is a regular form POST; the wizard does not
    disable earlier-step fields. Validate each step's data on the server
    for the relevant step before advancing.

E2E Test Coverage

7 dedicated tests in examples/site/e2e_form_module_test.go:

  • PasswordInput renders and toggles (password→text)
  • SearchInput clear button
  • InputGroup renders prepend/append
  • ValidationSummary renders anchor links
  • Form field order and title
  • Checkbox/Radio primitives present
  • Forms page loads quickly