{"id":43258597,"url":"https://github.com/pthm/hxcmp","last_synced_at":"2026-02-01T14:02:21.342Z","repository":{"id":334101519,"uuid":"1139430247","full_name":"pthm/hxcmp","owner":"pthm","description":"Component system for Go using Templ and HTMX, strongly typed props, code-generated action dispatchers, and zero runtime reflection.","archived":false,"fork":false,"pushed_at":"2026-01-23T00:40:27.000Z","size":186,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-23T12:32:57.312Z","etag":null,"topics":["components","framework","golang","htmx","hypermedia","server-side-rendering","templ","templates"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pthm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-22T00:21:51.000Z","updated_at":"2026-01-23T00:40:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pthm/hxcmp","commit_stats":null,"previous_names":["pthm/hxcmp"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/pthm/hxcmp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pthm%2Fhxcmp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pthm%2Fhxcmp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pthm%2Fhxcmp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pthm%2Fhxcmp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pthm","download_url":"https://codeload.github.com/pthm/hxcmp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pthm%2Fhxcmp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28980159,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T13:38:33.235Z","status":"ssl_error","status_checked_at":"2026-02-01T13:38:32.912Z","response_time":56,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["components","framework","golang","htmx","hypermedia","server-side-rendering","templ","templates"],"created_at":"2026-02-01T14:02:16.830Z","updated_at":"2026-02-01T14:02:21.333Z","avatar_url":"https://github.com/pthm.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg width=\"434\" height=\"83\" alt=\"hxcmp\" src=\"https://github.com/user-attachments/assets/11a72728-2030-4977-ac59-c2976b9f13f4\" /\u003e\n\n# hxcmp\n\nA component system for Go that brings React-like composition to server-rendered applications using [Templ](https://templ.guide) templates and [HTMX](https://htmx.org). Components are strongly typed via generics with compile-time verified actions and zero reflection in the request path.\n\n```go\ntype Counter struct {\n    *hxcmp.Component[Props]\n}\n\ntype Props struct {\n    Count int `hx:\"count\"`\n}\n\nfunc New() *Counter {\n    c := \u0026Counter{Component: hxcmp.New[Props](\"counter\")}\n    c.Action(\"increment\", c.handleIncrement)\n    return c\n}\n\nfunc (c *Counter) Hydrate(ctx context.Context, props *Props) error { return nil }\n\nfunc (c *Counter) Render(ctx context.Context, props Props) templ.Component {\n    return counterTemplate(c, props)\n}\n\nfunc (c *Counter) handleIncrement(ctx context.Context, props Props) hxcmp.Result[Props] {\n    props.Count++\n    return hxcmp.OK(props)\n}\n```\n\nEach component is a self-contained unit with its own props, lifecycle methods, action handlers, and routes. The `hxcmp generate` tool produces type-safe dispatchers and serializers so the runtime never uses reflection.\n\n\u003e **Work in progress** -- contributions welcome.\n\n## Installation\n\n### Runtime library\n\n```bash\ngo get github.com/pthm/hxcmp\n```\n\n### CLI tool\n\n```bash\ngo install github.com/pthm/hxcmp/cmd/hxcmp@latest\n```\n\n### Client extension\n\nInclude the provided `hxcmp-ext.js` after HTMX in your layout. It handles event data injection and toast auto-dismiss.\n\n```html\n\u003cscript src=\"https://unpkg.com/htmx.org\"\u003e\u003c/script\u003e\n\u003cscript src=\"/static/hxcmp-ext.js\"\u003e\u003c/script\u003e\n```\n\n## Code Generation\n\n`hxcmp generate` parses your component source files and produces `*_hx.go` files containing fast prop encoders/decoders, Wire methods, and HTTP dispatch logic. Generation must run before `templ generate`:\n\n```bash\nhxcmp generate ./...   # produces *_hx.go files\ntempl generate ./...   # produces *_templ.go files (may reference generated actions)\ngo build ./...\n```\n\nOther commands:\n\n```bash\nhxcmp generate --dry-run ./...   # preview without writing\nhxcmp clean ./...                # remove generated files\n```\n\n## Quick Start\n\nMount the component system onto your mux and register components:\n\n```go\nfunc main() {\n    mux := http.NewServeMux()\n\n    // Mount creates a registry, sets it as default, and attaches the handler.\n    // In production pass hxcmp.WithKey(key) for stable prop signing across restarts.\n    reg := hxcmp.Mount(mux)\n\n    // Register components\n    reg.Add(\n        counter.New(),\n        todolist.New(store),\n    )\n\n    http.ListenAndServe(\":8080\", mux)\n}\n```\n\n## Core Concepts\n\n### Component Lifecycle\n\nEvery component embeds `*hxcmp.Component[P]` where `P` is a props struct, and implements two interfaces:\n\n- **`Hydrate(ctx, *P) error`** -- Runs before every request. Reconstructs rich objects (DB lookups, service calls) from the serialized prop IDs.\n- **`Render(ctx, P) templ.Component`** -- Produces the Templ output after hydration and handler execution.\n\n### Props\n\nProps are the component's serializable state. Scalar fields are encoded into signed URLs by default; complex fields marked `hx:\"-\"` are excluded and populated during hydration.\n\n```go\ntype Props struct {\n    ItemID   string `hx:\"id\"`\n    Page     int    `hx:\"page,omitempty\"`\n    Item     *Item  `hx:\"-\"` // hydrated, not serialized\n}\n```\n\nCall `.Sensitive()` on a component to encrypt props instead of signing them.\n\n### Actions\n\nActions register named handlers on a component. They default to POST; override with `.Method()`:\n\n```go\nc.Action(\"save\", c.handleSave)\nc.Action(\"delete\", c.handleDelete).Method(http.MethodDelete)\n```\n\nCode generation produces Wire methods (e.g. `c.WireSave(props)`, `c.WireDelete(props)`) that return `templ.Attributes` with the minimal HTMX attributes. All other HTMX attributes are written directly in templates:\n\n```html\n\u003c!-- In a templ template --\u003e\n\u003cbutton { c.WireSave(props)... } hx-target=\"#form\" hx-confirm=\"Save changes?\"\u003e\n    Save\n\u003c/button\u003e\n```\n\nHandler signatures are auto-detected -- use whichever you need:\n\n```go\nfunc (c *Comp) handle(ctx context.Context, props Props) Result[Props]\nfunc (c *Comp) handle(ctx context.Context, props Props, r *http.Request) Result[Props]\nfunc (c *Comp) handle(ctx context.Context, props Props, w http.ResponseWriter) Result[Props]\n```\n\n### Result\n\nHandlers return `Result[P]`, a fluent builder for the response:\n\n```go\nreturn hxcmp.OK(props)                                  // re-render with updated props\nreturn hxcmp.OK(props).Flash(\"success\", \"Saved!\")       // with toast notification\nreturn hxcmp.OK(props).Trigger(\"item:changed\")          // broadcast event\nreturn hxcmp.OK(props).PushURL(\"/items/42\")             // update browser URL\nreturn hxcmp.Err(props, err)                            // error response\nreturn hxcmp.Redirect[Props](\"/dashboard\")              // client redirect\nreturn hxcmp.Skip[Props]()                              // handler wrote its own response\n```\n\n### Wire Methods\n\nGenerated Wire methods return minimal `templ.Attributes` containing only the HTTP\nmethod attribute and encoded props. All other HTMX attributes are written directly\nin templates:\n\n```html\n\u003cbutton\n    { c.WireIncrement(props)... }\n    hx-target=\"#counter\"\n    hx-swap=\"outerHTML\"\n\u003e+\u003c/button\u003e\n```\n\nThis keeps templates HTMX-native — you write standard HTMX attributes for targeting,\nswapping, triggers, confirms, etc.\n\n### Component Communication\n\nComponents communicate through events, not direct references:\n\n```go\n// Sender: broadcast after mutation\nreturn hxcmp.OK(props).Trigger(\"todo:changed\")\n```\n\n```html\n\u003c!-- Receiver: re-render when event fires --\u003e\n\u003cdiv { c.WireRender(props)... }\n     hx-target=\"#stats\"\n     hx-swap=\"outerHTML\"\n     hx-trigger=\"todo:changed from:body\"\u003e\n\u003c/div\u003e\n```\n\n### Lazy Loading\n\nDefer rendering until the element enters the viewport:\n\n```go\nc.Lazy(props, placeholder)  // loads on intersection\nc.Defer(props, placeholder) // loads immediately after page\n```\n\n### Flash Messages\n\nToast notifications rendered via HTMX out-of-band swaps:\n\n```go\nreturn hxcmp.OK(props).Flash(hxcmp.FlashSuccess, \"Item saved!\")\n```\n\nAdd `hxcmp.ToastContainer()` to your layout to receive them.\n\n## Security\n\n- **Prop integrity**: Props are HMAC-signed by default. Use `.Sensitive()` for AES encryption.\n- **CSRF protection**: Mutating actions require the `HX-Request: true` header (sent automatically by HTMX).\n- **No direct prop access**: Users cannot forge or tamper with component state.\n\n## Testing\n\nThe `hxcmp` package provides testing utilities for unit-testing components without a running server:\n\n```go\n// Test rendering\nresult, err := hxcmp.TestRender(comp, props)\nresult.HTMLContains(\"expected text\")\n\n// Test action handlers\nresult, err := hxcmp.TestAction(comp, actionURL, \"POST\", formData)\nresult.IsOK()\nresult.HasFlash(\"success\", \"Saved!\")\nresult.HasEvent(\"item:changed\")\nresult.WasRedirected()\n```\n\nUse `MockHydrater` to inject test data without real dependencies.\n\n## Examples\n\nA complete working example with multiple interacting components is available in the [`examples/todo`](./examples/todo) directory of this repository.\n\n## Dependencies\n\n- [templ](https://github.com/a-h/templ) -- Go HTML templating\n- [msgpack](https://github.com/vmihailenco/msgpack) -- Efficient prop serialization\n\n## License\n\nSee [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpthm%2Fhxcmp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpthm%2Fhxcmp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpthm%2Fhxcmp/lists"}