Live preview
Try #/dashboard, #/users, then drill into a user (e.g. #/users/ada). Browser back / forward and bookmarks all work.
One <streaming-ui-script> tag renders a four-page UI
synced to the URL hash. Click the nav, use deep links, hit the browser
back button — it all stays in sync, with zero framework lock-in.
Try #/dashboard, #/users, then drill into a user (e.g. #/users/ada). Browser back / forward and bookmarks all work.
The nav stays visible across every page;
main is a Routes(...) outlet that swaps in
the page that matches the current URL. Path parameters land in
params, and $route is reactive everywhere
else.
$users = [
{id: "ada", name: "Ada Lovelace", role: "Founding engineer", joined: "2019-04-02"},
{id: "grace", name: "Grace Hopper", role: "Compiler researcher", joined: "2020-01-15"},
{id: "lin", name: "Lin-Manuel", role: "Product designer", joined: "2021-08-21"},
{id: "ken", name: "Ken Thompson", role: "Platform engineer", joined: "2018-11-04"}
]
$visits = 0
$lastEdited = "—"
root = Stack([header, nav, main])
header = Card([
CardHeader("Acme console", "Routing demo · current path: " + $route),
Stack([
Tag("$route = " + $route, "compass", "sm", "info"),
Tag("visits = " + $visits, "eye", "sm", "neutral")
], "row", "xs")
])
nav = Card([
Stack([
NavLink("Home", "/", "ghost", true, "house"),
NavLink("Dashboard", "/dashboard", "ghost", false, "chart-pie"),
NavLink("Users", "/users", "ghost", false, "users"),
NavLink("Settings", "/settings", "ghost", false, "gear")
], "row", "s")
])
main = Routes([
Route("/", homePage),
Route("/dashboard", dashboardPage),
Route("/users", usersListPage),
Route("/users/:id", userDetailPage),
Route("/settings", settingsHomePage),
Route("/settings/*", settingsAreaPage),
Route("*", notFoundPage)
], "/")
homePage = Card([
CardHeader("Welcome", "A multi-page UI in one Streaming UI Script program"),
Markdown("Pick a section above, or jump straight in:"),
Buttons([
Button("Open dashboard", Action([@Navigate("/dashboard")]), "primary"),
Button("Browse users", Action([@Navigate("/users")]), "secondary"),
Button("Open Ada", Action([@Navigate("/users/ada")]), "ghost")
])
])
dashboardPage = Card([
CardHeader("Dashboard", "Reactive across routes"),
Stack([
StatCard("Users", "" + @Count($users), "up", "+2 this month"),
StatCard("Visits", "" + $visits, "up", "this session"),
StatCard("Last edit", $lastEdited)
], "row", "m", "stretch", "start", true),
Buttons([
Button("Track a visit", Action([@Set($visits, $visits + 1)]), "primary"),
Button("Back to home", Action([@Navigate("/")]), "ghost")
])
])
usersListPage = Card([
CardHeader("Users", "Click a row to deep-link into the detail page"),
Stack([@Each($users, "u", userRow)])
])
userRow = Card([
Stack([
Stack([
TextContent(u.name, "body-heavy"),
TextContent(u.role + " · joined " + u.joined, "small", "muted")
]),
Buttons([Button("Open", Action([@Js("ctx.host.navigate('/users/' + ctx.args.id)", {id: u.id})]), "ghost")])
], "row", "m", "center", "between")
], "outlined")
userDetailPage = Card([
CardHeader("User " + params.id, "Deep-linkable detail page"),
detailRow,
Buttons([
Button("Back to users", Action([@Navigate("/users")]), "ghost"),
Button("Mark edited", Action([@Set($lastEdited, params.id), @Navigate("/dashboard")]), "primary")
])
])
detailRow = Markdown("Path parameter: **" + params.id + "** · open URL: `#/users/" + params.id + "`")
settingsHomePage = Card([
CardHeader("Settings", "Wildcard nested route below"),
Stack([
NavLink("Profile", "/settings/profile", "pill"),
NavLink("Notifications", "/settings/notifications", "pill"),
NavLink("Security", "/settings/security", "pill")
], "row", "s"),
TextContent("Pick a sub-section above — it's matched by /settings/*.", "small", "muted")
])
settingsAreaPage = Card([
CardHeader("Settings · " + params._, "Sub-section captured by wildcard"),
TextContent("params._ = " + params._),
Buttons([Button("Back to settings", Action([@Navigate("/settings")]), "ghost")])
])
notFoundPage = Callout("warning", "Not found", "No page matches " + $route + ". Use the nav above or go back to /.")
$route state is exposed everywhere, and the routing
section is part of the generated system prompt by default.
nav is rendered once at the top of root so
it stays visible across every page. NavLinks reflect
data-active="true" automatically — the home link uses
exact=true so it doesn't light up on every path.
Routes(...) picks exactly one Route per
render based on window.location.hash. The default
("/") and the catch-all Route("*", …)
guarantee something is always rendered.
userDetailPage and settingsAreaPage,
the params loop variable is automatically injected by
the evaluator — no extra wiring needed. params._ holds
the wildcard remainder.
@Navigate("/path") is the declarative way to move; the
imperative el.navigate("/path") works from JS (see the
Open button in the users list, which captures
u.id per row).
$visits counter shows that the rest of the
state model keeps working untouched — routing is additive, not
intrusive.