Form Validation

View Original

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.

Goshtoso Component

Go + Templ

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
Usage Example
// 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{SubmitText: "Submit", CancelText: "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
    }
}

This site uses localStorage to remember your theme preference. No tracking cookies.