This guide walks you through setting up and registering your first HxComponents application.
myproject/
├── main.go
├── go.mod
├── components/
│ └── counter/
│ ├── counter.go
│ ├── counter.templ
│ └── counter_templ.go (generated by templ)
└── pages/
└── index.templ
go get github.com/a-h/templ
go get github.com/go-chi/chi/v5
go get github.com/ocomsoft/HxComponents
go install github.com/a-h/templ/cmd/templ@latestSee the migration guides for examples of creating components from React, Vue, or Svelte.
main.go:
package main
import (
"log"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/ocomsoft/HxComponents/components"
"myproject/components/counter"
"myproject/pages"
)
func main() {
// Create the component registry
registry := components.NewRegistry()
// Register components with their URL paths
// The string parameter becomes the URL: /component/counter
components.Register[*counter.CounterComponent](registry, "counter")
// You can register multiple components
// components.Register[*todolist.TodoListComponent](registry, "todolist")
// components.Register[*userform.UserFormComponent](registry, "userform")
// Setup router
router := chi.NewRouter()
router.Use(middleware.Logger) // Log all requests
router.Use(middleware.Recoverer) // Recover from panics
// Mount component handlers (supports both GET and POST)
router.Get("/component/*", registry.Handler)
router.Post("/component/*", registry.Handler)
// Serve your pages
router.Get("/", func(w http.ResponseWriter, r *http.Request) {
if err := pages.IndexPage().Render(r.Context(), w); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
// Serve static files (CSS, JS, images)
router.Handle("/static/*", http.StripPrefix("/static/",
http.FileServer(http.Dir("./static"))))
log.Println("Server starting on http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", router))
}pages/index.templ:
package pages
import "myproject/components/counter"
templ IndexPage() {
<!DOCTYPE html>
<html>
<head>
<title>My HxComponents App</title>
<!-- Load HTMX -->
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
</head>
<body>
<h1>Welcome to HxComponents</h1>
<!-- Render component with initial state -->
@counter.Counter(counter.CounterComponent{Count: 0})
</body>
</html>
}# Generate templ files (run this whenever you change .templ files)
templ generate
# Run your server
go run main.goVisit http://localhost:8080 and your component will be live!
// Basic registration
components.Register[*MyComponent](registry, "mycomponent")
// The component will be available at:
// - GET /component/mycomponent?param1=value1¶m2=value2
// - POST /component/mycomponent (with form data)
// Components can have initial state when embedded in pages
comp := &counter.CounterComponent{Count: 10}
@Counter(*comp)- Client makes request to
/component/counterwith form data or query params - Registry finds the registered component by name
- Registry creates a new instance of the component
- Registry parses form data into component fields (using
formtags) - Registry calls lifecycle hooks in order:
BeforeEvent(ctx, eventName)if implementedOn{EventName}()method if hxc-event parameter is presentAfterEvent(ctx, eventName)if implementedProcess(ctx)if implemented
- Component is rendered using its
Render()method - HTML is returned to the client
- HTMX swaps the content based on
hx-targetandhx-swapattributes
Request → Parse Form Data → BeforeEvent → On{EventName} → AfterEvent → Process → Render → Response
BeforeEvent(ctx context.Context, eventName string) error
- Called before any event handler
- Use for authentication, loading data from database/session
- Return error to abort the request
- Context provides request-scoped values and cancellation
On{EventName}(ctx context.Context) error
- Event handler method (e.g.,
OnSubmit,OnAddItem) - Called when
hxc-eventparameter matches the event name - Context provides request-scoped values and cancellation
- Return error to indicate failure
AfterEvent(ctx context.Context, eventName string) error
- Called after successful event handler
- Use for saving data, triggering webhooks, notifications
- Return error to indicate failure
- Context provides request-scoped values and cancellation
Process(ctx context.Context) error
- Called after all events, before rendering
- Use for final data transformations
- Return error to indicate failure
- Context provides request-scoped values and cancellation
Components can use a constructor function pattern to be easily instantiated in templ templates while still having initialization logic. This is done using the Init interface.
type Initializer interface {
Init(ctx context.Context) error
}The Init method is called:
- After form decoding (in HTTP handlers)
- Before validation
- Before event handling
- Before processing
Component struct:
package card
import (
"context"
"fmt"
"time"
)
type CardComponent struct {
Title string `form:"title"`
Count int `form:"count"`
Description string `json:"-"` // Computed field
Timestamp string `json:"-"` // Computed field
}
// Constructor function for use in templ templates
func NewCard(ctx context.Context, title string, count int) *CardComponent {
c := &CardComponent{
Title: title,
Count: count,
}
// Call Init to set up defaults and computed fields
_ = c.Init(ctx)
return c
}
// Init implements the Initializer interface
func (c *CardComponent) Init(ctx context.Context) error {
// Set defaults
if c.Title == "" {
c.Title = "Untitled Card"
}
// Compute derived fields
c.Description = fmt.Sprintf("This card contains %d item(s)", c.Count)
c.Timestamp = time.Now().Format("2006-01-02 15:04:05")
// You could also load data from database here using ctx
// user, err := db.GetUser(ctx, userID)
// if err != nil {
// return err
// }
return nil
}
// Render implements templ.Component
func (c *CardComponent) Render(ctx context.Context, w io.Writer) error {
return CardTemplate(*c).Render(ctx, w)
}package pages
import "myproject/components/card"
templ Dashboard() {
<div class="dashboard">
<h1>Dashboard</h1>
// Use constructor to create components with specific values
@card.NewCard(ctx, "Active Users", 1250)
@card.NewCard(ctx, "Revenue", 45000)
@card.NewCard(ctx, "Orders", 328)
// All cards get Init() called automatically
// Computed fields like Description and Timestamp are set
</div>
}The same component works as an HTMX endpoint:
<form hx-post="/component/card" hx-target="#result">
<input name="title" placeholder="Card Title"/>
<input name="count" type="number" value="0"/>
<button>Create Card</button>
</form>
<div id="result"></div>When submitted, the registry will:
- Decode form data into
CardComponent - Call
Init(ctx)to set defaults and computed fields - Call other lifecycle hooks if present
- Render the component
- Type-Safe: Constructor enforces required parameters at compile time
- Reusable: Same component works in templates AND as HTMX endpoint
- Initialized: Computed fields and defaults are always set
- Context-Aware: Can load data from database in
Init(ctx) - Clean Templates:
@NewCard(ctx, "Title", 100)is cleaner than setting struct fields
Request → Parse Form → Init → Validate → BeforeEvent → On{EventName} → AfterEvent → Process → Render → Response
↑
New step!
- Create component struct with
formtags for inputs - Add event handlers as
On{EventName}() errormethods - Create templ template for rendering
- Implement lifecycle hooks if needed (BeforeEvent, AfterEvent)
- Register component in main.go
- Run
templ generateto compile templates - Test your component
- See the migration guides for specific framework examples (React, Vue, Svelte)
- Read ADVANCED_PATTERNS.md for HTMX patterns, animations, and optimizations
- Read TESTING.md for testing strategies
- Read COMMON_GOTCHAS.md for troubleshooting common issues
- Read COMPONENT_COMPOSITION.md for building complex UIs