Set a message server-side with ctx.SetFlash("key", text) and render it with
{{.lvt.FlashTag "key"}}, which emits an accessible <output> tag. Over a WebSocket
connection a flash persists across renders until you ctx.ClearFlash(key) it or it
auto-prunes via FlashExpiry(d); over plain HTTP it is one-shot. This example shows a
required-field error that stays put, a success toast that expires after 5s, and a notice
that lingers until dismissed.
Flash Messages / Toasts
Server-set messages that render via {{.lvt.FlashTag "key"}} as <output role="…" data-flash="…">. On WebSocket connections flash persists across renders until cleared with ctx.ClearFlash(key) or auto-pruned via FlashExpiry(d). On HTTP it's inherently one-shot.
Try: Submit empty → persistent error. Type a name + Save → success that auto-dismisses after 5s (a server-side goroutine fires session.TriggerAction("refresh", nil) at the deadline; the resulting render's getMessages prunes the expired flash). Click Notify → persistent info until you click Dismiss.
Template
Two forms plus three FlashTag slots — one each for the success, error, and info keys
the handler sets.
{{define "content"}}
<article>
<h3>Flash Messages / Toasts</h3>
<p><small>Server-set messages that render via <code>{{"{{.lvt.FlashTag \"key\"}}"}}</code> as <code><output role="…" data-flash="…"></code>. On WebSocket connections flash <strong>persists across renders</strong> until cleared with <code>ctx.ClearFlash(key)</code> or auto-pruned via <code>FlashExpiry(d)</code>. On HTTP it's inherently one-shot.</small></p>
<form method="POST">
<fieldset role="group">
<input name="name" placeholder="Enter name…" aria-label="Name">
<button name="save">Save</button>
</fieldset>
</form>
<form method="POST">
<fieldset role="group">
<button name="notify" type="submit">Notify</button>
<button name="dismissNotify" type="submit" class="compact secondary">Dismiss</button>
</fieldset>
</form>
{{.lvt.FlashTag "success"}}
{{.lvt.FlashTag "error"}}
{{.lvt.FlashTag "info"}}
<p><small><strong>Try:</strong> Submit empty → persistent error. Type a name + Save → success that auto-dismisses after 5s (a server-side goroutine fires <code>session.TriggerAction("refresh", nil)</code> at the deadline; the resulting render's <code>getMessages</code> prunes the expired flash). Click <strong>Notify</strong> → persistent info until you click <strong>Dismiss</strong>.</small></p>
</article>
{{end}}
Save sets either an error or a 5s-expiring success; Notify / DismissNotify set and
clear a persistent info flash. Because FlashExpiry is render-driven, a small goroutine
nudges a re-render at the deadline so the expired toast actually leaves the DOM.
type FlashMessagesController struct{}
func (c *FlashMessagesController) Save(state FlashMessagesState, ctx *livetemplate.Context) (FlashMessagesState, error) {
name := strings.TrimSpace(ctx.GetString("name"))
if name == "" {
ctx.ClearFlash("success")
ctx.SetFlash("error", "Name is required")
return state, nil
}
ctx.ClearFlash("error")
ctx.SetFlash("success", "Saved: "+name, livetemplate.FlashExpiry(flashSuccessExpiry))
nudgeFlashExpiry(ctx, flashSuccessExpiry)
return state, nil
}
// Refresh is a no-op action whose only purpose is to trigger a re-render
// (and therefore a getMessages snapshot, which prunes expired flash).
// Invoked by the goroutine in Save after FlashExpiry elapses.
func (c *FlashMessagesController) Refresh(state FlashMessagesState, ctx *livetemplate.Context) (FlashMessagesState, error) {
return state, nil
}
func (c *FlashMessagesController) Notify(state FlashMessagesState, ctx *livetemplate.Context) (FlashMessagesState, error) {
ctx.SetFlash("info", "Heads up — this stays until you dismiss it")
return state, nil
}
func (c *FlashMessagesController) DismissNotify(state FlashMessagesState, ctx *livetemplate.Context) (FlashMessagesState, error) {
ctx.ClearFlash("info")
return state, nil
}
func flashMessagesHandler() http.Handler {
tmpl := newLayoutTmpl("templates/layout.tmpl", "templates/feedback/flash-messages.tmpl")
return tmpl.Handle(&FlashMessagesController{}, livetemplate.AsState(&FlashMessagesState{
Title: "Flash Messages",
Category: "Visual Feedback",
}))
}