Skip to content

Routing

FluxUI has a built-in SPA router. Navigating between pages does not reload the browser — it sends a navigate event over the WebSocket and the server renders the new page into the existing shell.


Declaring Pages

Use the @app.page(path) decorator to register a page handler:

import fluxui as ui

app = ui.App("My App")

@app.page("/")
def home(session):
    ui.Heading("Home", level=1)
    ui.Link("Go to about", href="/about")

@app.page("/about")
def about(session):
    ui.Heading("About", level=1)
    ui.Text("FluxUI is a reactive Python web UI framework.")

app.run(port=8000)

Path Parameters

Declare dynamic segments with {name} syntax. They are passed as keyword arguments to the page function:

@app.page("/user/{id}")
def user_profile(session, id: str):
    user = load_user(id)
    if user is None:
        ui.Alert(f"User {id!r} not found.", variant="danger")
        return

    ui.Heading(user.name, level=1)
    ui.Text(user.email)

@app.page("/product/{category}/{slug}")
def product_detail(session, category: str, slug: str):
    product = load_product(category, slug)
    ui.Heading(product.title, level=1)
    ui.Text(product.description)

Programmatic Navigation

Use session.navigate(url) from within any callback to change pages programmatically:

@app.page("/login")
def login(session):
    username = ui.state("")
    password = ui.state("")
    error    = ui.state("")

    async def handle_login():
        if authenticate(username.peek(), password.peek()):
            await session.navigate("/dashboard")
        else:
            error.set("Invalid credentials")

    with ui.Card(title="Sign in"):
        ui.TextInput(label="Username", value=username, on_change=username.set)
        ui.TextInput(label="Password", value=password, on_change=password.set)
        if error.value:
            ui.Alert(error, variant="danger")
        ui.Button("Sign in", on_click=handle_login)

Replace history entry

Pass replace=True to replace the current history entry instead of pushing a new one. The user's "back" button will skip the replaced page:

await session.navigate("/dashboard", replace=True)

SPA Behaviour

  • All navigation happens client-side — the browser never does a full page load after the initial shell.
  • The URL in the address bar updates correctly via the History API.
  • The browser "back" and "forward" buttons work as expected.
  • Deep links work: opening http://localhost:8000/user/42 directly navigates to the /user/{id} page.

Accessing the Current Route

The current route is available on the session object:

@app.page("/dashboard")
def dashboard(session):
    current = session.current_route   # e.g. "/dashboard"

    with ui.NavMenu(brand="MyApp"):
        ui.NavItem("Home",      href="/",          active=lambda: session.current_route == "/")
        ui.NavItem("Dashboard", href="/dashboard", active=lambda: session.current_route == "/dashboard")

404 Handling

If a route is not found, FluxUI renders a built-in 404 page. You can override it with a catch-all page:

@app.page("/404")
def not_found(session):
    with ui.Column(align="center", justify="center"):
        ui.Heading("404 — Page not found", level=1)
        ui.Text("The page you are looking for does not exist.")
        ui.Button("Go home", on_click=lambda: session.navigate("/"))

Note

Custom 404 routing via the FluxUI Router is planned for v0.2. For now, handle unknown paths inside your page functions by checking parameters.


Per-Page Component Overrides

You can scope component overrides to a single page using the overrides= kwarg:

class LargeButton:
    """A big, full-width button used only on the landing page."""
    def render(self, label, *, on_click=None, **kwargs):
        ...

@app.page("/", overrides={"Button": LargeButton})
def landing(session):
    ui.Button("Get started")   # uses LargeButton here only