Decker and Lil: Building HyperCard-Style Interactive Documents (Without the Old Constraints)
The moment it clicks: interactive pages that feel like a toy
The best demos don’t start with a framework. They start with a feeling.
Picture a blank “deck” (Decker’s word for a collection of interactive pages). On one page you place a button. On another you place a field (a typed text box). Click the button and something changes immediately—maybe sound plays, maybe the page navigates somewhere else, maybe a pixel-y canvas redraws.
That’s the core promise behind Decker: interactive documents you can author visually, but also extend with real scripting. It’s inspired by HyperCard and carries a classic Mac OS visual vibe, including the ditherpunk aesthetic—cozy constraints that make improvising more fun than polishing. (github.com)
And here’s the practical twist that makes it modern: finished decks can be exported as standalone HTML documents that self-execute in a browser, so sharing doesn’t require “install the app first.” ()
The mental model: deck → card → widget → script
If Decker has a learning curve, it’s mostly about one mental model.
- Deck: the whole interactive document.
- Card: a page inside the deck (think of it like a screen in a slideshow).
- Widget: a visual/interactive component on a card, like a button, field, slider, canvas, or a custom component.
- Script: code attached to a widget (or a card or the deck) that responds to events.
This matters because Decker’s programming style is event-driven. A typical event is click (button activation), change (a field’s contents edited), or view (a refresh tick while something is visible). When an event fires, Decker runs the matching script handler. (beyondloom.com)
Stateless scripting, with state stored in widgets
One concept that feels odd at first—until you see it drawn out—is Decker’s approach to state.
Decker scripts are stateless in the sense that each event handler invocation runs in a fresh universe: local variables don’t persist across events. What persists is widget state (for example, the text inside a field, the value of a checkbox, or the pixels inside a canvas). That design reduces “why did my variable forget itself?” debugging and makes deck editing safer. ()
A tiny example: a counter button
Here’s the kind of logic that makes Decker feel approachable even before you’ve learned Lil (Decker’s scripting language).
on click do
countField.text:countField.text+1
end
Interpretation in plain language:
- on click do... end defines the function Decker should run when the widget receives a click event.
- countField is the name of a field widget.
- countField.text:... assigns to that field’s text, updating visible state.
The important detail is that the persistent number lives in the widget (countField.text), not in some hidden global variable. (beyondloom.com)
Widgets become components: Contraptions (custom widgets)
Built-in widgets are great, but real projects want reuse.
Decker calls reusable custom components Contraptions: “custom widgets” built from simpler widgets and defined in a Prototype (a definition template). You can design one contraption—say a labeled counter with an increment button—and then place multiple instances around your deck. ()
Even better, Decker treats these as first-class citizens for sharing:
- you can copy a contraption and paste it into another deck,
- and the clipboard snippet can include the prototype definition so the component stays self-contained. ()
This is one of those “quality of life” design choices that makes Decker feel like an authoring tool, not just a runtime.
Lil: a layered language for behavior and data work
Decker’s scripting language is Lil. The language is designed to be learned in layers (a friendlier on-ramp), but it’s still multi-paradigm—it borrows ideas from imperative, functional, declarative, and vector-oriented styles. (beyondloom.com)
The syntax you’ll recognize from other languages
Lil’s most approachable syntax looks familiar:
- on click do... end for event handlers
- if... else... end for branching
- square brackets go["Card" "Transition" 15] for calling functions with arguments
Scripts can navigate between cards, play sounds, and manipulate widget properties using a dot-notation like inventory.widgets.haskey.value. That reads like a path through the deck’s structure. ()
Queries: where Lil starts to feel powerful
Once widgets hold state, you quickly want to find and transform that state.
Lil includes an SQL-like query language operating on tables. Query clauses can filter and sort rows, and can also group results. The key idea is that the same query syntax can apply to dictionaries, lists, and strings—not just “database tables.” ()
That means “my deck contains a bunch of widgets” can turn into “my deck can compute on its own structure,” which is where interactive documents start to feel like real applications.
Portability and export: from deck edits to self-executing HTML
Here’s the part that changes how people share work.
Decker supports two execution modes:
- Web-Decker: the project can be built as a web application distributed as a single freestanding HTML file. (github.com)
- Native-Decker: a compiled native app written in C, using SDL2 for graphics/input, and it can be built for multiple OSes. ()
But the real magic for sharing is the export format.
A deck is saved as a UTF-8 text document and can be embedded inside a wrapper HTML structure where a PAYLOAD section contains the deck data and a RUNTIME section provides the self-contained JavaScript/CSS/HTML needed to run it. Decker also emphasizes that the payload is line-oriented and diff-friendly, so your source control history stays understandable. (beyondloom.com)
If a deck is normally distributed with full HTML, it can also be stored and manipulated as a .deck-style payload-only file for non-browser environments, without dragging a full parser along. ()
Lilt: the command-line side of Lil
A visual tool is fun. A terminal is productive.
To bridge those worlds, Decker ships Lilt, a command-line interface for Lil. It supports REPL usage (interactive evaluation), plus command-line execution of files/expressions, and includes bindings for basic CLI and filesystem I/O so you can automate deck generation. (beyondloom.com)
One practical pattern looks like this:
- Use Lilt to load a deck file.
- Modify widgets or scripts programmatically.
- Write out an exported HTML deck.
This is how “interactive sketching” can graduate into repeatable tooling—without abandoning the deck format. ()
The one security detail beginners should not ignore
Because Decker can run in a browser and can also export self-executing documents, it needs a story for safety.
Decker normally sandboxes scripts to keep them from doing low-level host access and to keep Web and Native capabilities aligned. For advanced use cases, it includes opt-in APIs for “dangerous” operations under a named mechanism (“The Danger Zone”). ()
In other words: Decker is designed to run sketchy creative prototypes without immediately turning the browser into a wild west.
Why this all feels different from “yet another web editor”
It’s tempting to treat Decker as a retro novelty, but the real distinction is architectural.
Decker makes interactive documents feel like a toolkit:
- state lives in widgets (so event-driven behavior stays reliable), ()
- custom UI becomes reusable contraptions (so complexity has components), ()
- Lil combines event scripting with query-style data work, so decks can compute as well as display. ()
- exports are plain text + self-executing HTML, so sharing and versioning aren’t an afterthought. ()
What happens when you build an interactive document you can edit like source code, navigate like a stack of cards, and share like a single web page? Decker’s approach makes that feel normal—and normal is where creators keep working.
Closing thought
Decker sits in a sweet spot: beginner-friendly event handlers and named widgets, with a path to deeper programming through Lil, queries, and command-line tooling via Lilt. It borrows the spirit of HyperCard while upgrading the parts that usually trip modern creators—portability, diff-friendly storage, and a scripting model that stays predictable under pressure. ()
Comments (0)
No comments yet. Be the first to respond!
Leave a Comment
Your comment will be visible after review.