Form

A composable form layout with sections, collapsible areas, flip-card read/edit panels, sub-sections, and built-in Goshtoso field rendering. Supports HTMX validation and external submit triggers.

Identification

Lowercase letters, numbers, and hyphens only
  • AWS
  • Azure
  • GCP
  • MAAS
Custom component — any templ.Component works as children

Network

Pod Subnet CIDR 10.244.0.0/16
Service Subnet CIDR 10.96.0.0/12
Default: 10.244.0.0/16
Default: 10.96.0.0/12
  • Info
  • Debug
  • Warning
  • Error

Node Pools

Control Plane

  • m5.large (2 vCPU, 8 GB)
  • m5.xlarge (4 vCPU, 16 GB)

Workers

  • m5.xlarge (4 vCPU, 16 GB)
  • m5.2xlarge (8 vCPU, 32 GB)
Cancel
Usage Example
// Built-in field types — set Input, Combobox, Textarea, Toggle, etc. directly
@form.FieldGroup(form.FieldGroupConfig{
    ID: "name", Label: "Name", Required: true,
    Input: &textinput.Config{ID: "name", Name: "name"},
})
@form.FieldGroup(form.FieldGroupConfig{
    ID: "provider", Label: "Provider",
    Combobox: &gocombobox.Config{ID: "provider", Name: "provider", Source: gocombobox.Source{Static: opts}},
})

// Custom component — use { children... } for anything not built-in
@form.FieldGroup(form.FieldGroupConfig{ID: "tags", Label: "Tags"}) {
    @myCustomTagsInput(tags)  // any templ.Component
}

// Flip Section — read-only front, editable back
@form.FlipSection(form.FlipSectionConfig{
    SectionConfig: form.SectionConfig{Title: "Network"},
}, networkReadView()) {
    @form.FieldGroup(form.FieldGroupConfig{
        Label: "Pod CIDR",
        Input: &textinput.Config{ID: "pod-cidr", Name: "pod_cidr", Value: "10.244.0.0/16"},
    })
}

// External submit (modal pattern — no footer, modal confirmation calls requestSubmit)
@form.Form(form.Config{
    ID: "modal-form",
    HTMX: &form.HTMXConfig{
        Post: "/api/submit",
        Swap: "none",
    },
}) { ... }
@modal.Modal(modal.Config{
    ID:            "modalSubmitConfirm",
    Title:         "Confirm submit",
    TriggerLabel:   "Submit",
    PrimaryLabel:   "Confirm submit",
    SecondaryLabel: "Cancel",
    PrimaryAction: &modal.ButtonAction{
        OnClick: "document.getElementById('modal-form').requestSubmit()",
    },
})
@toast.Container(toast.ContainerConfig{})

Field Validation States

FieldGroup surfaces success/error input states, error messages (Errors), and helper hints (Hints).

Validation Examples

This cluster ID is already taken
Must be between 3 and 63 characters Only lowercase letters, numbers, and hyphens
Field Validation States
@form.FieldGroup(form.FieldGroupConfig{
    ID: "error-field", Label: "Error Field", Required: true,
    Errors: []string{"This cluster ID is already taken"},
    Input:  &textinput.Config{ID: "error-field", Name: "error", State: textinput.StateError},
})
@form.FieldGroup(form.FieldGroupConfig{
    ID: "hint-field", Label: "Field with Hints",
    Hints: []string{"Must be 3-63 characters", "Lowercase letters, numbers, hyphens"},
    Input: &textinput.Config{ID: "hint-field", Name: "hints"},
})

Server-Side Validation

Server-side field validation with HTMX. Fields validate on change, dependent fields update automatically (e.g. slug from name), and full-form submit validates everything at once.

Try it out

Type in the fields below and tab out to see validation in action. The slug field auto-generates from name. Try "admin", "test", or "demo" as slugs to see the uniqueness check.

Project Details

At least 3 characters.
URL-friendly identifier. Auto-generated from name.
Contact email for notifications.
Reset
Server-Side Validation
// 1. Define the form fields and validation rules
def := &validation.FormDef{
    FormID:   "demo-validation",
    Endpoint: "/api/components/form-validation",
    Fields: map[string]*validation.FieldDef{
        "name":  {Name: "name", FieldGroup: nameField, OnChange: true},
        "slug":  {Name: "slug", FieldGroup: slugField, OnChange: true, DependsOn: []string{"name"}},
        "email": {Name: "email", FieldGroup: emailField, OnChange: true},
    },
}
def.Bind() // wires up HTMX attributes and metadata

