UI
Server-driven UI components and real-time SSE updates.
(import (sigil web ui)
(sigil web)
(sigil http))The (sigil web ui) module provides server-side rendering of interactive components. The server is the source of truth; the client is a thin rendering layer powered by a small JavaScript library.
Setup
Include the client library in your HTML head and add a route to serve it:
(define (layout title body)
(sxml->html
`(html
(head
,@(sigil-web-ui-head)
(title ,title))
(body ,@body))))
(define app
(router
(route GET "/js/sigil-web-ui.js" (sigil-web-ui-handler))
...))sigil-web-ui-head returns script tags for the idiomorph library and the Sigil UI script. Customize URLs with idiomorph-url: and script-url: keywords.
SSE Updates
Server-Sent Events allow pushing real-time updates from the server. Each helper produces an SSE-formatted string to send via chunked response.
sse-morph
Morph HTML content into a target element.
(sse-morph target: "#messages" mode: "append"
content: `(div (@ (class "msg")) "New message!"))| Mode | Description |
|---|---|
"morph" | Intelligent diff/patch via idiomorph (default) |
"replace" | Replace target's outerHTML |
"inner" | Replace target's innerHTML |
"append" | Append to target's children |
"prepend" | Prepend to target's children |
"before" | Insert before target |
"after" | Insert after target |
The settle: keyword adds a delay in milliseconds after morphing, useful for CSS transitions.
sse-remove
Remove an element from the DOM.
(sse-remove target: "#notification")sse-class
Add or remove CSS classes on a target element.
(sse-class target: "#panel" add: "visible active")
(sse-class target: "#btn" remove: "loading" add: "done")sse-eval
Execute a limited client command (focus, scroll).
(sse-eval cmd: "focus" target: "#input")
(sse-eval cmd: "scroll-to" target: "#chat" position: "bottom")sse-redirect
Redirect the browser to a new URL.
(sse-redirect url: "/login")Response Helpers
sigil-ui-response
Create an HTML response with merge headers for single-target updates.
(sigil-ui-response target: "#results" mode: "inner"
content: `(ul ,@(map render-item items)))sigil-ui-redirect
Trigger a client-side redirect (falls back to HTTP 302 for non-JS clients).
(sigil-ui-redirect url: "/dashboard")sse-response-batch
Send multiple SSE events in one response for multi-target updates.
(sse-response-batch
(sse-morph target: "#sidebar" content: new-sidebar)
(sse-morph target: "#main" content: new-content)
(sse-eval cmd: "focus" target: "#search"))Form Fields
Form field helpers generate SXML with labels, error display, and standard HTML attributes.
| Helper | HTML type |
|---|---|
sg-text-field | <input type="text"> |
sg-email-field | <input type="email"> |
sg-password-field | <input type="password"> |
sg-hidden-field | <input type="hidden"> |
sg-textarea-field | <textarea> |
sg-select-field | <select> |
sg-checkbox-field | <input type="checkbox"> |
sg-submit-button | <button type="submit"> |
Common keywords shared by most fields:
| Keyword | Description |
|---|---|
name: | Input name attribute |
value: | Current value |
label: | Label text (wraps in <label>) |
placeholder: | Placeholder text |
required: | Add required attribute |
disabled: | Add disabled attribute |
error: | Error message (shown in <span>) |
class: | CSS class |
id: | Element ID |
(sg-text-field name: "username" label: "Username"
placeholder: "Enter name" required: #t)
(sg-select-field name: "role" label: "Role"
options: '(("admin" . "Admin")
("user" . "User"))
value: "user")
(sg-submit-button label: "Save" loading: "opacity-50")Interactive Components
sg-button
Create a button that triggers a server action via data-sg-* attributes.
(sg-button "Like" action: "/api/like" method: "post"
target: "#count" loading: "opacity-50")sg-link
Create a link that morphs content into a target (SPA-style navigation).
(sg-link "Introduction" action: "/lesson/1" target: "#content")sg-form
Create a form with action handling and optional error targeting.
(sg-form (list
(sg-email-field name: "email" label: "Email")
(sg-password-field name: "password" label: "Password")
(sg-submit-button label: "Login"))
action: "/api/login" method: "post"
target: "#result" error-target: "#errors")sg-sse
Create an SSE-connected container that receives real-time updates.
(sg-sse '((div (@ (id "messages"))))
url: "/events/chat" id: "chat-container")Higher-Level Components
Modal Dialogs
;; Define a modal
(sg-modal (list
(p "Are you sure you want to delete this item?")
(sg-button "Delete" action: "/api/delete/42" method: "delete")
(sg-modal-close "Cancel"))
id: "confirm-modal" title: "Confirm Delete")
;; Trigger button
(sg-modal-trigger "Delete Item" target: "#confirm-modal")Data Tables
Render tabular data with optional row actions. Column field values are extracted from dicts or alists. Action URLs support {field} placeholders interpolated from row data.
(sg-data-table
columns: '((name "Name") (email "Email"))
rows: users
row-actions: (list
(sg-table-action "Edit" action: "/users/{id}/edit"
method: "get")
(sg-table-action "Delete" action: "/users/{id}"
method: "delete"
confirm: "Delete this user?"))
empty-message: "No users found.")Paginator
Render pagination navigation with prev/next links and page numbers. Uses sg-link internally for SSE morphing.
(sg-paginator current-page: 3 total-pages: 10
base-url: "/users" target: "#user-list"
params: '((sort . "name")))Keywords: current-page:, total-pages:, base-url:, target:, params: (extra query params), window-size: (pages shown around current, default 2).
Flash Messages
Display temporary notifications with auto-removal.
;; Create a flash message element
(flash-message type: 'success message: "Record saved!"
remove-after: 3000)
;; Send via SSE (appends to #sg-flash-container)
(sse-flash type: 'error message: "Validation failed")Types: 'info, 'success, 'error, 'warning. Each gets a CSS class like sg-flash-success.
Loading Indicator
(sg-loading-indicator id: "spinner")
(sg-loading-indicator id: "spinner" active: #t)Common Patterns
Form with Validation Errors
(define (login-form . errors)
(sg-form (list
(sg-email-field name: "email" label: "Email"
error: (assoc-ref 'email errors #f))
(sg-password-field name: "password" label: "Password"
error: (assoc-ref 'password errors #f))
(sg-submit-button label: "Sign In"))
action: "/login" method: "post"
target: "#login-form"))
(define (login-handler request)
(let ((errors (validate-login request)))
(if errors
(sigil-ui-response target: "#login-form" mode: "morph"
content: (login-form errors))
(sigil-ui-redirect url: "/dashboard"))))Real-Time Updates
;; Page with SSE connection
(define (chat-page request)
(http-response/html 200
(layout "Chat"
(list
(sg-sse '((div (@ (id "messages"))))
url: "/events/chat")
'(div (@ (id "sg-flash-container")))))))
;; SSE handler pushes updates
(define (chat-event-handler request)
(sse-response-batch
(sse-morph target: "#messages" mode: "append"
content: `(div (@ (class "msg")) ,new-message))
(sse-eval cmd: "scroll-to" target: "#messages"
position: "bottom")))