Quick-reference for how standard HTML maps to LiveTemplate behavior. For the learning walkthrough, see the Progressive Complexity Guide. For lvt-* attributes, see the Client Attributes Reference.
| HTML Pattern | Framework Behavior | Go Method |
|---|---|---|
<form method="POST"> |
Auto-intercepted, routes to default action | Submit() |
<button name="save"> |
Button name becomes action | Save() |
<button name="saveDraft"> |
camelCase names converted to PascalCase | SaveDraft() |
<button name="delete" value="{{.ID}}"> |
Value passed as data | ctx.GetString("value") |
<form name="search"> |
Form name becomes action (JS client only) | Search() |
<input type="hidden" name="id" value="{{.ID}}"> |
Included in form data | ctx.GetString("id") |
<button name="increment"> |
Standalone button outside any form (JS client only) | Increment() |
When a standard HTML form is submitted, the action is resolved in this order (first match wins):
name attributename attribute"submit" → Submit()For the full resolution order including lvt-form:action and other Tier 2 attributes, see Client Attributes Reference — Action Resolution Order.
| Pattern | Behavior |
|---|---|
Controller has Change() method |
Server sends capabilities: ["change"]; client auto-wires debounced input events (300ms) |
<input name="X" value="{{.X}}"> |
Auto-bound when Change() exists (dynamic value detected) |
<input name="X"> (no template expression) |
NOT auto-bound (static only) |
lvt-mod:debounce="500" on input |
Overrides default 300ms debounce |
No Change() method |
Form is submit-only, no auto-binding |
HTML validation attributes are extracted by ctx.ValidateForm():
| HTML Attribute | Server Validation |
|---|---|
required |
Field must not be empty |
type="email" |
Must be valid email format |
type="url" |
Must be valid URL |
type="number" |
Must be numeric |
minlength="N" |
Minimum N characters |
maxlength="N" |
Maximum N characters |
min="N" |
Numeric minimum |
max="N" |
Numeric maximum |
pattern="regex" |
Must match regex |
formnovalidate on a named submit button |
ctx.ValidateForm() skips validation when that button is the submitter (all tiers; no-JS needs an empty-value button) |
| HTML Pattern | Framework Behavior |
|---|---|
command="show-modal" commandfor="id" |
Opens <dialog> via .showModal() (polyfilled by client for cross-browser support) |
command="close" commandfor="id" |
Closes <dialog> via .close() (polyfilled by client) |
<form> inside <dialog> |
Routes action to server; dialog auto-closes on success |
Browser support: The Invoker Commands API (
command/commandfor) is natively supported in Chrome 135+. The LiveTemplate client includes a lightweight polyfill for Firefox and Safari, using feature detection (commandForElement) to become a no-op when native support lands.
All <a href> links inside the LiveTemplate wrapper are auto-intercepted for SPA navigation.
| Pattern | Intercepted? |
|---|---|
<a href="/path"> |
Yes — fetched via fetch(), DOM patched, pushState updated |
<a href="/path" download> |
No — download attribute skips interception |
<a href="https://external.com"> |
No — different origin skips interception |
<a href="/path" lvt-nav:no-intercept> |
No — explicit opt-out for links (lvt-form:no-intercept for forms) |
During form submission, the framework automatically manages loading indicators:
| HTML Pattern | Automatic Behavior |
|---|---|
<form> during submit |
aria-busy="true" set on form |
<fieldset> inside submitting form |
disabled attribute set |
| Server responds | Both aria-busy and disabled cleared |
| Feature | No JS | JS + HTTP | JS + WebSocket |
|---|---|---|---|
| Form submit | POST + page reload | fetch() + DOM patch |
WS message + DOM patch |
button name routing |
Native POST | Client extracts | Client extracts |
| Standalone button (no form) | N/A (use form) | Client detects | Client detects |
form name routing |
N/A (use button name) | Client reads form.name |
Client reads form.name |
| Hidden inputs | Native POST | In FormData | In FormData |
Change() auto-binding |
N/A | Works | Works |
lvt-* attributes |
N/A | Works | Works |
| Server push (broadcast) | N/A | N/A | Works |
Security note: When using no-JS POST mode, implement CSRF protection (e.g.,
gorilla/csrfor equivalent CSRF middleware). The JS transport modes send theOriginheader that the server validates on WebSocket upgrade and fetch; plain HTML form POST does not carry the same protection.
lvt-* AttributesFor behaviors that standard HTML cannot express — timing control, reactive DOM, keyboard shortcuts, scroll management — use lvt-* attributes. The attribute prefixes are:
| Prefix | Purpose | Example |
|---|---|---|
lvt-on: |
Event bindings | lvt-on:click="save", lvt-on:window:keydown="close" |
lvt-el: |
Reactive DOM manipulation | lvt-el:addClass:on:pending="loading" |
lvt-fx: |
Visual directives | lvt-fx:scroll="bottom", lvt-fx:highlight="flash" |
lvt-mod: |
Event modifiers | lvt-mod:debounce="300", lvt-mod:throttle="100" |
lvt-form: |
Form behavior | lvt-form:preserve, lvt-form:disable-with="Saving..." |
Some lvt-* attributes are standalone (not prefixed):
| Attribute | Purpose | Example |
|---|---|---|
lvt-scroll-sentinel |
Infinite scroll trigger | <div lvt-scroll-sentinel data-key="sentinel">Loading…</div> |
lvt-scroll-away |
Scroll-position visibility | <button lvt-scroll-away="bottom" data-lvt-target="#log">↓</button> |
See the Client Attributes Reference for the complete listing.
Compose multiple lvt-* attributes for a chat UI with upward infinite scroll, auto-scroll on new messages, and a scroll-to-bottom button — all without custom JavaScript:
<div class="chat-scroll-wrap">
<!-- threshold 80: scroll more eagerly than the default 100 -->
<div class="chat-log" id="chat-log" lvt-fx:scroll="bottom-sticky"
style="--lvt-scroll-threshold: 80" data-key="chat-{{.SessionID}}">
{{if .HasMore}}
<div lvt-scroll-sentinel data-key="sentinel">
<small aria-busy="true">Loading older messages…</small>
</div>
{{end}}
{{range .Messages}}
<div class="chat-row {{.Role}}" data-key="{{.Key}}">
<div class="chat-bubble">{{.Text}}</div>
</div>
{{end}}
</div>
<button type="button" class="scroll-bottom-btn"
lvt-fx:scroll:on:click="bottom"
lvt-scroll-away="bottom"
data-lvt-target="#chat-log"
aria-label="Scroll to bottom">↓</button>
</div>
How the attributes compose:
| Attribute | Role |
|---|---|
lvt-fx:scroll="bottom-sticky" |
Auto-scrolls on new messages if user is near bottom; scrolls to bottom on first load |
lvt-scroll-sentinel |
IntersectionObserver triggers LoadMore() when sentinel enters viewport |
data-key on rows |
Differential DOM updates — only new/changed rows are sent over the wire |
lvt-fx:scroll:on:click="bottom" |
Scrolls target to bottom on button click |
lvt-scroll-away="bottom" |
Shows button only when scrolled away from bottom |
data-key on chat-log |
Session switches replace the element, resetting the sticky scroll marker |
Backend: LoadMore() increments a page counter; the handler returns the most recent page × N messages from the full log (growing the visible window upward). lvt-scroll-sentinel is conditionally rendered with {{if .HasMore}} to stop loading when all messages are shown.