Breaking up a big HTML5 app
βοΈ Breaking up a big HTML5 app
The Write an HTML5 page article showed you the contract of an HTML5 block: inputs written as double curly braces, declared outputs, an event to hand control back. One question that contract doesn't answer on its own: what do you do once your app no longer fits in a single block? Five splitting patterns answer that, each with a real trade-off. π§©
1οΈβ£ One block per screen
The default split, and the most profitable one. Each screen of your app becomes an HTML5 block a few dozen lines long; the sequence of screens becomes arrows in the graph.
What you gain: when you ask the AI to touch up a screen, it only sees that one block. The token cost of a change drops, and above all, the risk of breaking something elsewhere becomes zero by construction β you cannot break screen 3 while fixing screen 1, they live in different blocks.
What you lose: nothing serious as long as the screens are genuinely different. This is the starting point to default to.
2οΈβ£ One parameterized block instead of ten identical twins
Ten quiz questions don't need ten identical HTML5 blocks. A single block with {{question}}, {{choiceA}}, {{choiceB}} plus a loop in the graph does the job.
What you gain: fixing the layout happens once, not ten times. The graph stays small even as the content grows.
What you lose: a parameterized block is more abstract than a literal one β you have to open the block and look at what feeds it to understand what will show up. The practical threshold: if your ten screens really do differ (not just the text, but the layout itself), and more than two {{}} would be needed per difference, split them into separate blocks instead.
3οΈβ£ State belongs in variables, not in the browser's local storage
An HTML5 page can obviously write to the browser's local storage. That is almost always the wrong choice here, because a Celestory variable does everything that storage does, and better:
Browser local storage | Celestory variable |
|---|---|
invisible in the editor | listed, typed, grouped |
invisible while testing | shown live in the player's debugger |
not exportable | export / import from the Variables panel |
unreadable by other blocks | can be wired to any point |
survives between two playthroughs (often unwanted) | scoped to the current playthrough |
What you gain: everything the page has remembered stays visible and testable from the graph, including by someone who never opens the code.
What you lose: nothing β this is the one pattern of the five with no real downside, once it becomes a habit.
The typical case: a navigation menu you want to keep across screens. A Text variable (or an object) holding the menu's state, wired into the {{menu}} of every HTML5 block, instead of duplicated state inside each page.
4οΈβ£ A calculation leaves the HTML the moment it decides a branch
If a result computed inside your page determines the next screen, that result must be an output of the HTML5 block β never a JavaScript variable that stays internal to the page.
What you gain: the graph shows the story's actual logic. A Condition block wired to your output reads at a glance; an if hidden in JavaScript doesn't read at all.
What you lose: one extra round trip (posting the output, then reading it in a Condition block) where a plain JavaScript if would have done the job instantly. The rule that settles it: any decision the user can see must be visible on the graph.
5οΈβ£ One Baserow block per operation, never a hidden call inside the HTML
An HTML5 page can call the Baserow API directly from its JavaScript. Doing so buries the URL, the table and the access token inside code, loses the if error path the Baserow block provides for free, and hides, from the graph, the fact that you're writing to a database.
What you gain by going through separate Baserow blocks instead β one to read, one to create, one to update: the data's path reads like a diagram, you see where it comes in, where it's transformed, where it comes out. This is the strongest argument for the graph in the whole nocode case, and the easiest one to show someone who doubts it. One useful detail: a Baserow block grows with its configuration β in read mode, one output per column of the table; the table's schema becomes visible without even opening it.
What you lose: nothing on the reliability side, but a bit of speed on the first draft β a fetch typed straight into the HTML is quicker to write than three blocks and their connections.
π¨ Passing a theme to an HTML5 page
{{}} markers aren't limited to visible text: because the substitution is purely textual, a marker can sit inside a <style> tag, in the middle of a custom CSS property:
<style>
:root {
--brand: {{brandColor}};
--radius: {{radius}}px;
}
.card { background: var(--brand); border-radius: var(--radius); }
</style>
A handful of {{}} markers like these, and the page's whole look follows β without touching the rest of the code.
πΊ There is a limit, though, confirmed in the code: an HTML5 block's inputs only accept Text, a Number or a Boolean, while a Format text block's dynamic inputs also accept an Object, which it knows how to serialize as JSON. A Color-type variable can therefore never be wired directly into an HTML5 block's {{}} β it always has to pass through text first. That's why the useful pattern, once a theme has several tokens (color, radius, fontβ¦), goes through a detour: a Format text block that assembles every token into a single CSS string, wired into one {{theme}} on the HTML5 block. One connection instead of one per color β and two pages wired to the same Format text block share the same theme.
β Next step: take your biggest HTML5 block and run through the five patterns above in order β it's almost always pattern #1 (one block per screen) that solves the problem first.
Updated on: 21/09/2026
Thank you!