// 2. In the templ, render the form with FieldGroups
@form.Form(form.Config{
    ID: "demo-validation",
    HTMX: &form.HTMXConfig{Post: "/api/components/form-validation", Target: "#form-result", Swap: "innerHTML"},
    Footer: &form.FooterConfig{SubmitLabel: "Submit", CancelLabel: "Reset"},
}) {
    @form.Section(form.SectionConfig{Title: "Project Details"}) {
        @form.FieldGroup(*nameField)
        @form.FieldGroup(*slugField)
        @form.FieldGroup(*emailField)
    }
}

// 3. Handle validation in Go
func (s *Server) handleFormValidation(w http.ResponseWriter, r *http.Request) {
    def := buildDemoFormDef()
    result := validation.Handle(r, def, validateDemoField)
    if validation.IsFieldValidation(r) {
        validation.RenderFieldResponse(r.Context(), w, *result)
        return
    }
    // full submit...
}

// 4. Per-field validation hook
func validateDemoField(ctx validation.ValidationContext, name string, fg *form.FieldGroupConfig) bool {
    switch name {
    case "name":
        val := ctx.FormValues["name"]
        if len(val) < 3 {
            fg.Errors = []string{"Name must be at least 3 characters."}
            fg.Input.State = textinput.StateError
            return false
        }
        fg.Input.State = textinput.StateSuccess
        return true
    case "slug":
        // auto-generate from name on dependency trigger
        if ctx.Type == validation.ValidationDependency && val == "" { ... }
        // uniqueness check against takenSlugs map
    case "email":
        // format validation
    }
}

External Submit (Modal Pattern)

Render the form with no Footer; a button outside the form uses the form="..." attribute to submit it — the typical modal pattern.

Upgrade

  • v1.32.0
  • v1.31.5
  • v1.30.8
External Submit (Modal Pattern)
@form.Form(form.Config{
    ID: "external-form",
    HTMX: &form.HTMXConfig{
        Post: "/api/components/form/external-submit",
        Swap: "none",
    },
}) {
    @form.Section(form.SectionConfig{Title: "Upgrade"}) { ... }
}
@modal.Modal(modal.Config{
    ID:            "externalSubmitConfirm",
    Title:         "Confirm upgrade",
    TriggerLabel:   "Upgrade (External Button)",
    PrimaryLabel:   "Confirm upgrade",
    SecondaryLabel: "Cancel",
    PrimaryAction: &modal.ButtonAction{
        OnClick: "document.getElementById('external-form').requestSubmit()",
    },
})
@toast.Container(toast.ContainerConfig{})

API Reference

PropTypeDefaultDescription
IDstring""Form element id (target of external submit buttons via form="...").
Actionstring""Non-HTMX form action URL.
Methodstring"post"HTTP method for non-HTMX submits.
HTMX*HTMXConfignilHTMX submit config (Post/Put/Target/Swap, inline validation).
PreventEnterSubmit*boolnilWhen true, Enter in a field does not submit the form.
Footer*FooterConfignilSubmit/cancel footer; omit for the external-submit pattern.
RootClassstring""Extra classes on the <form>.
FieldGroupFieldGroupConfigPer-field wrapper: Label, Required, Errors, Hints + one built-in (Input/Combobox/Textarea/Toggle/...) or children.
Section / SubSectionSectionConfigGroup fields under a titled section; SubSection nests within a Section.
CollapsibleSectionCollapsibleSectionConfigA Section that collapses, with a Summary shown when collapsed.
FlipSectionFlipSectionConfigA section with a read-only front view and an editable back.
FormDef.FormIDstring""DOM id of the form (must match form.Config.ID).
FormDef.Endpointstring""Server URL that handles field + full-form validation requests.
FormDef.Fieldsmap[string]*FieldDefnilPer-field definitions keyed by field name.
FieldDef.Namestring""Form field name (matches the input's Name).
FieldDef.FieldGroup*form.FieldGroupConfignilThe field's FieldGroup (label, input, hints, errors).
FieldDef.OnChangeboolfalseValidate this field on change (blur), not just on submit.
FieldDef.DependsOn[]stringnilOther field names that re-trigger this field's validation (e.g. slug depends on name).
def.Bind()methodWires HTMX attributes + metadata onto each FieldGroup; call once after defining.
validation.HandlefuncServer entry point: routes field vs full-form requests and runs your per-field validator.