sigildocs

(sigil web ui)

(sigil web ui) - Server-Driven Hypermedia Library

Enables dynamic, real-time web interfaces driven entirely from Sigil. The server is the source of truth; the client is a thin rendering layer.

Core concepts:

  • Server-driven UI updates (ui-update, ui-remove, ui-eval, ui-redirect, ...): one vocabulary for both live broadcasts and per-request responses
  • ui-response delivers a batch of those updates to the acting request
  • Declarative HTML attributes (data-sg-*) for client interactions

Example:

(import (sigil web ui)
        (sigil http))

;; Broadcast a UI update to every connected client
(broadcast-send hub (ui-update target: "#chat" mode: "append"
                               content: `(div (@ (class "msg")) "Hello!")))

;; Respond to just the acting request with the same vocabulary
(ui-response (ui-update target: "#results" mode: "inner"
                        content: (map render-item items)))

Exports

Render a list of SXML nodes to a single HTML string.

Saves you from hand-rolling (apply string-append (map sxml->xml xs)) whenever you have a collection of nodes (e.g. a list of rendered rows) that needs to become one fragment.

Example:

(sxml-list->html (map render-row items))
ui-updateprocedure

Update a target element with HTML content.

The workhorse UI update: it replaces, morphs, or inserts content at a target element. content: accepts a string, a single SXML node, or a list of SXML nodes (joined automatically). mode: selects how the content lands; the default "morph" diffs the target in place via idiomorph (preserving focus/scroll).

Parameters: target: CSS selector for the target element (e.g., "#chat") content: string, SXML node, or list of SXML nodes mode: how the content lands (default: "morph") settle: Milliseconds to wait after applying (for CSS transitions)

Modes: "morph" - Morph the target ELEMENT itself via idiomorph, in place (default). The content is a full element whose id matches the target — the common case. "morph-inner" - Morph the target's INNER content via idiomorph (the content is the target's new children). "replace" - Replace target's outerHTML (no diffing) "inner" - Replace target's innerHTML (no diffing) "append" - Append to target's children "prepend" - Prepend to target's children "before" - Insert before target "after" - Insert after target "remove" - Remove target element (no content needed)

Example:

(ui-update target: "#messages" mode: "append"
           content: `(div (@ (class "msg")) "Hello!"))
ui-removeprocedure

Format a UI remove update.

Removes the target element from the DOM. Shorthand for (ui-update target: TARGET mode: "remove").

Example:

(ui-remove target: "#notification")
ui-classprocedure

Format a UI class update.

Adds or removes CSS classes on the target element.

Parameters: target: CSS selector for the target element add: Space-separated class names to add remove: Space-separated class names to remove

Example:

(ui-class target: "#panel" add: "visible active")
(ui-class target: "#btn" remove: "loading" add: "done")
ui-evalprocedure

Format a UI eval update.

Executes a limited command on the client. Only use for things that can't be expressed as HTML state (focus, scroll).

Commands: "focus" - Focus the target element "scroll-to" - Scroll target into view (or to bottom with position: "bottom")

Example:

(ui-eval cmd: "focus" target: "#input")
(ui-eval cmd: "scroll-to" target: "#chat" position: "bottom")
ui-redirectprocedure

Format a UI redirect update.

Redirects the browser to a new URL.

Example:

(ui-redirect url: "/login")
ui-reloadprocedure

Format a UI reload update.

Tells the browser to re-fetch the current page and morph the result into the DOM using Idiomorph. Preserves scroll position, form state, and focus. Useful for live-coding: redefine a view function, then send a reload to see the update immediately.

Parameters: target: CSS selector for the element to reload (default: "body")

Example:

(ui-reload)                      ; reload full body
(ui-reload target: "#sidebar")   ; reload specific element
ui-css-reloadprocedure

Format a UI css-reload update.

Reloads CSS stylesheets in the browser without a full page reload. If href: is provided, only stylesheets matching that substring are reloaded. Otherwise all stylesheets are reloaded.

Example:

(ui-css-reload)                        ; reload all stylesheets
(ui-css-reload href: "styles.css")     ; reload specific stylesheet
ui-jsprocedure

Format a UI js update.

Executes JavaScript code in all connected browsers.

Example:

(ui-js code: "console.log('hello')")

Build Sigil-UI headers for merge operations.

Returns a dict of headers to include in an HTTP response.

Example:

(http-response
  status: 200
  headers: (dict-merge (sigil-ui-headers target: "#results" mode: "append")
                       (dict content-type: "text/html"))
  body: "<div>New item</div>")

DEPRECATED. Prefer ui-response with a ui-update: (ui-response (ui-update target: "#chat" mode: "append" content: ...))

Create a single-fragment HTML response carried via Sigil-UI-Merge-* headers. This is the pre-unification actor-response mechanism, kept as a shim so existing consumers keep working; new code should return ui-response batches (the same ui-* vocabulary you broadcast).

Example:

(sigil-ui-response target: "#chat" mode: "append"
                   content: `(div (@ (class "msg")) "Hello!"))

Create a redirect response using Sigil-UI header.

For JavaScript-enhanced clients, this triggers a client-side redirect. For non-JS clients, falls back to standard HTTP redirect.

Example:

(sigil-ui-redirect url: "/dashboard")
ui-responseprocedure

Respond to the acting request with a batch of UI updates.

This is THE response an action handler returns: any number of ui-* updates, applied by the client on arrival. It's the same vocabulary you broadcast to every client, so a handler reads as "the mutation is what everyone sees; this response is what the acting user sees."

Call with no updates to return an empty response — the common case when the visible change already went out over a broadcast and the actor needs nothing extra.

The optional status: keyword sets the HTTP status (default 200), purely for transport hygiene (logs, proxies, API clients). The status does not drive the UI: the client applies the updates regardless. Surface an error to the user like any other update, by targeting an error element.

Example:

(ui-response)                                   ; broadcast handled it
(ui-response (ui-update target: "#add-form" content: (add-form)))
(ui-response status: 422
  (ui-update target: "#errors" content: (errors msgs)))
ui-routesprocedure

Combine hypermedia route handlers, defaulting their responses.

Like routes (first non-#f handler wins), but a handler that returns a value which isn't an HTTP response is taken to mean "handled, nothing extra for the acting user" and becomes an empty (ui-response). So an action handler can just end with its live-collection mutation:

(define (toggle-handler req)
  (live-update! todos (item-id req)
    (lambda (i) (dict-set i done?: (not (dict-ref i done?:))))))

Gather your hypermedia routes under ui-routes and keep other kinds (a JSON API, a webhook) under plain routes/router, which never coerce. #f still falls through, and explicit responses pass through untouched, so ui-routes composes inside routes just like router:

(routes
  (ui-routes (router …ui…) (live-routes todos))   ; forgiving
  (router …api…))                                  ; untouched

Serialize a full SXML page to an HTML-document response.

The low-level escape hatch: you bring the whole page (including <head>), and this prepends the <!DOCTYPE html>, serializes with sxml->html, and sets Content-Type: text/html. Use http-response/page for the common case where you only have a title and body.

Example:

(http-response/sxml
  `(html (head (title "Hi")) (body (h1 "Hi"))))

The default page template used by http-response/page.

A minimal HTML shell: charset + viewport meta, a <title> from title, the Sigil Web UI client scripts (so data-sg-* actions and live updates work with zero head plumbing), and body spliced into <body>. body may be a single SXML node or a list of nodes.

Write your own (title body) -> full-page-sxml function and pass it as template: to http-response/page to customize the shell.

Respond with a full HTML page from a title and body.

The streamlined common case. Wraps body in template (default: default-page-template, a working script-wired shell) and returns an HTML-document response. A beginner writes (http-response/page "To-Do" body) and gets a complete page with head, viewport, and the UI client wired up. Override the shell with template: (a (title body) -> sxml function), or drop to http-response/sxml to supply the whole page.

body may be a single SXML node or a list of nodes.

Example:

(http-response/page "To-Do" (todo-page items))
(http-response/page "Not found" '(p "No such item.") status: 404)

Build an SXML attribute list, filtering out #f values.

Takes alternating name/value pairs and returns a list of (name value) entries suitable for splicing into SXML (@...) forms. Pairs where the value is #f are omitted.

Example:

`(div (@ (class "main")
        ,@(optional-attrs
            'id my-id
            'data-target target
            'disabled (and disabled "disabled"))))
sg-buttonprocedure

Create a button that triggers an action.

method: accepts an HTTP-method symbol (POST, GET, ...) and defaults to POST (actions are almost always POST); a "post" string still works.

Example:

(sg-button "Like" action: "/api/like"          ; POST by default
           loading: "opacity-50")
sg-formprocedure

Create a form with action handling.

method: accepts an HTTP-method symbol (POST, GET, ...) and defaults to POST; a "post" string still works.

Example:

(sg-form (list
           (sg-email-field name: "email")
           (sg-password-field name: "password")
           (sg-submit-button "Login"))
         action: "/api/login"                 ; POST by default
         target: "#result" error-target: "#errors")
sg-sseprocedure

Create an SSE-connected container.

Example:

(sg-sse '((div (@ (id "messages")))) url: "/events/chat" id: "chat")

Create an input field of any type.

Handles label wrapping, error display, and value coercion. Non-string values are auto-coerced: numbers become strings, #f omits the value attribute.

Example:

(sg-input-field type: "text" name: "title" label: "Title"
                value: some-value required: #t)
sg-text-fieldprocedure

Create a text input field.

Create an email input field.

Create a password input field.

Create a hidden input field.

Create a textarea field.

Value is auto-coerced: numbers become strings, #f becomes "".

Create a select dropdown field.

Options can be:

  • List of strings: ("a" "b" "c")
  • List of (value . label) pairs: (("a" . "Option A") ("b" . "Option B"))

Value is auto-coerced for comparison: numbers become strings.

Create a checkbox field.

Create a submit button.

Create a flash message notification.

Returns a div with role="alert" for accessibility. The type: controls the CSS class (sg-flash-success, sg-flash-error, etc.). When remove-after: is set, the element auto-removes after that many milliseconds.

Example:

(sg-flash-message type: 'success message: "Saved!" remove-after: 3000)
(sg-flash-message type: 'error message: "Failed to save")
ui-flashprocedure

Push a flash message as a UI update.

Wraps ui-morph with mode "append" to add a flash message to a container element. Defaults to targeting "#sg-flash-container".

Example:

(ui-flash type: 'success message: "Record updated" remove-after: 3000)

Create a loading indicator element.

Returns a div with class "sg-loading". When active: is #t, adds the "active" class. Uses aria-hidden for accessibility.

Example:

(sg-loading-indicator id: "spinner")
(sg-loading-indicator id: "spinner" active: #t)
sg-modalprocedure

Create a modal dialog using the HTML <dialog> element.

Example:

(sg-modal (list (p "Are you sure?")
                (sg-modal-close "Cancel"))
          id: "confirm" title: "Confirm")

Create a button that opens a modal dialog.

Example:

(sg-modal-trigger "Open Settings" target: "#settings-modal")

Create a button that closes the nearest modal dialog.

Example:

(sg-modal-close "Cancel")

Create a table action descriptor for use with sg-data-table.

Example:

(sg-table-action "Edit" action: "/users/{id}/edit")
(sg-table-action "Delete" action: "/users/{id}"
                 method: "delete" confirm: "Delete this user?")
sg-data-tableprocedure

Create a data table with headers, rows, and optional actions.

Example:

(sg-data-table columns: '((name "Name") (email "Email"))
               rows: users
               row-actions: (list
                 (sg-table-action "Edit" action: "/users/{id}/edit")
                 (sg-table-action "Delete" action: "/users/{id}"
                                  method: "delete"
                                  confirm: "Delete?")))
sg-paginatorprocedure

Create a pagination navigator.

Renders prev/next links and page numbers. Links use sg-link internally for SSE morphing. Current page is displayed as a <span>. Ellipsis shown when page window doesn't cover full range.

Example:

(sg-paginator current-page: 3 total-pages: 10
              base-url: "/users" target: "#user-list"
              params: '((sort . "name")))

Return the Sigil Web UI JavaScript library as a string.

Use this to include the script in static builds or serve it from a custom route.

Example:

(http-response/text 200 (sigil-web-ui-script))

Create an HTTP handler that serves the Sigil Web UI script.

Example:

(router
  (route 'GET "/js/sigil-web-ui.js" (sigil-web-ui-handler))
  ...)

Generate SXML for including Sigil Web UI in a page head.

Parameters: idiomorph-url: URL for idiomorph library (default: CDN) script-url: URL for sigil-web-ui.js (default: /js/sigil-web-ui.js)

Example:

(html
  (head
    ,@(sigil-web-ui-head)
    (title "My App"))
  (body ...))
with-sigil-uiprocedure

Middleware that serves the Sigil Web UI client script.

A chain-style wrapper: it intercepts GET <path> (default /js/sigil-web-ui.js, the URL sigil-web-ui-head points at) and serves the bundled client, passing every other request through. This is the middleware counterpart to http-response/page auto-injecting the script tag: add (with-sigil-ui) to your chain and you never register the JS route by hand.

Example:

(-> (routes (router ...))
    (with-sigil-ui)
    (with-logging)
    (with-not-found))
sse-morphvariable

(No description)

sse-removevariable

(No description)

sse-classvariable

(No description)

sse-evalvariable

(No description)

sse-redirectvariable

(No description)

sse-reloadvariable

(No description)

(No description)

sse-jsvariable

(No description)

sse-flashvariable

(No description)

(No description)