Your First Page - Final

Build your first Thorm page and learn the basic workflow: structure, state, and rendering mode.

Final considerations

When planning this tutorial, we decided to break from the current theme and seriousness of the Thorm website. We wanted something simple that doesn’t push users into too many Thorm features, but also not as basic as the counter example. The choice was between a newsletter signup form and a contact form. The newsletter didn’t spark any design ideas and felt too simple, so we chose the contact form. While looking around the web for examples, everything felt too rigid and plain, and the brain revolted—plotting a zany, mischievous revenge on Thorm’s seriousness. And it was right.

We aimed to keep this tutorial very simple and avoided using any PHP framework. To showcase Thorm features, plain old PHP is more than enough. The only exception is Composer: we define namespaces with its help and build the app through it. At the time of writing this tutorial, using Composer to create the app isn’t verified yet because the Thorm Git repo is not public, so that path is blocked.

We split the page code into several classes and moved them into separate files. The contact page is small (especially compared to what we deal with on thorm.dev) and could have fit comfortably into a single class with methods for Header, Hero, and Contact. Still, we want to build self‑contained components with Thorm, because that will make development easier later. Writing components by hand every time is tedious and boring, but it becomes incredibly easy once you understand how to compose Thorm functions, actions, and events. Hopefully the learning curve is as smooth for you as it was for us—though we started from zero and iterated on everything.

For the images shown in the contact form, we used genAI. The reason is simple: speed and cost. The scope of this tutorial is to teach Thorm—we could have done that with plain, boring HTML and CSS—but since generating images is only a prompt away and the results are much better, we went with genAI. I know some artists will object, but consider this: we would never have hired an artist for this, so there’s no actual loss. Also, we aren’t distributing these images—they’re only visible on our website—and we’ll use placeholders in the downloadable code.

There are two images on this page: a hero mascot (a wolf, or maybe a fox) and a map. The mascot was the only image originally planned. But something funny happened when we tested form submission: we introduced a layout shift. The form was taller than the response view, so the container height snapped smaller and the page visibly shifted. It was distracting.

Our first thought was to record the form’s height and force the response view to match it—more JavaScript, and more code in Thorm. At the time, the left pane with ACME HQ info was shorter than the form. Since we wanted to avoid extra JS, we took a defensive approach: make the static info box slightly taller than the form. That way, the overall layout height stays stable and TailwindCSS keeps the grid from shifting.

Genius, right? The brain agreed—but what should we add to the static contact box? Obviously a map, but not just any map: a map of our client’s HQ.

Why am I highlighting this? Because a dynamic solution (JavaScript) isn’t always the best answer. Static, defensive solutions can be better than a fluid, “smart” design.

Whit no further ado here is the final result:

Status: Developer Preview
Things may change, things might break. If something feels awkward, it's probably a design edge we're still smoothing out.