Examples
Complete Sema Web examples using the current state / SIP / component APIs.
Counter
scheme
(def count (state 0))
(define (increment ev)
(update! count (fn (n) (+ n 1))))
(define (decrement ev)
(update! count (fn (n) (- n 1))))
(define (reset-count ev)
(put! count 0))
(defcomponent app ()
[:div {:class "counter"}
[:h1 "Counter: " @count]
[:div {:class "buttons"}
[:button {:on-click "decrement"} "-"]
[:button {:on-click "reset-count"} "Reset"]
[:button {:on-click "increment"} "+"]]])
(mount! "#app" "app")html
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script type="text/sema" src="/counter.sema"></script>
<script type="module">
import { SemaWeb } from "@sema-lang/sema-web";
await SemaWeb.init();
</script>
</body>
</html>Todo List
scheme
(def todos (state '()))
(def input-text (state ""))
(def next-id (state 1))
(define (set-input ev)
(put! input-text (dom/event-value ev)))
(define (maybe-add-todo ev)
(when (string=? (dom/event-key ev) "Enter")
(add-todo ev)))
(define (add-todo ev)
(let ((text @input-text))
(when (not (string=? text ""))
(let ((id @next-id))
(update! todos (fn (items)
(append items (list {:id id :text text :done false}))))
(update! next-id (fn (n) (+ n 1)))
(put! input-text "")))))
(defcomponent app ()
[:div {:class "todo-app"}
[:h1 "Todos"]
[:div {:class "input-row"}
[:input {:value @input-text
:placeholder "What needs to be done?"
:on-input "set-input"
:on-keydown "maybe-add-todo"}]
[:button {:on-click "add-todo"} "Add"]]
[:ul
(map (fn (todo)
[:li (:text todo)])
@todos)]])
(mount! "#app" "app")Streaming Chat
Requires a deployed LLM proxy.
scheme
(def messages (state '()))
(def input-text (state ""))
(def current-stream (state nil))
(define (set-input ev)
(put! input-text (dom/event-value ev)))
(define (maybe-send ev)
(when (string=? (dom/event-key ev) "Enter")
(send-message ev)))
(define (send-message ev)
(let ((text @input-text))
(when (not (string=? text ""))
(let ((next-messages (append @messages (list {:role "user" :content text}))))
(put! messages next-messages)
(put! input-text "")
(put! current-stream
(llm/chat-stream
(map (fn (msg)
(message (string->keyword (:role msg)) (:content msg)))
next-messages)
{:model "gpt-4o"}))))))
(defcomponent app ()
[:div {:class "chat"}
[:div {:class "messages"}
(map (fn (msg)
[:div {:class (string-append "message " (:role msg))}
(:content msg)])
@messages)
(when @current-stream
(let ((stream-state (deref @current-stream)))
[:div {:class "message assistant"}
(:text stream-state)]))]
[:div {:class "input-bar"}
[:input {:value @input-text
:placeholder "Ask anything..."
:on-input "set-input"
:on-keydown "maybe-send"}]
[:button {:on-click "send-message"} "Send"]]])
(mount! "#app" "app")Notes
- Use named handler strings in SIP attributes like
{:on-click "save"}. - Lower-level APIs like
dom/on!,watch, andon-mountcan accept function values directly. - For production deploys, prefer compiled
.vfsarchives. See Deployment. - For a fuller production-style example, see
examples/sema-web-app/in the repository and Building a Sema Web App.