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 src="/static/js/htmx.min.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>}HTMX Patterns
Click to Load
templ LoadButton() { <button hx-get="/api/data" hx-target="#result" hx-swap="innerHTML" hx-indicator="#spinner" > Load Data </button> <span id="spinner" class="htmx-indicator">Loading...</span> <div id="result"></div>}Form Submission
templ TodoForm() { <form hx-post="/todos" hx-target="#todo-list" hx-swap="beforeend" hx-on::after-request="this.reset()" > <input type="text" name="title" placeholder="New todo..." required /> <button type="submit">Add</button> </form> <div id="todo-list"></div>}Delete with Confirmation
templ DeleteButton(id string) { <button hx-delete={ "/items/" + id } hx-target={ "#item-" + id } hx-swap="delete" hx-confirm="Are you sure you want to delete this?" class="btn-danger" > Delete </button>}Toggle/Update
templ TodoItem(todo Todo) { <div id={ "todo-" + todo.ID } class="todo-item"> <input type="checkbox" checked?={ todo.Done } hx-post={ "/todos/" + todo.ID + "/toggle" } hx-target={ "#todo-" + todo.ID } hx-swap="outerHTML" /> <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 hx-get={ fmt.Sprintf("/items?page=%d", page+1) } hx-trigger="revealed" hx-swap="afterend" > @ItemCard(item) </div> } else { @ItemCard(item) } }}Out-of-Band Updates
// Update multiple elements with one responsetempl NewItem(item Item, totalCount int) { // Main response - goes to hx-target @ItemCard(item) // Out-of-band update - updates #item-count <span id="item-count" hx-swap-oob="true"> { fmt.Sprintf("%d items", totalCount) } </span>}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
- HTMX Integration - More HTMX patterns and techniques
- Routing & Handlers - Connect templates to handlers
- templ Documentation - Full templ reference
