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>&copy; 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 templ
Tip

When using irgo dev, template generation is handled automatically via the air configuration.

Next Steps