Skip to content
Brew

Brew components

A Brew component is a subclass of org.konaframework.brew.Component. Public fields (or Kotlin properties with backing fields) are state. Public methods are actions. The class returns HTML through template(), render(), or a convention Crema view.

Override template() with Crema source. Best for small widgets.

class Counter : Component() {
var count = 0
fun increment() { count++ }
override fun template() = """
<div>
<button wire:click="increment">+</button>
<span>{{ count }}</span>
</div>
""".trimIndent()
}

Omit template() and either return view("brew.todos") from render(), or rely on the convention path views/brew/<name>.crema.html.

class Todos : Component() {
var draft = ""
var items = mutableListOf<String>()
fun add() {
if (draft.isNotBlank()) {
items.add(draft.trim())
draft = ""
}
}
// Convention: views/brew/todos.crema.html when render() / template() are null
}
{{-- views/brew/todos.crema.html — single root element required --}}
<div>
<form wire:submit="add">
<input wire:model="draft" />
<button type="submit">Add</button>
</form>
<ul>
@foreach(items as item)
<li wire:key="todo-{{ loop.index }}">{{ item }}</li>
@endforeach
</ul>
</div>

render() may return:

Return Meaning
null Use template(), else convention view brew.<name>
BrewView from view(name, data) / inline(source, data) Named or inline Crema
String Raw HTML (still must have a single root element)

By default BrewServiceProvider adds the app.brew package location. Class app.brew.Counter is named counter; nested packages become dotted names (app.brew.posts.CreatePost → posts.create-post).

API Role
Brew.discover("app.brew") Scan a package
Brew.addLocation("com.acme.ui") Extra package root
Brew.addNamespace("admin", "app.admin.brew") Resolve admin::users
Brew.component("counter", Counter::class.java) Explicit registration
@BrewComponent("explicit-name") Pin the discovered name

Namespaces use ns::name in tags: <brew:admin::users />.

<brew:counter />
<brew:counter :start="3" title="Demo" />
@brew('counter', ['start' => 3])
  • Static attributes become string props / mount args.
  • :attr="expr" evaluates the Crema expression.
  • Kebab-case attributes map to camelCase (:user-id → userId).

From code:

Brew.mount("counter", mapOf("start" to 3))
Brew.exists("counter") // true when registered / discoverable

Inside a running component:

id() // unique instance id
name() // registered name, e.g. "counter"
Situation Exception / behaviour
Unknown name ComponentNotFoundException
More than one root HTML element MultipleRootElementsException
Empty / no root tag RootTagMissingFromViewException
Client calls a non-action / lifecycle method Method-not-found / lifecycle guard
Terminal window
./grind make:brew counter # single-file in app.brew
./grind make:brew posts.create --class # class + views/brew/posts/create.crema.html
./grind make:brew counter --lang=java
./grind make:brew counter --test
  • Properties — state API and security annotations
  • Actions — calling methods from the browser
  • Templates — assets and Crema surface