Examples
Learn irgo through complete, working examples. Each example demonstrates key patterns and can be used as a starting point for your own apps.
Todo App
The classic todo app demonstrates CRUD operations, form handling, and HTMX interactions.
Handler
handlers/todos.go
package handlersimport ( "myapp/templates" "github.com/stukennedy/irgo/pkg/render" "github.com/stukennedy/irgo/pkg/router")type Todo struct { ID string Title string Done bool}var todos = []Todo{}var nextID = 1func ListTodos(ctx *router.Context) (string, error) { return renderer.Render(templates.TodoList(todos))}func CreateTodo(ctx *router.Context) (string, error) { title := ctx.FormValue("title") if title == "" { return "", ctx.BadRequest("Title is required") } todo := Todo{ ID: fmt.Sprintf("%d", nextID), Title: title, Done: false, } nextID++ todos = append(todos, todo) return renderer.Render(templates.TodoItem(todo))}func ToggleTodo(ctx *router.Context) (string, error) { id := ctx.Param("id") for i := range todos { if todos[i].ID == id { todos[i].Done = !todos[i].Done return renderer.Render(templates.TodoItem(todos[i])) } } return "", ctx.NotFound("Todo not found")}func DeleteTodo(ctx *router.Context) (string, error) { id := ctx.Param("id") for i := range todos { if todos[i].ID == id { todos = append(todos[:i], todos[i+1:]...) return "", nil // hx-swap="delete" removes the element } } return "", ctx.NotFound("Todo not found")}Templates
templates/todos.templ
package templatestempl TodoPage() { @Layout("Todos") { <div class="container"> <h1>My Todos</h1> @TodoForm() <div id="todo-list"> for _, todo := range todos { @TodoItem(todo) } </div> </div> }}templ TodoForm() { <form hx-post="/todos" hx-target="#todo-list" hx-swap="beforeend" hx-on::after-request="this.reset()" class="todo-form" > <input type="text" name="title" placeholder="What needs to be done?" required autofocus /> <button type="submit">Add</button> </form>}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("completed", todo.Done) }> { todo.Title } </span> <button hx-delete={ "/todos/" + todo.ID } hx-target={ "#todo-" + todo.ID } hx-swap="delete" hx-confirm="Delete this todo?" class="delete-btn" > × </button> </div>}templ TodoList(todos []Todo) { for _, todo := range todos { @TodoItem(todo) } if len(todos) == 0 { <p class="empty-state">No todos yet. Add one above!</p> }}Routes
app/app.go
func NewRouter() *router.Router { r := router.New() r.GET("/", handlers.ListTodos) r.POST("/todos", handlers.CreateTodo) r.POST("/todos/{id}/toggle", handlers.ToggleTodo) r.DELETE("/todos/{id}", handlers.DeleteTodo) return r}Search with Debounce
Real-time search with debounced input.
templates/search.templ
templ SearchPage() { @Layout("Search") { <div class="search-container"> <input type="text" name="q" placeholder="Search users..." hx-get="/search" hx-trigger="keyup changed delay:300ms" hx-target="#results" hx-indicator="#spinner" /> <span id="spinner" class="htmx-indicator">Searching...</span> <div id="results"></div> </div> }}templ SearchResults(users []User, query string) { if len(users) == 0 { <p class="no-results">No users found for "{ query }"</p> } else { <ul class="user-list"> for _, user := range users { @UserCard(user) } </ul> }}handlers/search.go
func Search(ctx *router.Context) (string, error) { query := ctx.Query("q") if query == "" { return "", nil } users := searchUsers(query) return renderer.Render(templates.SearchResults(users, query))}Modal Dialog
A reusable modal pattern with HTMX.
templates/modal.templ
templ ModalContainer() { <div id="modal-container"></div>}templ ConfirmModal(title string, message string, action string) { <div class="modal-backdrop" hx-on:click="this.remove()"> <div class="modal" hx-on:click="event.stopPropagation()"> <h2>{ title }</h2> <p>{ message }</p> <div class="modal-actions"> <button hx-post={ action } hx-target="#modal-container" hx-swap="innerHTML" class="btn-danger" > Confirm </button> <button hx-on:click="this.closest('.modal-backdrop').remove()" class="btn-secondary" > Cancel </button> </div> </div> </div>}templ DeleteUserButton(userID string) { <button hx-get={ "/modal/delete-user/" + userID } hx-target="#modal-container" hx-swap="innerHTML" class="btn-danger" > Delete User </button>}handlers/modal.go
func ShowDeleteModal(ctx *router.Context) (string, error) { userID := ctx.Param("id") return renderer.Render(templates.ConfirmModal( "Delete User", "Are you sure you want to delete this user?", "/users/" + userID, ))}func DeleteUser(ctx *router.Context) (string, error) { userID := ctx.Param("id") deleteUser(userID) // Close modal and trigger refresh ctx.Trigger("userDeleted") return "", nil}Infinite Scroll
Load more items as the user scrolls.
templates/feed.templ
templ Feed(posts []Post, page int, hasMore bool) { <div class="feed"> for i, post := range posts { if i == len(posts)-1 && hasMore { // Last item triggers loading more <div hx-get={ fmt.Sprintf("/feed?page=%d", page+1) } hx-trigger="revealed" hx-swap="afterend" > @PostCard(post) </div> } else { @PostCard(post) } } if !hasMore && len(posts) > 0 { <p class="end-of-feed">You've reached the end!</p> } </div>}templ PostCard(post Post) { <article class="post-card"> <h3>{ post.Title }</h3> <p>{ post.Excerpt }</p> <time>{ post.CreatedAt.Format("Jan 2, 2006") }</time> </article>}Tabs
Tab navigation with HTMX.
templates/tabs.templ
templ TabsPage(activeTab string) { @Layout("Dashboard") { <div class="tabs-container"> <nav class="tab-nav"> <button class={ "tab", templ.KV("active", activeTab == "overview") } hx-get="/dashboard/overview" hx-target="#tab-content" hx-push-url="true" > Overview </button> <button class={ "tab", templ.KV("active", activeTab == "analytics") } hx-get="/dashboard/analytics" hx-target="#tab-content" hx-push-url="true" > Analytics </button> <button class={ "tab", templ.KV("active", activeTab == "settings") } hx-get="/dashboard/settings" hx-target="#tab-content" hx-push-url="true" > Settings </button> </nav> <div id="tab-content"> { children... } </div> </div> }}templ OverviewTab() { <div class="tab-panel"> <h2>Overview</h2> <p>Your dashboard overview content here.</p> </div>}Running the Examples
The todo example is included in the irgo repository:
git clone https://github.com/stukennedy/irgo.gitcd irgo/examples/todogo mod tidybun installirgo devNext Steps
- Getting Started - Create your own project
- HTMX Integration - More HTMX patterns
- GitHub - Browse the source code
