Events
Handlers on elements, keyboard shortcuts, the events components fire, and the overlays a page opens.
Handlers
A handler is written inside an element's block: on <event> { … }, or on <event>(name) { … } when the body reads the event object.
wf
page Clicks(path: "/") {
state n = 0
state last = ""
Button("+1") { on click { n = n + 1 } }
Input(bind: last, placeholder: "type") {
on input { log(last) }
on keydown(e) { if e.key == "Enter" { log("submitted {last}") } }
on focus { log("in") }
on blur { log("out") }
}
Text("{n}")
}
A handler may name any DOM event: the common ones are click, input, change, submit, focus, blur, keydown, keyup, mouseenter and mouseleave, and the rest — dblclick, contextmenu, pointerdown, pointermove, touchstart, wheel, scroll, paste, drop, dragover, animationend, ended and the others — work the same way. The components reference lists every one. A component's own declared events (chapter 11) join them on its calls. A handler's body is an imperative block, so it may assign state, call actions, await, and navigate.
The event object
Name a parameter to read the event: on keydown(e) { … }. Without one, the event is still in scope as event. It is the browser's own event object, so e.key, e.target.value, e.clientX, e.preventDefault() and e.stopPropagation() are all there.
wf
page Drop(path: "/", title: "Drop", description: "A place to drop files.") {
state names: [String] = []
Heading("Drop files here").h1
Card {
on dragover(e) { e.preventDefault() }
on drop(e) {
e.preventDefault()
names = Array.from(e.dataTransfer.files).map(f => f.name)
}
Text(if names.length == 0 { "Nothing yet" } else { names.join(", ") })
}
}
A handler on a component call — UserCard("x") { on click { } } — attaches to the component's root element, so a styled button component is clickable wherever it is used.
Keys
on key("…") answers to one key, with modifiers:
wf
page Keys(path: "/") {
state open = false
state q = ""
Input(bind: q, ref: box, placeholder: "Search") {
on key("Escape") { q = "" } // on the element: fires while it has focus
on key("Enter", e) { log(e.target.value) }
}
on key("ctrl+k") { box.focus() } // on the page: fires anywhere on the page
on key("cmd+k") { box.focus() }
on key("?") { open = !open }
if open { Alert("Shortcuts: ctrl+k to search, ? for this.").info }
}
Spelling: modifiers ctrl, shift, alt, meta (or cmd), joined with +, then the key as the browser names it — k, Enter, Escape (or esc), Tab, ArrowDown (or down), space. On an element the handler listens on that element; at the top of a page or component it listens on the document for as long as the page shows. Every modifier must match exactly, so ctrl+k does not fire on ctrl+shift+k.
Emitting events from components
A component declares the events it fires and fires them with emit; the caller handles them like DOM events. Chapter 11 covers it:
wf
component Counter(_ start: Number) {
event change(value: Number)
state n = start
Row(gap: .sm) {
Button("−") { on click { n = n - 1 emit change(n) } }
Text("{n}")
Button("+") { on click { n = n + 1 emit change(n) } }
}
}
page Use(path: "/") {
state total = 0
Counter(10) { on change(v) { total = v } }
Text("Now {total}")
}
Overlays: modals, dialogs, toasts and menus
wf
page Overlays(path: "/", title: "Overlays", description: "What a page opens.") {
state confirm = false
state deleted = false
Heading("Overlays").h1
Button("Delete").danger { on click { confirm = true } }
Modal(visible: confirm, title: "Delete this?") {
Text("This cannot be undone.")
Modal.Footer {
Button("Cancel") { on click { confirm = false } }
Button("Delete").danger { on click { confirm = false deleted = true } }
}
}
if deleted { Toast("Deleted").success }
Menu(trigger: "More") {
Menu.Item { Text("Rename") }
Menu.Divider
Menu.Item { Text("Archive") }
}
Tooltip("Copies the link") { IconButton(icon: "copy", label: "Copy link") }
}
ModalandDialogare real<dialog>elements, opened while theirvisible:state is true. The browser traps focus, makes the rest of the page inert and closes on Escape or a click on the backdrop — and when it closes that way, the state is set back tofalse, so it never disagrees with what is on screen. Focus returns to what opened it.title:is the accessible name (A08without one).Modal.Footeris the row of actions.Toastshows the moment it is rendered and leaves after three seconds; it is announced politely to screen readers. Put it under a condition that turns true.MenuandDropdownopen a list from a button. Arrow keys move between items, Home and End jump to the ends, Enter and Space choose, Escape closes with focus back on the button, and a click outside closes it.Tooltipshows its text when its child is hovered or focused, and is linked to it witharia-describedby.
Listening outside the page's elements
A handler on an element hears that element. For the window or the document — a resize, a scroll of the page, a message from another window — use an effect with a cleanup, which removes the listener when the page leaves. An action named without parentheses is the function itself, so it can be handed to the browser:
wf
page Scrolled(path: "/", title: "Scrolled", description: "How far down the reader is.") {
state y = 0
action track() { y = window.scrollY }
effect {
window.addEventListener("scroll", track)
cleanup { window.removeEventListener("scroll", track) }
}
Heading("Scrolled").h1
Text("{y}px down")
}
For the viewport's size, the network and the URL, the language already keeps values current — viewport, network, query, hash — so no listener is needed (State).
On this page
Handlers Keys Emitting events from components Overlays: modals, dialogs, toasts and menus Listening outside the page's elementsChecked by the test suite
Every code block in the guide is parsed, checked and type-checked on each release.