{"id":33236992,"url":"https://github.com/skyizwhite/hsx","last_synced_at":"2026-04-07T23:03:29.830Z","repository":{"id":221578848,"uuid":"752189456","full_name":"skyizwhite/hsx","owner":"skyizwhite","description":"HTML S-expression","archived":false,"fork":false,"pushed_at":"2026-01-02T00:40:08.000Z","size":184,"stargazers_count":37,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-02-06T01:32:29.489Z","etag":null,"topics":["common-lisp","html","web"],"latest_commit_sha":null,"homepage":"","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/skyizwhite.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2024-02-03T09:46:11.000Z","updated_at":"2026-01-30T05:00:34.000Z","dependencies_parsed_at":"2024-05-28T10:02:34.759Z","dependency_job_id":"e11d0b0f-d3f0-4419-80a8-601ebb10e21c","html_url":"https://github.com/skyizwhite/hsx","commit_stats":null,"previous_names":["skyizwhite/piccolo","skyizwhite/hsx"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/skyizwhite/hsx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyizwhite%2Fhsx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyizwhite%2Fhsx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyizwhite%2Fhsx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyizwhite%2Fhsx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skyizwhite","download_url":"https://codeload.github.com/skyizwhite/hsx/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skyizwhite%2Fhsx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31532342,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T16:28:08.000Z","status":"ssl_error","status_checked_at":"2026-04-07T16:28:06.951Z","response_time":105,"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":["common-lisp","html","web"],"created_at":"2025-11-16T19:00:28.311Z","updated_at":"2026-04-07T23:03:29.825Z","avatar_url":"https://github.com/skyizwhite.png","language":"Common Lisp","readme":"# HSX – HTML S-expression\n\nHSX is a declarative, component-oriented HTML DSL for Common Lisp.\nIt lets you describe HTML structures and reusable components directly in Lisp, safely render them to HTML strings, and seamlessly integrate with your web applications.\n\n→ [Example Project](https://github.com/skyizwhite/website)\n\n---\n\n## How It Works\n\nHSX translates Lisp S-expressions into HTML by expanding them into calls to `create-element`.\n\nEach tag or component inside an `(hsx ...)` form becomes:\n\n```lisp\n(create-element type props children)\n```\n\nFor example:\n\n```lisp\n(hsx\n  (article :class \"container\"\n    (h1 \"Title\")\n    (p \"Paragraph\")\n    (~share-button :service :x)))\n```\n\nExpands into:\n\n```lisp\n(create-element :article\n                (list :class \"container\")\n                (list (create-element :h1 nil (list \"Title\"))\n                      (create-element :p nil (list \"Paragraph\"))\n                      (create-element #'~share-button\n                                      (list :service :x)\n                                      nil)))\n```\n\n---\n\n## Quick Example\n\n```lisp\n(hsx\n  (div :id \"main\" :class \"container\"\n    (h1 \"Hello, HSX!\")\n    (p \"This is a simple paragraph.\")))\n```\n\n↓\n\n```html\n\u003cdiv id=\"main\" class=\"container\"\u003e\n  \u003ch1\u003eHello, HSX!\u003c/h1\u003e\n  \u003cp\u003eThis is a simple paragraph.\u003c/p\u003e\n\u003c/div\u003e\n```\n\n---\n\n## Basic Usage\n\n### Step 1: Create a Component\n\nComponents are defined using `defcomp`.\nThey are simple Lisp functions that return HSX elements.\n\nComponent names must start with `~` and props should be declared with `\u0026key` and/or `\u0026rest`.\nThe special `children` key automatically receives any nested elements.\n\n```lisp\n(defcomp ~button (\u0026key href class children)\n  (hsx\n    (a :href href :class (clsx \"btn\" class)\n      children)))\n```\n\n### Step 2: Combine Components\n\nHSX allows composition of components just like JSX.\n\n```lisp\n(defcomp ~card (\u0026key title children)\n  (hsx\n    (div :class \"card\"\n      (h2 title)\n      (div :class \"content\"\n        children))))\n\n(defparameter *view*\n  (hsx\n    (div :class \"container\"\n      (~card :title \"Hello\"\n        (~button :href \"/start\" :class \"primary\"\n          \"Get Started\"))\n      (~card :title \"Docs\"\n        (p \"Read the documentation to learn more.\")))))\n```\n\n### Step 3: Render to HTML\n\nUse `render-to-string` to produce a full HTML string.\nPass `:pretty t` for indented, human-readable output.\n\n```lisp\n(render-to-string *view* :pretty t)\n```\n\nOutput:\n\n```html\n\u003cdiv class=\"container\"\u003e\n  \u003cdiv class=\"card\"\u003e\n    \u003ch2\u003eHello\u003c/h2\u003e\n    \u003cdiv class=\"content\"\u003e\n      \u003ca href=\"/start\" class=\"btn primary\"\u003eGet Started\u003c/a\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cdiv class=\"card\"\u003e\n    \u003ch2\u003eDocs\u003c/h2\u003e\n    \u003cdiv class=\"content\"\u003e\n      \u003cp\u003eRead the documentation to learn more.\u003c/p\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n\u003c/div\u003e\n```\n\n---\n\n## Fragments\n\n### `\u003c\u003e` — Fragment\n\nCombine multiple elements without creating an extra parent tag.\n\n```lisp\n(hsx\n  (\u003c\u003e\n    (li \"One\")\n    (li \"Two\")))\n```\n\n↓\n\n```html\n\u003cli\u003eOne\u003c/li\u003e\n\u003cli\u003eTwo\u003c/li\u003e\n```\n\nFragments are useful when returning multiple sibling elements from a component.\n\n### `raw!` — Raw Fragment\n\nHSX automatically escapes unsafe characters in text and attribute values to prevent injection attacks.\nIf you need to insert raw, unescaped HTML, you can do so — but use it only with trusted content, as it disables automatic escaping and may expose security risks.\n\n```lisp\n(hsx\n  (script (raw! \"alert('unsafe if user-generated!')\")))\n```\n\n---\n\n## Expressions and Logic\n\nYou can embed any Lisp expression directly inside an HSX form.\nSince HSX is just Lisp syntax, you can use if, when, loop, or any other macro to build dynamic content.\n\n### Conditional Rendering\n\n```lisp\n(hsx\n  (div\n    (if (\u003e (random 10) 5)\n        (hsx (p \"High!\"))\n        (hsx (p \"Low!\")))))\n```\n\n### Loop Rendering\n\n```lisp\n(hsx\n  (ul\n    (loop :for item :in items :collect\n      (hsx (li item)))))\n```\n\n### Dynamic Props\n\nHSX supports both inline plist props and dynamic plist props.\n\n```lisp\n(let ((props '(:class \"btn\" :href \"/\")))\n  (hsx (a props \"Dynamic Link\")))\n```\n\n---\n\n## Utilities\n\n### `register-web-components`\n\nMakes Web Components usable in HSX.\n\n```lisp\n(register-web-components\n custom1 custom2)\n\n(hsx\n  (custom1 :prop \"val\"\n    (custom2)))\n```\n\n↓\n\n```html\n\u003ccustom1 prop=\"val\"\u003e\n  \u003ccustom2\u003e\u003c/custom2\u003e\n\u003c/custom1\u003e\n```\n\n### `clear-web-components`\n\nClears all registered Web Components.\n\n### `clsx`\n\nBuilds class strings conditionally.\nRemoves `nil` and joins the remaining strings with spaces.\n\n```lisp\n(clsx \"btn\" nil \"primary\")\n;; =\u003e \"btn primary\"\n```\n---\n\n## License\n\nMIT License\n\n© 2024 Akira Tempaku\n\n© 2018 Bo Yao (original [flute](https://github.com/ailisp/flute) project)\n \n","funding_links":[],"categories":["Interfaces to other package managers"],"sub_categories":["Isomorphic web frameworks"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskyizwhite%2Fhsx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskyizwhite%2Fhsx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskyizwhite%2Fhsx/lists"}