Inline Validation

Validate on the server as the user types — no client validation library. The form's Change action re-checks the fields and the template renders each error next to its input via {{.lvt.AriaInvalid}} + {{.lvt.ErrorTag}}. Submit is only accepted once ctx.ValidateForm() passes.

Inline Validation

Template

The browser enforces HTML rules (type="email", required, minlength); the server re-checks the same rules and renders aria-invalid + the error message inline.

{{define "content"}}
<article>
    <h3>Inline Validation</h3>
    <form method="POST">
        <fieldset>
            <label>Email
                <input type="email" name="email" value="{{.Email}}" required {{.lvt.AriaInvalid "email"}}>
                {{.lvt.ErrorTag "email"}}
            </label>
            <label>Username
                <input name="username" value="{{.Username}}" required minlength="3" maxlength="20" {{.lvt.AriaInvalid "username"}}>
                {{.lvt.ErrorTag "username"}}
            </label>
            <button type="submit">Submit</button>
        </fieldset>
    </form>
    {{if .Saved}}<ins style="display:block;text-decoration:none">Saved successfully!</ins>{{end}}
</article>
{{end}}

inline-validation.tmpl

Handler & state

Change updates the touched field and runs ValidateForm (errors surface via the template tags); Submit rejects until validation passes.

type InlineValidationController struct{}

func (c *InlineValidationController) Change(state InlineValidationState, ctx *livetemplate.Context) (InlineValidationState, error) {
	if ctx.Has("email") {
		state.Email = ctx.GetString("email")
	}
	if ctx.Has("username") {
		state.Username = ctx.GetString("username")
	}
	_ = ctx.ValidateForm()
	return state, nil
}

func (c *InlineValidationController) Submit(state InlineValidationState, ctx *livetemplate.Context) (InlineValidationState, error) {
	if err := ctx.ValidateForm(); err != nil {
		return state, err
	}
	state.Saved = true
	return state, nil
}

func inlineValidationHandler() http.Handler {
	tmpl := newLayoutTmpl("templates/layout.tmpl", "templates/forms/inline-validation.tmpl")
	return tmpl.Handle(&InlineValidationController{}, livetemplate.AsState(&InlineValidationState{
		Title:    "Inline Validation",
		Category: "Forms & Editing",
	}))
}

handlers_forms.go:90-118

type InlineValidationState struct {
	Title    string
	Category string
	Email    string
	Username string
	Saved    bool
}

state_forms.go:29-36

When to use

source: livetemplate/docs · path: examples/patterns/templates/forms/inline-validation.tmpl