Summary#
app.Ui is where you hand the platform your own components for the system surfaces it would otherwise draw a
bare fallback for — the "something happened, here's the app's branded page" moments. It is a singleton — one UI
configuration per app — and it groups these overrides under one Ui block, so an app opts into each surface it wants to
own and inherits the platform default for the rest.
app.Ui = new AppUi {
ConnectionSurface = OfflineOverlay, // shown when the server drops mid-session
NotFoundSurface = NotFoundPage, // shown (HTTP 404) when no route matches the address
ForbiddenSurface = ForbiddenPage, // shown when a signed-in user is refused a page (403)
ErrorSurface = ErrorPage, // shown when a page fails to load unexpectedly
};Signature#
app.Ui = new AppUi {
ConnectionSurface = OfflineOverlay, // the connection-loss overlay
NotFoundSurface = NotFoundPage, // the not-found (404) page
ForbiddenSurface = ForbiddenPage, // the forbidden (403) page
ErrorSurface = ErrorPage, // the error page
};app.Ui is a single value, not a list. Each member names a component by name — the same name you gave it in its
component X { … } declaration.
Description#
An AppUi has these members (each optional — omit one, or omit app.Ui entirely, to keep the platform default):
ConnectionSurface— the component the platform mounts, as a fixed overlay over the current page, when the browser loses its link to the server. It reads the Connection ambient (Connection.State,Connection.Attempts) and callsConnection.Retry()/Connection.Reload(). Because it has to render with the server gone, it must be self-contained — built from the component built-in elements, with no child components to fetch and no data to load. See Connection for the full surface and its constraints.NotFoundSurface— the page the platform serves, with HTTP status 404, when a visitor hits an address the app doesn't route. The server sends your app's shell with this component as the page (so the 404 keeps the correct status for crawlers and monitoring, but the body is your branded page instead of plain text) and the client renders it. Unlike the connection surface, a 404 means the server answered, so this page may use anything the app has. Mark it[AllowAnonymous]so a bad URL renders it for anyone rather than bouncing a signed-out visitor to login.ForbiddenSurface— the page shown when a signed-in user is refused a page they aren't permitted to see (a 403). The platform mounts it in place of the built-in access-denied surface.ErrorSurface— the page shown when a page fails to load for an unexpected reason, in place of the built-in "couldn't load" surface.
Three more members tune the busy indicator — the automatic spinner the platform shows while an action is in flight
(see Pending for the full surface, including the Pending ambient):
PendingIndicator— your own component for the global busy affordance, in place of the built-in top progress bar.PendingDelayMs— how long an action must run before the indicator appears (so an instant action never flashes one);0uses the platform default.PendingMinShowMs— once shown, the minimum time the indicator stays up, so it can't blink off;0uses the default.
The forbidden and error surfaces render at boot-failure time — a refusal or a failure that can happen before the app
has a working session, or while the server is unreliable. So the platform inlines their trees into the page up
front, and the client renders them with no further request. That is what makes them work when nothing else does — and it
puts the same constraint on them as the connection surface: they must be self-contained, built from the
component built-in elements (with [Composable] children bundled), carrying no data. Offer a way out with plain
Links (sign in as a different account, go home). Mark each [AllowAnonymous].
Each nominated component must exist, and none is a routed page — the platform serves or mounts each by name for its
occasion, so none needs a [Page("…")].
app.Ui is a singleton: an app declares it once. Removing a member reverts that surface to the platform default.
Sign-in is deliberately not part of this block — it is a real flow, declared via
app.AuthBootstrap { LoginPage = … }, not a passive surface.
Examples#
Declare the surfaces, then nominate them:
[AllowAnonymous]
component OfflineOverlay() {
action Retry() { Connection.Retry(); }
render {
if (Connection.State == ConnState.Lost) {
Row(align: Align.Center, justify: Justify.Center, position: Position.Fixed, inset: 0, bg: "#1a1a1a") {
Text("Can't reach the server");
}
}
}
}
[AllowAnonymous]
component NotFoundPage() {
render {
Row(align: Align.Center, justify: Justify.Center, minH: "100vh", bg: Colors.Surface) {
Stack(gap: 3, align: Align.Center) {
Text("Page not found");
Link(href: "/") { Text("Go home"); }
}
}
}
}
app.Ui = new AppUi {
ConnectionSurface = OfflineOverlay,
NotFoundSurface = NotFoundPage,
};See also#
- Connection — the
Connectionambient the connection surface reads, and its offline-render constraint. - Pending — the automatic busy indicator the
Pending*members tune, and thePendingambient. - component — components,
renderblocks, and the built-in elements a surface is built from.