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>&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>}

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 templ
Tip

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

Next Steps