The language
A BML file reads like HTML because it mostly is HTML — plus routable pages, components with typed Kotlin props, control flow as tags, and styles that scope themselves. Everything a page needs lives in the file that renders it.
Pages
Each page file declares exactly one <page route="…">. Path parameters like {id} are matched by the router and arrive in server code as in-scope values; the bare requireAuth attribute redirects signed-out visitors before anything renders.
<if> takes <else-if> and <else> as bare separators — one closing tag ends the whole conditional.<for item in expr> iterates any Iterable; <for (i, item) in expr> adds the index.<script server provides="…"> — the block's last expression binds into scope.<page route="/group/{id}" requireAuth>
<script server provides="page">
myapp.group(ctx)
</script>
<main class="wrap">
<if page.error != null>
<p class="error">{ page.error }</p>
<else-if page.found>
<group-head :name="page.groupName" :members="page.members"/>
<else>
<p>This group isn't available.</p>
</if>
</main>
</page>Expressions
The expression syntax is small on purpose. Escaped by default, raw only when you say so.
{ expr }A server-evaluated Kotlin expression, HTML-escaped by default.
{@ expr }Raw HTML output — the explicit, visible trusted path.
:href="user.url"A bound attribute: one server expression. false or null omits the attribute entirely.
class="card-{ size }"Static attributes interpolate server values inline.
requireAuthA bare attribute means true by being there — presence is the value.
{# note #}A build-only comment: never emitted, unlike an HTML comment.
Components
<component tag="…"> declares a reusable tag. Props are typed inputs — any Kotlin type, including your own classes and generics — and they're in scope in the body and its server script. Callers pass them with the same binding syntax: <prayer-card :prayer="p"/>.
<style scoped> is isolated to the component: the compiler stamps a marker attribute and rewrites each selector to require it.<style> stays global.<component tag="prayer-card">
<prop name="prayer" type="myapp.PrayerRow" required/>
<article class="prayer-card">
<span class="author">{ prayer.profileName }</span>
<if prayer.answered>
<span class="status-pill">Answered</span>
</if>
<h3><a href="{ prayer.href }">{ prayer.title }</a></h3>
</article>
<style scoped>
.prayer-card { border: 1px solid var(--divider); }
.status-pill { color: var(--forest); }
</style>
</component>Layouts & overrides
A layout is a component with a <slot/>: the caller's children fill it. Real sites wrap every page in one shell component that owns the document element, head metadata, header, and footer — the page supplies the content.
<component tag="a"> replaces every anchor on the site.html: namespace always resolves to the native element — how an override emits the real tag without recursing.page, component, if, for, island, slot) are protected.<component tag="site-layout">
<prop name="title" type="String" required/>
<html lang="en">
<head><title>{ title }</title></head>
<body>
<site-head/>
<slot/>
<site-foot/>
</body>
</html>
</component>Keep exploring
What BML is, what makes it powerful, and how to use it.
Islands and interactivity without hand-written client code.
The typed GraphQL data plane and client-managed auth.
The compiler, the IDE plugin, the dev loop, and one-process deploys.
Ready for the reference? Read the BML documentation