{"id":13632180,"url":"https://github.com/reggi/htmx-components","last_synced_at":"2025-04-15T19:32:05.146Z","repository":{"id":63050409,"uuid":"564681558","full_name":"reggi/htmx-components","owner":"reggi","description":"🧩 Async HTMX + JSX","archived":false,"fork":false,"pushed_at":"2023-05-05T17:49:26.000Z","size":5771,"stargazers_count":84,"open_issues_count":11,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-29T00:11:12.181Z","etag":null,"topics":["deno","deno-deploy","htmx"],"latest_commit_sha":null,"homepage":"https://reggi-htmx-components.deno.dev","language":"TypeScript","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/reggi.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}},"created_at":"2022-11-11T08:46:50.000Z","updated_at":"2025-03-02T12:35:34.000Z","dependencies_parsed_at":"2024-01-22T01:11:57.302Z","dependency_job_id":"10f9bc93-bde1-475b-8426-9ad5552a23bb","html_url":"https://github.com/reggi/htmx-components","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reggi%2Fhtmx-components","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reggi%2Fhtmx-components/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reggi%2Fhtmx-components/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/reggi%2Fhtmx-components/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/reggi","download_url":"https://codeload.github.com/reggi/htmx-components/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249138482,"owners_count":21218894,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["deno","deno-deploy","htmx"],"created_at":"2024-08-01T22:02:54.947Z","updated_at":"2025-04-15T19:32:05.115Z","avatar_url":"https://github.com/reggi.png","language":"TypeScript","funding_links":[],"categories":["TypeScript","deno"],"sub_categories":[],"readme":"# HTMX Components\n\nI think of it as:\n1. Async Server Components \n2. HTMX in JSX (Fully typed JSX for all HTMX attributes without the `hx` prefix.)\n3. Dynamic routing / nesting / serving (no files / folders)\n\nTo run all the examples below in one server run:\n\n```bash\ndeno task all-examples\n```\n\n## \"Click To Edit\" Example\n\nThis example acts as the \"rosetta stone\" it's a near 1:1 with the first example in the HTMX docs:\n\n* Start the server `deno task click-to-edit`\n* Original HTMX code found here: https://htmx.org/examples/click-to-edit/\n* Open the code on github https://github.com/reggi/htmx-components/blob/main/examples/1.click-to-edit.tsx\n* Open the file locally `code ./examples/1.click-to-edit.tsx`\n* Navigate to http://localhost:8000/contacts/1\n* Navigate to http://localhost:8000/contacts/1/edit\n* Web https://reggi-htmx-components.deno.dev/contacts/1\n* Web https://reggi-htmx-components.deno.dev/contacts/1/edit\n\n## \"Bulk Update\" Example\n\n* Start the server `deno task bulk-update`\n* Original HTMX code found here: https://htmx.org/examples/bulk-update/\n* Open the code on github https://github.com/reggi/htmx-components/blob/main/examples/2.bulk-update.tsx\n* Open the file locally `code ./examples/2.bulk-update.tsx`\n* Navigate to http://localhost:8000/people\n* Web https://reggi-htmx-components.deno.dev/people\n\n## \"Click To Load\" Example\n\n* Start the server `deno task click-to-load`\n* Original HTMX code found here: https://htmx.org/examples/click-to-load/\n* Open the code on github https://github.com/reggi/htmx-components/blob/main/examples/3.click-to-load.tsx\n* Open the file locally `code ./examples/3.click-to-load.tsx`\n* Navigate to http://localhost:8000/click-to-load\n* Web https://reggi-htmx-components.deno.dev/click-to-load\n\n## Philosophy / Anatomy / Ergonomics\n\nThis is an HTMX component:\n\n```tsx\nconst Contact = component('/contacts/:identifier', async ({ identifier }: { identifier: string }, ctx) =\u003e {\n  const { firstName, lastName, email } = await getOrUpdate({ identifier }, ctx.request)\n  return (\n    \u003cHTMX.div targetThis swapOuter\u003e\n        \u003cdiv\u003e\u003clabel\u003eFirst Name\u003c/label\u003e: {firstName} \u003c/div\u003e\n        \u003cdiv\u003e\u003clabel\u003eLast Name\u003c/label\u003e: {lastName} \u003c/div\u003e\n        \u003cdiv\u003e\u003clabel\u003eEmail\u003c/label\u003e: {email} \u003c/div\u003e\n        \u003cEdit.button.get identifier={identifier} pushUrl class=\"btn btn-primary\"\u003e\n        Click To Edit\n        \u003c/Edit.button.get\u003e\n    \u003c/HTMX.div\u003e\n  )\n})\n```\n\nIt's a wrapped component that couples the `URLPattern` route with the markup. This is an essential philosphy of HTMX Components, and complements HTMX well, why? Because HTMX is HTML over the wire, in HTMX the endpoints themselves are \"components\". Deeply coupling the markup with the route is a no-brainer, this also provides a number of benifits.\n\nDid you know that the JavaScript `string` (like the primative) object has a [method called `link`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/link), like... (`'hello\".link()`) which returns a DOM element 🤯. Obviously this is a odd feature for a programming language but not _the_ programmming language of the web. It's long deprecated now but it gives us a glimpse into the fossil record of what was going through the early developers of the webs minds. While I have a love / hate relationship with this `link` method, I'm paying homage to it in this library. Every HTMX Component returns a normal JSX element totally as you'd expect except with two exceptions, they can run async code (like next.js / react 18), they also return extra \"function\" properties that return other JSX elements. That allows you to target them in specific typesafe ways. For instance you can do `Contact.href`, now this may not be great if there's a param in the url, but for that there's `Contact.getPath({ typesafety! })`, there's also nested JSX elements like `\u003cContact.anchor.href/\u003e` and `\u003cContact.form.put/\u003e`, these allow you to target the route of a component in a typesafe way.\n\n---\n\n\u003cdetails\u003e\n\u003csummary\u003eDefault Example (more chaotic example)\u003c/summary\u003e\n\n* `deno task start`\n* http://localhost:8000/nest/bob\n* http://localhost:8000/nest/alice/matt\n* http://localhost:8000/registry/@reggi/alicebob\n\n# Default Example:\n\n![](./screenshots/J9x_9P1Y.jpg)\n![](./screenshots/DR2PrQJK.png)\nw\n```tsx\nimport { HTMX, HTMXComponents, serve, Fragment } from \"./mod.tsx\"\n\n// http://localhost:8000/registry/@reggi/alicebob\nconst { component, routes, context } = new HTMXComponents('@reggi/alicebob')\n\nconst Alice = component('/alice/:name', async ({ name }: { name: string}, ctx) =\u003e {\n  const _name = await Promise.resolve(name)\n  const req = new URL(ctx.request.url)\n  const query = req.searchParams.get('meow')\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003eThis is {_name} + {ctx.data.love} {ctx.id} {query}\u003c/div\u003e\n    \u003c/div\u003e\n  )\n})\n\nconst Bob = component('/bob', async (_p, ctx) =\u003e {\n  const name = await Promise.resolve('bob')\n  return (\n    \u003cFragment\u003e\n      \u003cHTMX.button get={Alice.getPath(ctx, { name: 'alice' }, { meow: 'meow' })}\u003eDifferent\u003c/HTMX.button\u003e\n      \u003cAlice.button.get name={'kettle'} query={{meow: true}}\u003eAlice Button\u003c/Alice.button.get\u003e\n      \u003cAlice.anchor.href name={'kettle'} useQuery\u003eAlice Link\u003c/Alice.anchor.href\u003e\n      \u003cAlice.iframe.src name={'kettle'} query={{meow: true}} useQuery\u003eAlice Link\u003c/Alice.iframe.src\u003e\n      \u003cdiv\u003eThis is {name} {ctx.id}\u003c/div\u003e\n      \u003cAlice name=\"alice\"/\u003e\n    \u003c/Fragment\u003e\n  )\n})\n\nconst e = context({\n  nestPath: '/nest',\n  love: 'lauriel'\n})\n\nawait serve(e)\n\n// or \n// export default routes // like express routes\n```\n\n\u003c/details\u003e\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freggi%2Fhtmx-components","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Freggi%2Fhtmx-components","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Freggi%2Fhtmx-components/lists"}