Skip to main content
Fuego integrates seamlessly with HTMX for building interactive web applications without client-side JavaScript frameworks.

Why HTMX?

  • No JavaScript - Server renders HTML, HTMX handles interactivity
  • Progressive enhancement - Works without JS enabled
  • Simpler architecture - No separate frontend build
  • Smaller bundles - HTMX is ~14KB minified

Setup

The default layout includes HTMX from CDN:
// app/layout.templ
templ Layout(title string) {
    <!DOCTYPE html>
    <html>
    <head>
        <script src="https://unpkg.com/[email protected]"></script>
    </head>
    <body>
        { children... }
    </body>
    </html>
}

Core Concepts

Loading Data

Use hx-get to load content:
<div hx-get="/api/users" hx-trigger="load">
    Loading...
</div>
On page load, HTMX fetches /api/users and replaces the div content.

Form Submission

Use hx-post for forms:
<form hx-post="/api/users" hx-target="#user-list">
    <input name="name" required/>
    <button type="submit">Add User</button>
</form>

Delete Operations

<button hx-delete="/api/users?id=123" hx-target="#user-list">
    Delete
</button>

HTMX Attributes

AttributeDescription
hx-getGET request to URL
hx-postPOST request to URL
hx-putPUT request to URL
hx-deleteDELETE request to URL
hx-triggerWhen to trigger (load, click, etc.)
hx-targetElement to update with response
hx-swapHow to swap content (innerHTML, outerHTML, etc.)
hx-confirmShow confirmation dialog

Server-Side Patterns

Detecting HTMX Requests

func Get(c *fuego.Context) error {
    if c.IsHTMX() {
        // Return partial HTML for HTMX
        return c.HTML(200, "<li>New item</li>")
    }
    // Return full page or JSON
    return c.JSON(200, items)
}

Returning HTML Fragments

func Get(c *fuego.Context) error {
    tasks := taskStore.List()
    
    html := "<ul>"
    for _, task := range tasks {
        html += fmt.Sprintf("<li>%s</li>", task.Title)
    }
    html += "</ul>"
    
    return c.HTML(200, html)
}

Using templ for Partials

// components.templ
templ TaskList(tasks []Task) {
    <ul>
        for _, task := range tasks {
            <li>{ task.Title }</li>
        }
    </ul>
}

// route.go
func Get(c *fuego.Context) error {
    tasks := taskStore.List()
    return c.Render(components.TaskList(tasks))
}

Common Patterns

Load on Page Load

<div id="data" hx-get="/api/data" hx-trigger="load">
    <p class="loading">Loading...</p>
</div>

Click to Load More

<button hx-get="/api/items?page=2" hx-target="#item-list" hx-swap="beforeend">
    Load More
</button>

Form with Reset

<form 
    hx-post="/api/tasks" 
    hx-target="#task-list"
    hx-on::after-request="this.reset()"
>
    <input name="title" required/>
    <button type="submit">Add</button>
</form>

Toggle Checkbox

<input 
    type="checkbox" 
    hx-post="/api/tasks/toggle?id=123"
    hx-target="#task-list"
    checked
/>

Delete with Confirmation

<button 
    hx-delete="/api/tasks?id=123" 
    hx-target="#task-list"
    hx-confirm="Are you sure?"
>
    Delete
</button>

Infinite Scroll

<div 
    hx-get="/api/items?page=2" 
    hx-trigger="revealed"
    hx-swap="afterend"
>
    Loading more...
</div>

Search with Debounce

<input 
    type="search" 
    name="q"
    hx-get="/api/search"
    hx-trigger="keyup changed delay:300ms"
    hx-target="#results"
/>

Polling

<div hx-get="/api/status" hx-trigger="every 5s">
    Status: Active
</div>

Full Example: Task Manager

Page Template

// app/dashboard/page.templ
package dashboard

templ Page() {
    <div class="container mx-auto p-4">
        <h1 class="text-2xl font-bold mb-4">Tasks</h1>
        
        <!-- Task list loads on page load -->
        <div id="task-list" hx-get="/api/tasks" hx-trigger="load">
            Loading tasks...
        </div>
        
        <!-- Add task form -->
        <form 
            class="mt-4 flex gap-2"
            hx-post="/api/tasks" 
            hx-target="#task-list"
            hx-on::after-request="this.reset()"
        >
            <input 
                type="text" 
                name="title" 
                placeholder="New task..." 
                class="flex-1 border rounded px-3 py-2"
                required
            />
            <button 
                type="submit"
                class="bg-blue-500 text-white px-4 py-2 rounded"
            >
                Add
            </button>
        </form>
    </div>
}

API Routes

// app/api/tasks/route.go
package tasks

import "github.com/abdul-hamid-achik/fuego/pkg/fuego"

// GET /api/tasks - List all tasks
func Get(c *fuego.Context) error {
    tasks := taskStore.List()
    return c.HTML(200, renderTaskList(tasks))
}

// POST /api/tasks - Create a task
func Post(c *fuego.Context) error {
    title := c.FormValue("title")
    if title == "" {
        return c.HTML(400, `<p class="text-red-500">Title required</p>`)
    }
    
    taskStore.Add(title)
    return c.HTML(200, renderTaskList(taskStore.List()))
}

// DELETE /api/tasks - Delete a task
func Delete(c *fuego.Context) error {
    id := c.QueryInt("id", 0)
    taskStore.Delete(id)
    return c.HTML(200, renderTaskList(taskStore.List()))
}

func renderTaskList(tasks []Task) string {
    if len(tasks) == 0 {
        return `<p class="text-gray-500">No tasks yet</p>`
    }
    
    html := `<ul class="space-y-2">`
    for _, task := range tasks {
        html += fmt.Sprintf(`
            <li class="flex items-center gap-2 p-2 bg-gray-50 rounded">
                <input 
                    type="checkbox" 
                    hx-post="/api/tasks/toggle?id=%d"
                    hx-target="#task-list"
                    %s
                />
                <span class="%s">%s</span>
                <button 
                    hx-delete="/api/tasks?id=%d"
                    hx-target="#task-list"
                    class="ml-auto text-red-500"
                >
                    Delete
                </button>
            </li>`,
            task.ID,
            ifChecked(task.Completed),
            ifStrikethrough(task.Completed),
            task.Title,
            task.ID,
        )
    }
    html += `</ul>`
    return html
}

Toggle Route

// app/api/tasks/toggle/route.go
package toggle

import "github.com/abdul-hamid-achik/fuego/pkg/fuego"

func Post(c *fuego.Context) error {
    id := c.QueryInt("id", 0)
    taskStore.Toggle(id)
    return c.HTML(200, renderTaskList(taskStore.List()))
}

Best Practices

Only return the HTML that needs to change, not the entire page.
Give your target elements clear, descriptive IDs.
Use hx-indicator to show loading spinners.
Always validate input on the server, even with client hints.

Next Steps