Build a front end by writing a document
star-burxt turns a .sbmx file into a working component — one that renders, responds to clicks,
holds state, fetches, and routes.
A component is markup, and the logic that goes with it, in one file:
::: props count: Int
:::
# Counter
The count is {{ to_string(count) }}.
::: button on:click=count + 1
increment
:::
You get this:
No build config. No component library. No JavaScript to write. The document is the component.
::: section class=card
# Today
::: p
{{ to_string(model.left) }} left
:::
::: for task in model.tasks key to_string(task.id)
::: button class={{ mark(task.done) }} on:click=Msg.Toggle(string_to_int(key, 0))
::: span class=box
:::
::: span class=text
{{ task.label }}
:::
:::
:::
:::
That screenshot is the real component, running as WebAssembly — clicking a
row runs your update. The other half of the file is Burxt: a Model, a
Msg, and one update function, which
chapter 7 walks through.
For anything past a counter, a component has two halves — the markup, and a ===bx section holding
your own Burxt:
===bx
class Item { id: Int, name: String }
class Model { count: Int, items: [Item] }
enum Msg { Increment, Reset }
pure function update(msg: Msg, m: Model) -> Model {
match msg {
Increment => { return Model { count: m.count + 1, items: m.items }; }
Reset => { return Model { count: 0, items: m.items }; }
}
}
===
::: props model: Model
:::
# Counter
The count is {{ to_string(model.count) }}.
::: button on:click=Msg.Increment
increment
:::
on:click=Msg.Increment names a message, and your update decides what it does. That’s one
function you can read, test, and diff — not a closure that re-runs.
What a component can do
| You write | You get |
|---|---|
# Heading and paragraphs |
headings and paragraphs, as in any Markdown |
{{ total }} |
your data, in the page |
::: props model: Model |
what the component is given |
::: button on:click=Msg.Add |
a button, and your update decides what it does |
::: for item in items key item.id |
a row per item, each with its own buttons |
::: match model.route |
a screen per route — forget one and the build fails |
::: if ready / ::: else |
a section that appears when it should |
::: Badge amount={{ n }} |
another component, imported with use |
===style.local |
CSS scoped to this component, with no default |
commands and watch |
fetching, timers, websockets — without await |
load |
server rendering, and server code that cannot reach the browser |
That is the whole idea. If you can write a document, you can write an app.
Start with the tour
Six short chapters. By the end you will have built a counter, a form, a list, and a price table, and put them on a real page.
The one thing that will surprise you
star-burxt reads your buttons.
Most frameworks treat what happens on a click as your business — it is a function, it runs, and if it is wrong you find out when a user finds out. Here, a click is checked before the page exists.
Misspell something:
The total is {{ toatl }}
unknown variable: toatl
Try to add a word to a number:
::: button on:click=count + "one"
increment
:::
cannot apply `+` to Int and String
Round money without saying how:
::: button on:click=total * 1.5
apply surcharge
:::
You do not have to know why this works to use it. If you would like to, Burxt is the language underneath and the explanation lives there.
Where to go
- Your first component — start here, it takes ten minutes
- Set up — what to install before chapter one
- How do I…? — short answers to common tasks
- When it says no — every refusal, and what to write instead