Templ Templates
irgo uses templ for type-safe HTML templating. Templ compiles to Go code, giving you compile-time error checking and full IDE support.
Basic Syntax
templates/greeting.templ
package templates// Simple componenttempl Greeting(name string) { <div class="greeting"> <h1>Hello, { name }!</h1> </div>}After running templ generate, this creates greeting_templ.go.
Rendering Templates
import ( "myapp/templates" "github.com/stukennedy/irgo/pkg/render")renderer := render.NewTemplRenderer()func handler(ctx *router.Context) (string, error) { html, err := renderer.Render(templates.Greeting("World")) if err != nil { return "", err } return html, nil}Component Parameters
// Multiple parameterstempl UserCard(name string, email string, avatar string) { <div class="user-card"> <img src={ avatar } alt={ name } /> <h3>{ name }</h3> <p>{ email }</p> </div>}// Struct parametertempl UserProfile(user User) { <div class="profile"> <h1>{ user.Name }</h1> <p>{ user.Email }</p> <span>Joined: { user.CreatedAt.Format("Jan 2, 2006") }</span> </div>}Conditionals
templ Status(active bool) { if active { <span class="badge badge-success">Active</span> } else { <span class="badge badge-danger">Inactive</span> }}templ UserBadge(user User) { <div class="badge"> if user.IsAdmin { <span class="admin">Admin</span> } else if user.IsModerator { <span class="mod">Moderator</span> } else { <span class="user">Member</span> } </div>}Loops
templ UserList(users []User) { <ul class="user-list"> for _, user := range users { <li> @UserCard(user.Name, user.Email, user.Avatar) </li> } </ul>}templ NumberedList(items []string) { <ol> for i, item := range items { <li>{ fmt.Sprintf("%d. %s", i+1, item) }</li> } </ol>}Component Composition
Use @ to include other components:
templ Layout(title string) { <!DOCTYPE html> <html> <head> <title>{ title }</title> <link rel="stylesheet" href="/static/css/output.css" /> <script type="module" src="/static/js/datastar.js"></script> </head> <body> @Header() <main> { children... } </main> @Footer() </body> </html>}templ Header() { <header class="header"> <a href="/">Home</a> <nav> <a href="/about">About</a> <a href="/contact">Contact</a> </nav> </header>}templ Footer() { <footer class="footer"> <p>© 2024 My App</p> </footer>}Using Layouts
templ HomePage() { @Layout("Home") { <h1>Welcome!</h1> <p>This is the home page.</p> }}templ AboutPage() { @Layout("About") { <h1>About Us</h1> <p>Learn more about us.</p> }}Note
The { children... } syntax lets you pass content into a component. It's like React's children prop or Vue's slots.
Dynamic Attributes
Conditional Attributes
// Boolean attribute (checked, disabled, etc.)templ Checkbox(checked bool) { <input type="checkbox" checked?={ checked } />}// Dynamic valuetempl SelectedOption(value string, selected string) { <option value={ value } selected?={ value == selected }> { value } </option>}Conditional Classes
// Using templ.KV for conditional classestempl TodoItem(todo Todo) { <div class={ "todo-item", templ.KV("completed", todo.Done) }> <span class={ templ.KV("line-through", todo.Done) }> { todo.Title } </span> </div>}// Multiple conditional classestempl Button(primary bool, large bool, disabled bool) { <button class={ "btn", templ.KV("btn-primary", primary), templ.KV("btn-lg", large), templ.KV("btn-disabled", disabled), } disabled?={ disabled }> { children... } </button>}Safe URLs
// Use templ.SafeURL for dynamic URLstempl Link(url string, text string) { <a href={ templ.SafeURL(url) }>{ text }</a>}// For URLs you constructtempl UserLink(userID string) { <a href={ templ.SafeURL("/users/" + userID) }> View Profile </a>}Datastar Patterns
Click to Load
templ LoadButton() { <div data-signals="{loading: false}"> <button data-on:click="$loading = true; @get('/api/data')" data-attr:disabled="$loading" > <span data-show="!$loading">Load Data</span> <span data-show="$loading">Loading...</span> </button> <div id="result"></div> </div>}Form Submission
templ TodoForm() { <form data-signals="{title: ''}" data-on:submit__prevent="@post('/todos')" > <input type="text" data-bind:title placeholder="New todo..." required /> <button type="submit">Add</button> </form> <div id="todo-list"></div>}Delete with Confirmation
templ DeleteButton(id string) { <div data-signals="{confirmDelete: false}"> <button data-show="!$confirmDelete" data-on:click="$confirmDelete = true" class="btn-danger" > Delete </button> <div data-show="$confirmDelete"> <span>Are you sure?</span> <button data-on:click={ "@delete('/items/" + id + "')" }>Yes</button> <button data-on:click="$confirmDelete = false">No</button> </div> </div>}Toggle/Update
templ TodoItem(todo Todo) { <div id={ "todo-" + todo.ID } class="todo-item"> <input type="checkbox" checked?={ todo.Done } data-on:click={ "@patch('/todos/" + todo.ID + "')" } /> <span class={ templ.KV("line-through", todo.Done) }> { todo.Title } </span> </div>}Infinite Scroll
templ ItemList(items []Item, page int, hasMore bool) { for i, item := range items { if i == len(items)-1 && hasMore { // Last item triggers loading more <div data-intersect={ fmt.Sprintf("@get('/items?page=%d')", page+1) }> @ItemCard(item) </div> } else { @ItemCard(item) } }}Multiple Updates
// Update multiple elements with one SSE responsefunc CreateItem(ctx *router.Context) error { item := createItem() count := getTotalCount() sse := ctx.SSE() // Update item list sse.PatchTempl(templates.ItemCard(item)) // Update counter sse.PatchTempl(templates.ItemCount(count)) // Clear input signal sse.PatchSignals(map[string]any{"title": ""}) return nil}Generating Templates
# Generate oncetempl generate# Watch for changestempl generate --watch# Or use the irgo CLIirgo templTip
When using irgo dev, template generation is handled automatically via the air configuration.
Next Steps
- Datastar Integration - More Datastar patterns and techniques
- Routing & Handlers - Connect templates to handlers
- templ Documentation - Full templ reference
