Complete reference for LiveTemplate form handling and lvt-* HTML attributes.
For server-side Go API: See pkg.go.dev/github.com/livetemplate/livetemplate
- Standard HTML Form Routing
- Event Bindings
- Data Passing
- Form Lifecycle Events
- Reactive Attributes
- Validation
- Rate Limiting
- Directives
- Modals
- File Uploads
- Form Behavior
- Attribute Reference
LiveTemplate follows a progressive complexity model. Standard HTML forms work without any lvt-* attributes. Use lvt-* only for behaviors HTML cannot express (debounce, loading states, reactive DOM, etc.).
All <form> elements within a LiveTemplate-managed region are automatically intercepted. Forms without explicit action routing default to the Submit() method on the controller:
<!-- No lvt-* needed — auto-routes to Submit() -->
<form method="POST">
<input type="text" name="title" placeholder="New todo...">
<button type="submit">Add</button>
</form>func (c *Controller) Submit(state State, ctx *livetemplate.Context) (State, error) {
title := ctx.GetString("title")
// ...
return state, nil
}The button's name IS the action. Button value carries optional data:
<form method="POST">
<input type="text" name="Title" value="{{.Title}}">
<button name="save">Save</button>
<button name="save-draft" formnovalidate>Save Draft</button>
</form><button name="save"> routes to Save(). <button name="save-draft"> routes to SaveDraft().
Use the name attribute on the form itself:
<form name="search" method="POST">
<input name="query" value="{{.Query}}">
<button type="submit">Search</button>
</form>Routes to Search() on the controller when using the JS client, which reads form.name. A plain HTML POST does not include the form's name attribute, so for no-JS compatibility use <button name="search"> instead.
Data can be passed via hidden inputs, button value, or data-* attributes:
{{range .Items}}
<form method="POST">
<input type="hidden" name="id" value="{{.ID}}">
<button name="toggle">{{if .Done}}Undo{{else}}Done{{end}}</button>
<button name="delete" value="{{.ID}}">Delete</button>
</form>
{{end}}- Hidden inputs:
ctx.GetString("id") - Button value:
ctx.GetString("value") data-*on button:ctx.GetString("key")
The client resolves the action name in this order (first match wins):
lvt-form:action="X"on the form → action isX(explicit routing, highest precedence)lvt-submit="X"on the form → action isX(backward compatible)- Clicked button's
nameattribute → action is the button name form name="X"→ action isX- None of the above → defaults to
"submit"→ routes toSubmit()
Note: The form field name
actionis not reserved. A form field<input name="action" value="approve">flows through toActionDataas normal data. Uselvt-form:actionon the<form>element for routing.
Forms that should NOT be auto-intercepted (external URLs, downloads):
<form action="/api/export" method="POST" lvt-form:no-intercept>
<button type="submit">Export CSV</button>
</form>Links that should NOT be auto-intercepted (external pages, legacy routes):
<a href="/legacy-page" lvt-nav:no-intercept>Legacy Page</a>Note: Use
lvt-form:no-intercepton<form>elements andlvt-nav:no-intercepton<a>elements. These are semantically distinct: form interception vs. link/navigation interception.
| Mechanism | No JS | JS + HTTP | JS + WebSocket |
|---|---|---|---|
button name="action" |
Native POST | Client extracts | Client extracts |
form name |
N/A (use button) | Client reads | Client reads |
| Hidden inputs | Native POST | In FormData | In FormData |
LiveTemplate uses lvt-* attributes to bind DOM events to server-side actions. These are for interactions that standard HTML forms cannot express.
<!-- Click events -->
<button lvt-on:click="submit">Submit</button>
<button lvt-on:click="delete" lvt-data-id="{{.ID}}">Delete</button>
<!-- Form submission -->
<form lvt-form:action="save">
<input type="text" name="title" required>
<button type="submit">Save</button>
</form>
<!-- Input events -->
<input lvt-on:change="validate" name="email">
<input lvt-on:input="search" name="query"><!-- Hover events -->
<div lvt-on:mouseenter="showTooltip" lvt-on:mouseleave="hideTooltip">
Hover for tooltip
</div>
<!-- Click events -->
<button lvt-on:click="handleClick">Click me</button><!-- Keydown events -->
<input lvt-on:keydown="handleKey" name="search">
<!-- With key filtering -->
<input lvt-on:keydown="submit" lvt-key="Enter" name="query">
<div lvt-on:window:keydown="closeModal" lvt-key="Escape">
Modal content
</div><!-- Global keyboard events -->
<div lvt-on:window:keydown="handleShortcut" lvt-key="Escape">
<!-- Scroll events -->
<div lvt-on:window:scroll="loadMore" lvt-mod:throttle="100">Pass data to Tier 2 event handlers using lvt-data-* attributes. For Tier 1 forms, use standard HTML instead: hidden inputs (<input type="hidden" name="id" value="{{.ID}}">), button value, or data-* attributes on buttons. See Standard HTML — Data Passing above.
<button lvt-on:click="delete" lvt-data-id="{{.ID}}">
Delete
</button><button lvt-on:click="update"
lvt-data-id="{{.ID}}"
lvt-data-status="{{.Status}}"
lvt-data-priority="{{.Priority}}">
Update Item
</button>// Action "delete" with lvt-data-id
func (c *Controller) Delete(state State, ctx *livetemplate.Context) (State, error) {
id := ctx.GetString("id")
// Delete item with id
return state, nil
}
// Action "update" with multiple lvt-data-* attributes
func (c *Controller) Update(state State, ctx *livetemplate.Context) (State, error) {
id := ctx.GetString("id")
status := ctx.GetString("status")
priority := ctx.GetInt("priority")
// Update item
return state, nil
}Available methods:
ctx.GetString(key string) stringctx.GetInt(key string) intctx.GetFloat(key string) float64ctx.GetBool(key string) boolctx.Has(key string) bool
Forms emit JavaScript events during the action lifecycle that you can listen to.
const form = document.querySelector('form');
// Fires when action starts
form.addEventListener('lvt:pending', (e) => {
console.log('Submitting...');
// Show loading spinner
});
// Fires on validation errors
form.addEventListener('lvt:error', (e) => {
console.log('Errors:', e.detail.errors);
// Display error messages
});
// Fires on successful action (no errors)
form.addEventListener('lvt:success', (e) => {
console.log('Saved!');
// Show success message, redirect, etc.
});
// Always fires when action completes (success or error)
form.addEventListener('lvt:done', (e) => {
console.log('Completed');
// Hide loading spinner
});Lifecycle events also bubble to the document level:
// Listen for any action lifecycle events
document.addEventListener('lvt:pending', (e) => {
console.log('Action starting:', e.detail.action);
});
document.addEventListener('lvt:success', (e) => {
console.log('Action succeeded:', e.detail.action);
});form.addEventListener('lvt:success', (e) => {
console.log(e.detail);
// {
// action: "save",
// data: {...},
// meta: {
// success: true,
// errors: {}
// }
// }
});Reactive attributes allow declarative DOM manipulation in response to action lifecycle events or native DOM events, without writing JavaScript.
lvt-el:{method}:on:{trigger}="param"
lvt-el:{method}:on:{action}:{trigger}="param"
Where {trigger} is a lifecycle state or any native DOM event (see below).
| Event | Description |
|---|---|
pending |
Action started, waiting for server response |
success |
Action completed successfully (no validation errors) |
error |
Action completed with validation errors |
done |
Action completed (regardless of success/error) |
In addition to lifecycle states, lvt-el: supports native DOM events as triggers.
These execute client-side with no server round-trip.
| Trigger | DOM Event | Use case |
|---|---|---|
click |
click |
Toggle visibility on click |
focusin |
focusin |
Open panel when focus enters (bubbles) |
focusout |
focusout |
Close panel when focus leaves (bubbles) |
mouseenter |
mouseenter |
Show on hover |
mouseleave |
mouseleave |
Hide on hover end |
click-away |
(synthetic) | Close when clicking outside element |
| Any other | Corresponding DOM event | Custom behavior |
| Method | Description | Param |
|---|---|---|
reset |
Calls form.reset() |
None |
addClass |
Adds CSS class(es) | Space-separated classes |
removeClass |
Removes CSS class(es) | Space-separated classes |
toggleClass |
Toggles CSS class(es) | Space-separated classes |
setAttr |
Sets an attribute | name:value format |
toggleAttr |
Toggles a boolean attribute | Attribute name |
Global - Reacts to any action:
<!-- Reset form on any successful action -->
<form name="save" method="POST" lvt-el:reset:on:success>
<input name="title">
<button type="submit">Save</button>
</form>Action-Specific - Reacts only to a specific action:
<!-- Reset form only when 'create-todo' succeeds -->
<form name="create-todo" method="POST" lvt-el:reset:on:create-todo:success>
<input name="title">
<button type="submit">Add Todo</button>
</form>Loading States:
<button name="save"
lvt-el:toggleAttr:on:pending="disabled"
lvt-el:addClass:on:pending="opacity-50 cursor-wait"
lvt-el:toggleAttr:on:done="disabled"
lvt-el:removeClass:on:done="opacity-50 cursor-wait">
Save
</button>Form Reset on Success:
<form name="create-todo" method="POST" lvt-el:reset:on:success>
<input type="text" name="title" placeholder="New todo">
<button type="submit">Add</button>
</form>Accessibility States:
<button name="submit"
lvt-el:setAttr:on:pending="aria-busy:true"
lvt-el:setAttr:on:done="aria-busy:false">
Submit
</button>Error Indicators:
<!-- Visual feedback on form-level errors -->
<!-- Note: For field-specific validation errors, use .lvt.HasError and .lvt.Error helpers -->
<div
lvt-el:addClass:on:error="border-red-500"
lvt-el:removeClass:on:success="border-red-500">
<form name="save" method="POST">
<input name="email">
<button type="submit">Save</button>
</form>
</div>Input Validation State:
<!-- For form inputs with validation errors -->
<input
type="email"
name="email"
lvt-el:setAttr:on:error="aria-invalid:true"
lvt-el:setAttr:on:success="aria-invalid:false">Multiple Actions on Same Element:
<button name="save"
lvt-el:toggleAttr:on:pending="disabled"
lvt-el:toggleAttr:on:done="disabled"
lvt-el:addClass:on:pending="loading"
lvt-el:removeClass:on:done="loading"
lvt-el:addClass:on:success="success"
lvt-el:addClass:on:error="error">
Save
</button>Note: When multiple reactive attributes target the same lifecycle event, all matching methods execute in DOM order. For example, lvt-el:addClass:on:pending="loading" and lvt-el:addClass:on:pending="disabled" will both add their respective classes.
<!-- Toggle dropdown visibility on click -->
<div lvt-el:toggleClass:on:click="open"
lvt-el:removeClass:on:click-away="open">
...
</div>
<!-- Show tooltip on hover -->
<div lvt-el:addClass:on:mouseenter="visible"
lvt-el:removeClass:on:mouseleave="visible">
...
</div>
<!-- Open suggestions on focus, close on blur -->
<div lvt-el:addClass:on:focusin="open"
lvt-el:removeClass:on:focusout="open"
lvt-el:removeClass:on:click-away="open">
<input type="text" ...>
<ul data-suggestions>...</ul>
</div>When the same reactive attribute applies to multiple actions, use bracket syntax to avoid repetition:
<!-- Shorthand: bracket syntax -->
<button
lvt-on:click="save"
lvt-el:addClass:on:[save,delete]:pending="opacity-50"
lvt-el:toggleAttr:on:[save,delete]:pending="disabled">
Save
</button>
<!-- Equivalent expanded form -->
<button
lvt-on:click="save"
lvt-el:addClass:on:save:pending="opacity-50"
lvt-el:addClass:on:delete:pending="opacity-50"
lvt-el:toggleAttr:on:save:pending="disabled"
lvt-el:toggleAttr:on:delete:pending="disabled">
Save
</button>Bracket expansion works for lvt-el:*, lvt-fx:*, and lvt-form:* prefixes, including boolean attributes (no ="value"). Bracket syntax works everywhere in templates, including inside {{range}} and {{if}} blocks.
Note: Attribute values must be quoted (
="..."or='...'). Unquoted values likelvt-el:addClass:on:[a,b]:pending=loadingwill produce incorrect output. Bracket expansion operates on raw template source, so patterns inside<script>or<style>blocks would also be expanded if they match — though thelvt-el:/lvt-fx:/lvt-form:prefixes make false matches unlikely in practice.
LiveTemplate provides server-side validation with automatic error display.
import "github.com/go-playground/validator/v10"
var validate = validator.New()
type TodoInput struct {
Title string `json:"title" validate:"required,min=3,max=100"`
Tags string `json:"tags" validate:"required"`
}
func (c *TodoController) Add(state TodoState, ctx *livetemplate.Context) (TodoState, error) {
var input TodoInput
if err := ctx.BindAndValidate(&input, validate); err != nil {
return state, err // Errors automatically sent to client
}
// Input is valid, proceed
state.Todos = append(state.Todos, Todo{Title: input.Title})
return state, nil
}<form name="add" method="POST">
<div>
<label for="title">Title</label>
<input
type="text"
name="title"
id="title"
{{if .lvt.HasError "title"}}aria-invalid="true"{{end}}>
{{if .lvt.HasError "title"}}
<small class="error">{{.lvt.Error "title"}}</small>
{{end}}
</div>
<button type="submit">Add Todo</button>
</form>In templates:
{{.lvt.HasError "field"}}- Check if field has error{{.lvt.Error "field"}}- Get error message for field{{.lvt.Errors}}- Get all errors map
Control how often events are processed using debounce and throttle.
Wait for user to stop typing before triggering action.
<!-- Wait 300ms after user stops typing -->
<input
lvt-on:input="search"
lvt-mod:debounce="300"
name="query"
placeholder="Search...">Use for: Search inputs, auto-save, validation
Limit event frequency to at most once per interval.
<!-- Fire at most once every 100ms -->
<div lvt-on:window:scroll="loadMore" lvt-mod:throttle="100">Use for: Scroll events, resize events, mouse tracking
Directives provide declarative behavior for common UI patterns.
Control scroll behavior after DOM updates.
<!-- Scroll to bottom -->
<div lvt-fx:scroll="bottom" class="chat-messages">
{{range .Messages}}
<div>{{.Text}}</div>
{{end}}
</div>
<!-- Sticky scroll (only if user is near bottom) -->
<div lvt-fx:scroll="bottom-sticky" style="--lvt-scroll-threshold: 100px">
{{range .Logs}}
<div>{{.}}</div>
{{end}}
</div>
<!-- Scroll to top -->
<div lvt-fx:scroll="top">...</div>
<!-- Preserve scroll position -->
<div lvt-fx:scroll="preserve">...</div>| Attribute | Description |
|---|---|
lvt-fx:scroll |
Scroll mode: bottom, bottom-sticky, top, preserve |
--lvt-scroll-behavior |
CSS custom property: auto (default), smooth |
--lvt-scroll-threshold |
CSS custom property: pixel threshold for sticky scroll (default: 100px) |
Temporarily highlight elements after updates.
<!-- Highlight updated item -->
<div lvt-fx:highlight="flash" style="--lvt-highlight-color: #ffc107; --lvt-highlight-duration: 500ms">
{{.UpdatedContent}}
</div>| Attribute | Description |
|---|---|
lvt-fx:highlight |
Highlight mode: flash |
--lvt-highlight-color |
CSS custom property: background color (default: #ffc107) |
--lvt-highlight-duration |
CSS custom property: duration (default: 500ms) |
Apply entrance animations to elements.
<!-- Fade in -->
<div lvt-fx:animate="fade">New content</div>
<!-- Slide in -->
<div lvt-fx:animate="slide" style="--lvt-animate-duration: 300ms">Slide content</div>
<!-- Scale in -->
<div lvt-fx:animate="scale">Pop content</div>| Attribute | Description |
|---|---|
lvt-fx:animate |
Animation type: fade, slide, scale |
--lvt-animate-duration |
CSS custom property: duration (default: 300ms) |
lvt-fx: attributes support three trigger modes:
Implicit (no :on:) -- fires on every DOM content update:
<div lvt-fx:scroll="bottom-sticky">...</div>
<div lvt-fx:highlight="flash">...</div>Lifecycle (:on:{state}) -- fires on action lifecycle state:
<div lvt-fx:highlight:on:success="flash">Saved!</div>
<div lvt-fx:highlight:on:save:success="flash">Save confirmed</div>DOM Event (:on:{event}) -- fires on any native DOM event:
<div lvt-fx:highlight:on:click="flash">Click to highlight</div>
<div lvt-fx:highlight:on:mouseenter="flash">Hover to highlight</div>
<div lvt-fx:animate:on:click="fade">Click to animate</div>Use the native <dialog> element with command/commandfor for modal dialogs. No lvt-* attributes needed — this is a Tier 1 pattern.
The client polyfills the Invoker Commands API for browsers that don't support it natively (Firefox, Safari as of April 2026). The polyfill calls .showModal() / .close() on the target <dialog>, providing backdrop, focus trapping, and Escape key handling across all browsers. Feature detection via commandForElement makes the polyfill a no-op when native support lands.
| Button Attribute | Target | Effect |
|---|---|---|
command="show-modal" commandfor="dialog-id" |
<dialog id="dialog-id"> |
Calls .showModal() |
command="close" commandfor="dialog-id" |
<dialog id="dialog-id"> |
Calls .close() |
Any form inside a <dialog> that completes successfully will have its parent dialog closed automatically. This means the dialog stays open for validation errors but closes on success — no extra attributes needed.
A <form method="dialog"> inside a <dialog> closes the dialog immediately on submit (before the server responds). Use this only when you don't need server-side validation feedback inside the dialog.
See Progressive Complexity Guide — Dialogs for the full walkthrough.
For modals whose visibility is controlled by server state (e.g., confirmation dialogs triggered by a server action), use the lvt/components/modal package. See the todos example.
Handle file uploads with progress tracking.
<form method="POST">
<input type="file" lvt-upload="avatar" name="avatar">
<button name="save-profile" type="submit">Save</button>
</form><input type="file" lvt-upload="documents" name="docs" multiple>| Attribute | Description |
|---|---|
lvt-upload |
Upload identifier for tracking |
Files are automatically uploaded when the form is submitted, with progress events emitted.
By default, forms reset after successful submission. Use lvt-form:preserve to keep form values:
<form name="search" method="POST" lvt-form:preserve>
<input name="query">
<button type="submit">Search</button>
</form>Show loading state on submit buttons:
<form method="POST">
<input name="title">
<button name="save" type="submit" lvt-form:disable-with="Saving...">Save</button>
</form>Use standard onsubmit for confirmation dialogs:
<form method="POST" onsubmit="return confirm('Are you sure?')">
<button name="delete">Delete</button>
</form>Complete reference of all lvt-* attributes.
| Attribute | Description | Example |
|---|---|---|
lvt-on:click |
Click event | <button lvt-on:click="save"> |
lvt-on:change |
Input change event | <select lvt-on:change="sort"> |
lvt-on:input |
Input event (every keystroke) | <input lvt-on:input="search"> |
lvt-on:keydown |
Keydown event | <input lvt-on:keydown="submit"> |
lvt-on:keyup |
Keyup event | <input lvt-on:keyup="handle"> |
lvt-on:focus |
Focus event | <input lvt-on:focus="highlight"> |
lvt-on:blur |
Blur event | <input lvt-on:blur="validate"> |
lvt-on:mouseenter |
Mouse enter event | <div lvt-on:mouseenter="show"> |
lvt-on:mouseleave |
Mouse leave event | <div lvt-on:mouseleave="hide"> |
lvt-on:click-away |
Click outside element | <div lvt-on:click-away="close"> |
lvt-on:window:keydown |
Global keydown | <div lvt-on:window:keydown="close"> |
lvt-on:window:keyup |
Global keyup | <div lvt-on:window:keyup="handle"> |
lvt-on:window:scroll |
Window scroll | <div lvt-on:window:scroll="load"> |
lvt-on:window:resize |
Window resize | <div lvt-on:window:resize="adjust"> |
lvt-on:window:focus |
Window focus | <div lvt-on:window:focus="refresh"> |
lvt-on:window:blur |
Window blur | <div lvt-on:window:blur="pause"> |
| Attribute | Description | Example |
|---|---|---|
lvt-data-<key> |
Pass data to action | lvt-data-id="{{.ID}}" |
lvt-value-<key> |
Pass value to action | lvt-value-count="{{.Count}}" |
Note: Both lvt-data-* and lvt-value-* attributes are accessible via ctx.GetString(), ctx.GetInt(), etc.
| Attribute | Description | Example |
|---|---|---|
lvt-el:reset:on:{trigger} |
Reset form on trigger | lvt-el:reset:on:success |
lvt-el:addClass:on:{trigger} |
Add class(es) on trigger | lvt-el:addClass:on:pending="loading" |
lvt-el:removeClass:on:{trigger} |
Remove class(es) on trigger | lvt-el:removeClass:on:done="loading" |
lvt-el:toggleClass:on:{trigger} |
Toggle class(es) on trigger | lvt-el:toggleClass:on:click="active" |
lvt-el:setAttr:on:{trigger} |
Set attribute on trigger | lvt-el:setAttr:on:pending="aria-busy:true" |
lvt-el:toggleAttr:on:{trigger} |
Toggle boolean attr on trigger | lvt-el:toggleAttr:on:pending="disabled" |
Note: {trigger} can be a lifecycle state (pending, success, error, done), any native DOM event (click, focusin, focusout, mouseenter, mouseleave, etc.), or the synthetic click-away. For action-specific: lvt-el:reset:on:create-todo:success.
| Attribute | Description | Example |
|---|---|---|
lvt-key |
Filter keyboard events by key | lvt-key="Enter" |
lvt-mod:debounce |
Debounce delay in milliseconds | lvt-mod:debounce="300" |
lvt-mod:throttle |
Throttle interval in milliseconds | lvt-mod:throttle="100" |
| Attribute | Description | Example |
|---|---|---|
lvt-form:action |
Explicit action routing on form | <form lvt-form:action="checkout"> |
lvt-form:preserve |
Keep form values after submit | <form lvt-form:preserve> |
lvt-form:disable-with |
Button text during submit | lvt-form:disable-with="Saving..." |
lvt-form:no-intercept |
Opt-out of form interception | <form lvt-form:no-intercept> |
lvt-nav:no-intercept |
Opt-out of link interception | <a lvt-nav:no-intercept> |
| Attribute | Description | Example |
|---|---|---|
lvt-fx:scroll |
Scroll behavior | lvt-fx:scroll="bottom" |
lvt-fx:highlight |
Highlight effect | lvt-fx:highlight="flash" |
lvt-fx:animate |
Entrance animation | lvt-fx:animate="fade" |
Directives use CSS custom properties for configuration: --lvt-scroll-behavior, --lvt-scroll-threshold, --lvt-highlight-color, --lvt-highlight-duration, --lvt-animate-duration.
| Attribute | Description | Example |
|---|---|---|
lvt-upload |
File upload identifier | lvt-upload="avatar" |
For lvt-key attribute:
- Letter keys:
"a","b","c", etc. - Special keys:
"Enter","Escape","Space","Tab","Backspace","Delete" - Arrow keys:
"ArrowUp","ArrowDown","ArrowLeft","ArrowRight" - Function keys:
"F1","F2", etc. - Modifiers: Check
e.ctrlKey,e.shiftKey,e.altKey,e.metaKeyin event listeners
Prefer declarative reactive attributes over JavaScript for common UI patterns:
<!-- Good: Declarative loading state -->
<button name="save"
lvt-el:toggleAttr:on:pending="disabled"
lvt-el:addClass:on:pending="opacity-50"
lvt-el:toggleAttr:on:done="disabled"
lvt-el:removeClass:on:done="opacity-50">
Save
</button>
<!-- Avoid: JavaScript for simple loading state --><input
lvt-on:input="search"
lvt-mod:debounce="300"
name="query"><div lvt-on:window:scroll="loadMore" lvt-mod:throttle="100"><input
type="email"
name="email"
{{if .lvt.HasError "email"}}aria-invalid="true"{{end}}>
{{if .lvt.HasError "email"}}
<span class="error">{{.lvt.Error "email"}}</span>
{{end}}Use reactive attributes for automatic form reset:
<form name="create-todo" method="POST" lvt-el:reset:on:success>
<input name="title" placeholder="New todo">
<button type="submit">Add</button>
</form><button name="save"
lvt-el:setAttr:on:pending="aria-busy:true"
lvt-el:setAttr:on:done="aria-busy:false"
lvt-el:setAttr:on:error="aria-invalid:true">
Save
</button>document.addEventListener('lvt:connected', () => {
console.log('WebSocket connected');
});
document.addEventListener('lvt:disconnected', () => {
console.log('WebSocket disconnected');
});form.addEventListener('lvt:pending', (e) => {
const formData = new FormData(e.target);
console.log('Submitting:', Object.fromEntries(formData));
});- Go API Reference - Server-side API
- Error Handling Reference - Validation, error display, client-side handling
- Template Support Matrix - Supported Go template features
- Architecture - System architecture
- Contributing Guide - How to contribute