Routing
Navigation and routing in Hypen applications
Routing
Hypen uses a declarative routing model. You define your routes in the DSL, and your module manages the navigation state. This approach works across all platforms — the same route definitions render via browser history on web, native navigation stacks on mobile, or state-driven routing in remote UI.
Basic Setup
A route table is declared with Router and Route components:
module App {
Router(initialRoute: "/home") {
Route(path: "/home") {
HomePage()
}
Route(path: "/about") {
AboutPage()
}
Route(path: "/settings") {
SettingsPage()
}
}
}The module manages which route is active via state.location:
import { app } from "@hypen-space/core";
export default app
.defineState({
location: "/home",
params: {},
})
.onAction<{ to: string }>("navigate", ({ state, action }) => {
state.location = action.payload!.to;
})
.build();Navigating Between Routes
Trigger navigation from buttons or links using actions:
// Simple navigation
Button { Text("Go to Settings") }
.onClick(@actions.navigate, { to: "/settings" })
// Navigation bar
Row {
Button { Text("Home") }
.onClick(@actions.navigate, { to: "/home" })
.color("${state.location == '/home' ? '#3B82F6' : '#6B7280'}")
Button { Text("About") }
.onClick(@actions.navigate, { to: "/about" })
.color("${state.location == '/about' ? '#3B82F6' : '#6B7280'}")
Button { Text("Settings") }
.onClick(@actions.navigate, { to: "/settings" })
.color("${state.location == '/settings' ? '#3B82F6' : '#6B7280'}")
}
.gap(16)
.padding(16)Route Parameters
Use :param in the path pattern to capture dynamic segments:
Router {
Route(path: "/users/:id") {
UserProfile(userId: ${state.params.id})
}
Route(path: "/posts/:postId/comments/:commentId") {
CommentView(
postId: ${state.params.postId},
commentId: ${state.params.commentId}
)
}
}In your module, handle navigation that includes parameters:
.onAction<{ userId: string }>("viewUser", ({ state, action }) => {
state.location = `/users/${action.payload!.userId}`;
state.params = { id: action.payload!.userId };
})Route Guards
Guards run before a route is entered. They can allow, redirect, or block navigation:
Route(path: "/admin", guard: @actions.requireAdmin) {
AdminDashboard()
}
Route(path: "/account", guard: @actions.requireAuth) {
AccountPage()
}.onAction("requireAuth", async ({ state }) => {
const isLoggedIn = await checkSession();
if (!isLoggedIn) {
state.location = "/login";
}
})
.onAction("requireAdmin", async ({ state }) => {
const user = await getUser();
if (!user?.isAdmin) {
state.location = "/home";
}
})Fallback Route
Handle unknown paths with Route.fallback:
Router(initialRoute: "/home") {
Route(path: "/home") { HomePage() }
Route(path: "/about") { AboutPage() }
Route.fallback {
Column {
Text("404")
.fontSize(64)
.fontWeight("bold")
Text("Page not found")
.color("#6B7280")
Button { Text("Go Home") }
.onClick(@actions.navigate, { to: "/home" })
}
.horizontalAlignment("center")
.verticalAlignment("center")
.flex(1)
}
}Nested Routing
Routes can contain child routers for tabbed or sectioned layouts:
Route(path: "/settings") {
Column {
// Tab bar
Row {
Button { Text("Profile") }
.onClick(@actions.navigate, { to: "/settings/profile" })
Button { Text("Security") }
.onClick(@actions.navigate, { to: "/settings/security" })
Button { Text("Notifications") }
.onClick(@actions.navigate, { to: "/settings/notifications" })
}
.gap(8)
.padding(16)
.borderBottom("1px solid #E5E7EB")
// Nested route content
Router {
Route(path: "/settings/profile") {
ProfileSettings()
}
Route(path: "/settings/security") {
SecuritySettings()
}
Route(path: "/settings/notifications") {
NotificationSettings()
}
}
}
}Complete Example
Here's a full app with a navigation bar, multiple routes, and parameterized routes:
module App {
Column {
// Navigation bar
Row {
Text("MyApp")
.fontSize(20)
.fontWeight("bold")
.color("#111827")
Spacer()
Row {
Button { Text("Home") }
.onClick(@actions.navigate, { to: "/" })
Button { Text("Users") }
.onClick(@actions.navigate, { to: "/users" })
Button { Text("Settings") }
.onClick(@actions.navigate, { to: "/settings" })
}
.gap(12)
}
.padding(16, 24)
.horizontalAlignment("center")
.borderBottom("1px solid #E5E7EB")
// Route content
Router(initialRoute: "/") {
Route(path: "/") {
Center {
Text("Welcome Home")
.fontSize(32)
}
.flex(1)
}
Route(path: "/users") {
Column {
Text("Users")
.fontSize(24)
.fontWeight("bold")
.padding(24)
ForEach(items: @state.users, key: "id") {
Button {
Row {
Avatar(@item.avatar)
.size(40)
Text(@item.name)
.fontSize(16)
}
.gap(12)
.verticalAlignment("center")
}
.onClick(@actions.viewUser, { userId: @item.id })
.padding(12, 24)
}
}
}
Route(path: "/users/:id") {
UserProfile(userId: ${state.params.id})
}
Route(path: "/settings", guard: @actions.requireAuth) {
SettingsPage()
}
}
}
.fillMaxSize(true)
}import { app } from "@hypen-space/core";
export default app
.defineState({
location: "/",
params: {},
users: [
{ id: "1", name: "Alice", avatar: "/alice.jpg" },
{ id: "2", name: "Bob", avatar: "/bob.jpg" },
],
})
.onAction<{ to: string }>("navigate", ({ state, action }) => {
state.location = action.payload!.to;
state.params = {};
})
.onAction<{ userId: string }>("viewUser", ({ state, action }) => {
state.location = `/users/${action.payload!.userId}`;
state.params = { id: action.payload!.userId };
})
.onAction("requireAuth", async ({ state }) => {
// Check authentication before entering route
const session = await checkSession();
if (!session) state.location = "/login";
})
.build();How It Works
Under the hood, Router and Route are passthrough components in the engine. This means:
- The engine preserves their props (
path,guard, etc.) and expands their children - No template transformation happens on Router/Route themselves
- The platform renderer reads
state.location, matches it against Route paths, and shows the matching route's children
This design keeps the engine platform-agnostic. The same route definitions work with:
- Web: Browser
history.pushState - iOS:
UINavigationController - Android: Jetpack Navigation
- Remote UI: Server-side state changes
Next Steps
- Control Flow — ForEach, When, and If
- State & Modules — Managing navigation state
- Components — All built-in components