Bosca / BML Read the docs

The language

Markup with a type system

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

A route is a file

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.
  • Server data comes from <script server provides="…"> — the block's last expression binds into scope.
group.bml
<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

Six marks to learn

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.

requireAuth

A bare attribute means true by being there — presence is the value.

{# note #}

A build-only comment: never emitted, unlike an HTML comment.

Components

Typed props, scoped styles

<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"/>.

  • A <style scoped> is isolated to the component: the compiler stamps a marker attribute and rewrites each selector to require it.
  • Scoped CSS is emitted once per page no matter how many times the component renders; a plain <style> stays global.
  • Slotted content is scoped to the caller — it's the caller's markup.
prayer-card.bml
<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

The shell is just a component

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.

  • Most built-in HTML tags are overrideable: <component tag="a"> replaces every anchor on the site.
  • The html: namespace always resolves to the native element — how an override emits the real tag without recursing.
  • Special tags (page, component, if, for, island, slot) are protected.
site.bml
<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

More on BML

Ready for the reference? Read the BML documentation