Typed counter
A LiveViewtrait LiveView[Msg, Model]Defines a server-rendered view with typed messages and model state. with a count model and Decrement, Reset, and Increment
messages.
final class CounterExample extends LiveView[CounterExample.Msg, CounterExample.Model]:
import CounterExample.*
def mount(ctx: MountContext): Task[Model] =
ZIO.succeed(Model(count = 0))
def handleMessage(model: Model, ctx: MessageContext) =
case Msg.Decrement => ZIO.succeed(model.copy(count = model.count - 1))
case Msg.Increment => ZIO.succeed(model.copy(count = model.count + 1))
case Msg.Reset => ZIO.succeed(model.copy(count = 0))
def view(model: Signal[Model]): HtmlElement[Msg] =
div(
cls := "docs-counter",
p(
cls := "docs-counter-status",
role := "status",
aria.live := "polite",
aria.atomic := true,
span(
cls := "docs-counter-model-label",
span("Current model"),
code("count =")
),
strong(cls := "docs-counter-value", model.map(_.count.toString))
),
fieldSet(
dataAttr("example-controls") := "",
legend(cls := "docs-visually-hidden", "Counter controls"),
button(
typ := "button",
cls := "docs-counter-secondary",
on.click(Msg.Decrement),
"Decrease"
),
button(
typ := "button",
cls := "docs-counter-secondary",
on.click(Msg.Reset),
"Reset"
),
button(
typ := "button",
cls := "docs-counter-increment",
on.click(Msg.Increment),
"Increase"
)
)
)
end CounterExample
object CounterExample:
enum Msg:
case Decrement, Increment, Reset
final case class Model(count: Int)View source (documentation/site/src/scalive/docs/examples/CounterExample.scala:8-63)
Live result
ConnectedReconnectingRead-onlyCurrent modelcount =0
Disconnected. Controls resume after reconnection.
LiveView observability
Interaction inspector
See how actions in the live result above travel through typed messages, server state, protocol frames, and DOM updates.
Reconnect to inspect live interactions
The live result and its inspector resume when the connection returns.
Related guidance: start with models, typed messages, and rendering.