A simple and powerful HTML generator for Gerbil Scheme.
Add this to your .gerbil/lib directory or install directly from source:
git clone https://github.com/luciusmagn/shsx
cd shsx
gxpkg install .SHSX uses a simple syntax where HTML elements are represented as S-expressions with colons:
(import :shsx/lib)
(define example
(shsx
(div: class: "container"
(h1: "Welcome to SHSX")
(p: "This is an example paragraph."))))
(displayln (render-html example))This generates:
<div class="container">
<h1>Welcome to SHSX</h1>
<p>This is an example paragraph.</p>
</div>You need to use unquoting to insert values into the template.
(shsx
(p: class: ,my-classes "Hello!"))If you are inserting from a variable with user data, make sure to sanitize the string:
(shsx
(blockquote: ,(sanitize user-input)))SHSX provides several control flow macros. Only the macro itself has to be unquoted, not the condition or the branches
Conditional rendering:
(define admin? #t)
(shsx
(div:
,(@if admin?
(p: "Admin panel")
(p: "Please log in"))))One-sided conditions:
(shsx
(div:
,(@when logged-in?
(p: "Welcome back!")
(button: "Logout"))
,(@unless admin?
(p: "Regular user area"))))Group multiple elements without creating a wrapper:
(shsx
(div:
,(@begin
(h1: "Title")
(p: "First paragraph")
(p: "Second paragraph"))))SHSX automatically handles self-closing tags like img:, br:, input::
(shsx
(div:
(img: src: "cat.jpg" alt: "A cute cat")
(br:)
(input: type: "text" placeholder: "Enter name")))You can unquote Scheme expressions inside SHSX:
(define name "Alice")
(define items '("One" "Two" "Three"))
(shsx
(div:
(h1: "Hello, " ,name "!")
(ul: class: ,(if (> (length items) 2) "big-list" "small-list")
(li: ,(car items))
(li: ,(cadr items)))))SHSX uses standard Scheme quasiquoting syntax to interpolate dynamic content:
Used to evaluate expressions, including control flow macros:
(define name "Alice")
(define admin? #t)
(shsx
(div:
(h1: "Hello, " ,name "!")
,(@when admin? ; Control macros use simple unquote
(button: "Delete")
(button: "Edit"))))Used to splice lists of elements directly into parent:
(define items '("One" "Two"))
(shsx
(ul:
,@(map (lambda (x)
(li: x))
items)))
;; Generates:
;; <ul>
;; <li>One</li>
;; <li>Two</li>
;; </ul>
;; Without ,@ would generate invalid:
;; <ul>((li: "One") (li: "Two"))</ul>You can test whether something is a SHSX template using the shsx-template? predicate:
(shsx-template? (shsx (div:))) => #t
(shsx-template? '(div:)) => #fAnd you can sanitize strings with sanitize
(test-case "String sanitization"
(check (equal? (sanitize "<script>") "<script>") => #t)
(check (equal? (sanitize "a & b") "a & b") => #t)
(check (equal? (sanitize "\"quote\" and 'apostrophe'")
""quote" and 'apostrophe'") => #t)
(check (string? (render-html sanitize-test)) => #t))Fair License
Copyright © 2025 Lukáš Hozda
Usage of the works is permitted provided that this instrument is retained with the works, so that any entity that uses the works is notified of this instrument.
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.