Skip to content
scalive
Menu
ConnectingLiveReconnectingOffline
GitHub

Flash messages, page titles, and connection status

Before you start

You need a LiveView with rendered state. The title section also requires a root layout that renders liveTitle. Review Lifecycle and connection behavior before attaching effects to lifecycle hooks.

Render keyed flash

Define a stable FlashKind, update it through the phase context, and render only that key with flash. Saved is FlashKind("saved"). Put the message from handleMessage:

Source
scala
case Msg.Save =>
  documents.save(model.title) *> ctx.flash.put(Saved, "Changes saved.").as(model)

Then render that key in view:

Source
scala
flash(Saved) { message =>
  p(role := "status", aria.live := "polite", message)
},

put replaces the value for the same key. clear removes that key, and clearAll removes every flash value owned by the current lifecycle. Prefer a specific key when independent notices can coexist.

Flash is lifecycle state, not a substitute for model data. Use it for brief feedback such as a save result or navigation notice. Keep values free of secrets, and put information that must survive arbitrary reloads in durable application state instead.

Derive the page title

Override pageTitle on the routed root LiveView and derive it from the same model exposed as a signal to view:

Source
scala
override def pageTitle(model: Model): Option[String] =
  Some(model.title)

The root layout's liveTitle renders that title during disconnected HTTP rendering. Connected model changes send title metadata so the client updates document.title. Returning None or a blank title uses the root layout's fallback.

Only the root LiveView owns the document title. A nested LiveView can project a title-like value for its own interface, but its pageTitle result does not replace the containing document's title. The embedded lifecycle example makes that boundary visible by displaying its projection inside the example.

Show connection state

Render both states and let declarative connection bindings switch them in the browser:

Source
scala
div(
  span(connection.visibleWhenConnected, "Connected"),
  span(connection.visibleWhenDisconnected, "Offline")
),

The disconnected state is visible in static HTML by default. Once the LiveSocket connects, the client hides it and reveals the connected state; it reverses those attributes if the transport drops. Include text or another non-color cue so the state remains understandable without color perception.

Disable controls while offline

A control that needs the server should not look usable while the page is offline. Render it disabled, then let the connection bindings run JS commands that enable it once the page connects and disable it again if the connection drops:

Source
scala
button(
  typ      := "button",
  disabled := true,
  connection.onConnect(JS.removeAttribute("disabled")),
  connection.onDisconnect(JS.setAttribute("disabled" -> "")),
  on.click(Msg.Save),
  "Save"
)

The button stays disabled in the static HTML and until the first connection, so an early click cannot be lost. The client applies the bindings again after each reconnect, and attributes changed by JS commands survive later server patches.

Put connection bindings in the root LiveView. When the connection drops, the client tears down nested LiveViews before it runs the disconnected commands, so bindings inside a nested LiveView do not react to the outage.

Keep after-render effects observational

Declare static hooks once on the LiveView:

Source
scala
override def hooks: LiveHooks[Msg, Model] =
  LiveHooks.afterRender { (model, ctx) =>
    ctx.connection match
      case Connection.Connected(capabilities) =>
        capabilities.client.push(RenderedEvent, Rendered(model.title))
      case Connection.Disconnected => ZIO.unit
  }

An after-render hook observes a render that already succeeded and cannot change the model. When an effect must produce the next state, use handleMessage, handleParams, an async completion, or a subscription message instead. Lifecycle hooks describes the rest of the after-render contract.

Exercise the behavior

Use the lifecycle example to put and clear flash, change the projected title, inspect connected mount state, and reset the nested LiveView. Its source keeps the model, messages, flash key, title projection, and after-render hook together.

For the full lifecycle sequence and reconnect model, read Lifecycle, state ownership, and reconnects.