Live demos.
Every demo shipped with these docs is a plain .aktion
program — 111 of them, in four folders under
docs/demos/. Each tile in the gallery boots the real
runtime in an iframe, so you are looking at the program running, not
a screenshot. Click one to open it full‑page, with a theme
switcher and a View .aktion source button that hands
the program straight to the playground.
Every demo
All 111 programs, grouped by folder — open a section to mount its
previews. The per‑section counts are read from
demos/manifest.json, so this list is whatever is on disk.
Everything below the gallery explains how the demos are built and how to
run them yourself.
What these demos are
One demo is one file. There is no build step, no JavaScript glue and no
per‑demo HTML page:
docs/demos/<folder>/<name>.aktion is the whole
program, and a single runner shell renders whichever one its
?app= parameter names.
That is what makes them useful as examples. Nothing in a demo is a
documentation‑only shortcut, so the text you read here is the text
that runs — paste it into the playground
or drop the file into your own src/ and it behaves the same.
$app(Column([
PageHeader("Invoices", { subtitle: "Everything billed this quarter" }),
Card([
Table([
Col("Invoice", ["INV-001", "INV-002"]),
Col("Amount", ["$1,200", "$840"])
])
])
], { gap: "lg" }))
Programs read like that the whole way down: one $app(…)
call composing components, with the data and helpers it references
declared around it. The median demo is 91 lines —
the smallest is 41, the largest 457.
Start here
Four programs cover most of what you will want to see first. Each link opens the full‑page runner in a new tab, so the gallery stays where it is.
To-do List
A $store with persist and undo history, drag‑to‑reorder rows, filters and an empty state.
Weather Dashboard
$query against a public API — cached, polled, and refetched when the tab regains focus.
Admin Dashboard
AppShell + Sidebar + $router: four router‑driven pages behind one shell.
Login Card
One drop‑in section: live validation, show‑password, social providers, and a submit that toggles a loading state.
Open the demo →Arrived from a guide and want the demo that exercises the primitive you just read about? This is the index:
| Guide | Demos that show it |
|---|---|
| HTTP | Dictionary, Pokedex, Aktion Website ($http); Weather Dashboard, GitHub Explorer, Crypto Watchlist, News Reader, Show Finder, Cocktail Explorer, Currency Converter ($query) — ten mini‑apps in all. |
| Stores | To-do List, Kanban Board, Budget Tracker, Invoice Builder — and 32 more. 36 of the 57 mini‑apps keep their data in a $store. |
| Routing | Admin Dashboard and Storefront — the only two demos that route between pages. |
| Hooks | components/forms, inputs, selection, utility — each holds per‑instance state with the $state hook. |
| Layout · sx | components/layout for the primitives, any industry-specific/ surface for dense Grid work. |
| Themes | Any demo — switch theme in the runner toolbar. Aktion Website and Saas Landing also call $theme(…) themselves. |
No bundled demo writes to a REST API
The $http demos are all reads. To-do
List looks like a CRUD app but persists through
$store({ persist }) to localStorage. For
create / update / delete over HTTP, scaffold the
todos-app template instead —
npm create aktion@latest my-app -- --template todos-app
— which wires $http create, toggle, edit and
delete against a REST endpoint.
The four categories
The folder a demo lives in tells you how much of an app it is. That is the entire taxonomy: a program lives in exactly one folder, and the folder name is the section heading you see in the gallery.
| Folder | Programs | What it demonstrates | Guides it illustrates |
|---|---|---|---|
mini-apps/ |
57 | Whole applications — trackers, dashboards, storefronts, API browsers, marketing pages. 36 keep data in a $store (34 of those persist it, 8 keep undo history), 10 fetch over the network, 10 build forms with $form, 2 route. |
Stores, HTTP, Routing |
blocks/ |
25 | One drop‑in section per file — login card, pricing plans, checkout form, data table, command palette. No stores and no network, just local atoms, which makes them the smallest complete things to read. 8 raise a $toast. |
Components, Layout |
components/ |
14 | Copy‑out function components grouped by kind — buttons, inputs, cards, charts, overlays, navigation, … Each file collects nine to twelve of them; 8 of the 14 use the $state hook. |
Components, Hooks |
industry-specific/ |
15 | One vertical surface per file — finance, healthcare, SaaS, e‑commerce, AI, real estate, travel, … Each is a dense dashboard assembled from reusable blocks; 10 include charts and all 15 raise a $toast. |
Layout, Components |
mini-apps/ is listed first everywhere because
scripts/build-docs.mjs sorts it there; the other folders
follow alphabetically and files inside a folder are sorted by name.
The counts above come from demos/manifest.json, which that
script regenerates from disk on every docs build.
Reading a demo
The demos are meant to be read, not just run. Three things make that quick: the source layout is the same in every file, the runner hands you the program in an editor, and the URLs are guessable.
The source layout
Read the first line before anything else. Every program calls
$app(…) near the top and declares everything that
call references below it — declarations hoist, so the head of the
file is the page shape and the rest is detail.
$app(Center([Container([Column([header, addRow, list], { gap: "md" })], { size: "sm" })], { minHeight: "85vh" }))
todos = $store({
persist: "todo.items",
history: 50,
items: [{ id: "t1", title: "Read the Aktion docs", done: false }],
add: (s, title) => { s.items = [...s.items, { id: $util.uuid(), title, done: false }] },
toggle: (s, id) => { s.items = s.items.map(t => t.id == id ? { ...t, done: !t.done } : t) }
})
$draft = ""
header = PageHeader("To-do List", { subtitle: `${todos.items.length} tasks` })
addRow = Row([
StackItem(Input("draft", { placeholder: "What needs doing?", value: $draft }), { grow: 1 }),
Button("Add", { variant: "primary", onClick: () => { todos.add($draft); $draft = "" } })
], { gap: "sm", align: "end" })
list = Card([Column(todos.items.map(t => Row([
Checkbox(`done-${t.id}`, { value: t.done, onChange: () => todos.toggle(t.id) }),
Text(t.title)
], { gap: "sm", align: "center", key: t.id })), { gap: "sm" })])
That is mini-apps/todo-list.aktion cut down to its
skeleton: header, addRow and
list are used on line 1 and defined further down. 110 of
the 111 programs put $app(…) in their first eight
lines; mini-apps/aktion-website.aktion is the one that
calls it lower, at line 33.
Most files also open with a comment header saying what the program is
and what it is a starting point for — 110 of 111 carry one, and 55
add a // Uses: line naming the primitives. Because the
demos are plain text, grep is a perfectly good index:
# Which demos fetch over the network?
grep -l '$query(' docs/demos/*/*.aktion
# Which demos route between pages?
grep -l '$router(' docs/demos/*/*.aktion
Eight programs answer the first question and two the second; count the
four $http demos as well and ten of the 57 mini‑apps
talk to a network, which is the figure
The four categories reports.
The runner shell
One page, demos/index.html, renders every demo. It adds a
thin toolbar above the program and nothing else:
| Control | What it does |
|---|---|
| App | Switches program without leaving the page, grouped by folder. It rewrites ?app= in the address bar as you go, so the URL stays shareable. |
| Theme | Re‑renders the program under any built‑in theme — light, dark, shadcn, shadcn-dark, mui, mui-dark, heroui, heroui-dark, signal, signal-dark, soft. The program itself never changes. |
| View .aktion source | Opens the program in the playground as a #code= share link, ready to edit and re‑run. Nothing is uploaded — the source is gzipped into the URL fragment. |
Errors are not swallowed. The runner listens for the element’s
error event and prints parse and runtime messages in a red
box above the program, so a demo you break while editing tells you
where.
Deep‑linking a demo
Both URL shapes the gallery uses are ones you can write by hand — useful when a guide, an issue, or your own notes should point at one exact program:
demos/index.html?app=mini-apps/todo-list.aktion // full page, with the toolbar
demos/index.html?app=blocks/login-card.aktion&embed=1 // chrome-less: the program only
A missing or unrecognised app falls back to
mini-apps/aktion-website.aktion. embed only
has to be present — any value hides the toolbar, which is exactly
what the preview tiles load.
How the preview tiles work
The tiles under Every demo are not images. Each one mounts an iframe pointing at the embedded runner, which is why the gallery is careful about how many programs it boots at once.
- One section at a time. Folders are accordions: opening one collapses the others, and Mini Apps is open on arrival.
- Only what is near the viewport runs. An
IntersectionObserverwith a 150 px margin mounts a tile’s iframe as it approaches and removes it again once it scrolls away. - Tiles are zoomed out, not cropped. The iframe renders at 400% of the tile and is scaled to 25%, so a whole page fits a 16:10 thumbnail.
- Hover to auto‑scroll. Point at a tile and its preview scrolls itself at about 55 content pixels per second, reverses at the bottom, and snaps back to the top when the pointer leaves.
Previews are look‑only
Pointer events are disabled inside a tile. Clicking opens the interactive full‑page demo in a new tab rather than pressing whatever control sits under the cursor. Hover auto‑scroll is the only way to see the rest of a long page from the gallery.
Running them locally
The gallery itself is static, but the runner imports the runtime from
dist/, so a fresh checkout has to be built once before any
demo renders. Two routes work.
From a clone, with Vite
git clone https://github.com/asfand-dev/aktion.git
cd aktion
npm install
npm run dev # = npm run build && vite — builds dist/, then serves the repo root
Open the URL Vite prints (http://localhost:5173 unless the
port is taken) and append /docs/live-demos.html. Editing a
.aktion file and reloading is enough: the runner fetches
each program with a cache‑busting ?ts= query, so you
never get a stale copy.
As the deployable site
npm run build:docs # rescans docs/demos/, then assembles ./site/
npx http-server site -p 4321 # or: npx serve site
Then open http://localhost:4321/live-demos.html. Because
build:docs regenerates
docs/demos/manifest.json before it copies anything, a
program you drop into one of the four folders shows up in the gallery
without editing the manifest by hand.
Opening the file directly will not work
Serve the folder over HTTP. The gallery and the
runner both fetch("demos/manifest.json"), and browsers
block that over file://. An empty gallery with no
visible error is almost always this.
Next
Playground
Paste a demo into the editor, change it live, and share the result as a URL.
Open the playground → ReferenceComponents
All 282 built-in components with a live preview and full prop reference for each.
Browse components → Start hereGet started
Scaffold a project, then keep a demo by copying its file into your src/.