v0.2.x — early but tagged. Expect breaking changes between minors.
Markdown is great for writing. HTML is great for showing. Tinkerdown lets you write the first and serve the second — from one file.
Tinkerdown turns a single markdown file — frontmatter for data sources, prose for content, declarative attributes for interactivity — into a live, themed, URL-routable web app. Built on LiveTemplate.
- One file, co-authorable. The source stays human- and AI-editable markdown. The rich rendered page is a result, not the artifact you have to maintain.
- Declarative, not freeform. Interactivity comes from a fixed vocabulary of
lvt-*attributes — predictable for LLMs, consistent across pages, no generic LLM-output aesthetic. - Live data, not a snapshot. Bind to SQLite, PostgreSQL, REST, JSON, CSV, shell commands, markdown, WASM, or computed sources. The rendered page reflects current state, not a frozen export.
- Real URLs. Every page is linkable, bookmarkable, deep-linkable. No in-memory SPA state hiding behind a single
/. - Git-native and self-hosted. Plain text in a repo. Version history, search, collaboration, offline access, no subscriptions.
- Progressive complexity. Standard markdown → declarative attributes → Go templates. Each step builds on the last without rewriting. See the Progressive Complexity Guide.
- Disposable-software friendly. The admin panel for this sprint. The tracker for that hiring round. The dashboard for the incident retro. Things you'd never scaffold a React app for, but that earn their keep for days or weeks.
That works — until you want to edit it. Generated HTML is opaque to hand-editing; the next change means another full re-prompt instead of a five-second text edit. And the data is whatever was true when the file was generated.
Tinkerdown keeps the source you edit (markdown + frontmatter + a small attribute vocabulary) separate from the page you ship (themed HTML, live data, WebSocket reactivity). You — or your agent — change the markdown. Everything else updates.
# Install
go install github.com/livetemplate/tinkerdown/cmd/tinkerdown@latest
# Create a new app
tinkerdown new myapp
cd myapp
# Run the app
tinkerdown serve
# Open http://localhost:8080Write a markdown file with a YAML source definition and a standard markdown table. Tinkerdown infers that the "Tasks" heading matches the "tasks" source and auto-generates an interactive table with add, edit, and delete:
---
title: Task Manager
sources:
tasks:
type: sqlite
db: ./tasks.db
table: tasks
readonly: false
---
# Task Manager
## Tasks
| Title | Status | Due Date |
|-------|--------|----------|Run tinkerdown serve and get a fully interactive app with database persistence — no HTML needed:
The same primitive scales to other artifacts:
| Artifact | What it looks like |
|---|---|
| Dashboard / status report | Frontmatter pulls from PostgreSQL or REST; tables, computed totals, and Mermaid diagrams render inline. (examples/markdown-data-dashboard) |
| Literate doc / runnable explainer | Prose alongside live widgets, real source cited by line range, and your deployed app embedded inline — the doc is the working code. (Literate Authoring guide, examples/literate-counter-include) |
| Triage / standup board | Action buttons mutate a shared SQLite or PostgreSQL source; every teammate's tab stays in sync over WebSocket. (examples/standup-bot, examples/team-tasks) |
| Throwaway admin panel | Point at a table you already have. Edit/delete/add for free. (examples/auto-table-sqlite) |
Literate authoring. When the page itself is a tutorial, the same markdown can show real source and run it. The snippet below shows two of the three primitives: include= cites line ranges from your .go / .tmpl source files, and embed-lvt drops the running app inline. The third — show-source — pairs an inline ```lvt block with its highlighted template. No copy-pasted snippets to drift out of date.
```go include="./_app/counter.go" lines="5-8"
```
```go include="./_app/counter.go" lines="13-35" highlight="20"
```
```embed-lvt path="/apps/counter/" upstream="http://127.0.0.1:9090"
```A literate doc in the wild: livetemplate.fly.dev/recipes/counter — prose, source listings, and a live counter on one page, all from a single markdown file.
See the Literate Authoring guide for the full primitive set (line ranges, named regions, line highlights, source-link footers) and examples/literate-counter-include for the canonical pattern.
Need more control? Tinkerdown has five complexity tiers. Each builds on the previous — nothing rewrites; start at the lowest tier that works:
- Tier 0 — pure markdown. Standard checklists become interactive; toggles and adds persist to the file.
- [ ] Buy milk - [x] Email Sarah
- Tier 1 — markdown + YAML sources. Frontmatter declares data; markdown tables auto-bind. (Shown above.)
- Tier 2 —
lvt-*attributes. Explicit HTML binding when auto-rendering isn't enough — custom forms, confirmation dialogs, datatables, cross-source selects.<table lvt-source="tasks" lvt-columns="title,status" lvt-datatable lvt-actions="Complete,Delete"> </table>
- Tier 3 — Go templates. Conditionals, loops, and custom layouts inside
```lvtblocks; full access to.Data,.Error,.Errors.```lvt {{range .Data}} <div class="card"><h3>{{.Title}}</h3></div> {{end}} ```
- Tier 4 — WASM sources. Write a custom data source in TinyGo when built-ins don't fit; the module exports a
fetchfunction and runs server-side.
See the Progressive Complexity Guide for full examples and the escape hatches between tiers.
- Single-file apps: Everything in one markdown file with frontmatter
- 9 data sources: SQLite, JSON, CSV, REST APIs, PostgreSQL, exec scripts, markdown, WASM, computed
- Auto-rendering: Tables, selects, and lists generated from data
- Real-time updates: WebSocket-powered reactivity
- Zero config:
tinkerdown servejust works - Hot reload: Changes reflect immediately
Define sources in your page's frontmatter:
---
sources:
tasks:
type: sqlite
path: ./tasks.db
query: SELECT * FROM tasks
users:
type: rest
from: https://api.example.com/users
config:
type: json
path: ./_data/config.json
---| Type | Description | Example |
|---|---|---|
sqlite |
SQLite databases | lvt-source-sqlite-test |
json |
JSON files | lvt-source-file-test |
csv |
CSV files | lvt-source-file-test |
rest |
REST APIs | lvt-source-rest-test |
pg |
PostgreSQL | lvt-source-pg-test |
exec |
Shell commands | lvt-source-exec-test |
markdown |
Markdown files | markdown-data-todo |
wasm |
WASM modules | lvt-source-wasm-test |
computed |
Derived/aggregated data | computed-source |
Generate HTML automatically from data sources:
<!-- Table with actions -->
<table lvt-source="tasks" lvt-columns="title,status" lvt-actions="Edit,Delete">
</table>
<!-- Select dropdown -->
<select lvt-source="categories" lvt-value="id" lvt-label="name">
</select>
<!-- List -->
<ul lvt-source="items" lvt-field="name">
</ul>See Auto-Rendering Guide for full details.
| Attribute | Description |
|---|---|
lvt-source |
Connect element to a data source |
name (on button) |
Handle click events |
name (on form) |
Handle form submissions |
lvt-on:change |
Handle input changes |
data-confirm |
Show confirmation dialog before action |
data-* |
Pass data with actions |
See lvt-* Attributes Reference for the complete list.
Recommended: Configure in frontmatter (single-file apps):
---
title: My App
sources:
tasks:
type: sqlite
path: ./tasks.db
query: SELECT * FROM tasks
styling:
theme: clean
---For complex apps: Use tinkerdown.yaml for shared configuration:
# tinkerdown.yaml - for multi-page apps with shared sources
server:
port: 3000
sources:
shared_data:
type: rest
from: ${API_URL}
cache:
ttl: 5mSee Configuration Reference for when to use each approach.
Tinkerdown's surface area is small on purpose: a fixed lvt-* attribute vocabulary, frontmatter with a small set of well-defined fields, and a single file that contains the whole app. That gives an LLM very few ways to be wrong.
Describe what you want, and the agent drafts the markdown:
Create a task manager with SQLite storage,
a table showing tasks with title/status/due date,
a form to add tasks, and delete buttons on each row.
The output is a .md file you can read, diff, and hand-edit. No component tree, no build config, no node_modules to reason about.
See AI Generation Guide for tips on Claude Code, Cursor, and other agents.
Getting Started:
Guides:
Reference:
Planning:
git clone https://github.com/livetemplate/tinkerdown.git
cd tinkerdown
go mod download
go test ./...
go build -o tinkerdown ./cmd/tinkerdownMIT
Contributions welcome! See ROADMAP.md for planned features and current priorities.
The framing in this README was sharpened by Thariq Shihipar's The Unreasonable Effectiveness of HTML and the Hacker News discussion around it. Tinkerdown is our attempt at a structured answer to the markdown-vs-HTML tension that piece mapped out.
