Skip to content

Example: Multi-Page SPA

Three pages connected by a shared NavMenu. Navigation is client-side — no full page reload.


multi_page.py
import fluxui as ui

app = ui.App("Multi-Page App")


def nav(session, active: str):
    """Shared navigation bar used on every page."""
    with ui.NavMenu(brand="MyApp"):
        ui.NavItem("Home",    href="/",        active=active == "home",    icon="🏠")
        ui.NavItem("About",   href="/about",   active=active == "about",   icon="â„šī¸")
        ui.NavItem("Contact", href="/contact", active=active == "contact", icon="âœ‰ī¸")


# ── Page: Home ────────────────────────────────────────────────────────────────

@app.page("/")
def home(session):
    nav(session, "home")

    with ui.Column(gap="var(--s-6)"):
        ui.Heading("Welcome to MyApp", level=1)
        ui.Text(
            "FluxUI is a reactive Python web UI framework. "
            "Navigate between pages using the links above — no page reload occurs."
        )

        with ui.Grid(cols=3, gap="var(--s-4)"):
            ui.Metric(label="Users",   value="1,234", delta="+12%")
            ui.Metric(label="Revenue", value="$56k",  delta="+5%")
            ui.Metric(label="Uptime",  value="99.9%")

        with ui.Row(gap="var(--s-3)"):
            ui.Button("Learn more", variant="primary",
                      on_click=lambda: session.navigate("/about"))
            ui.Button("Contact us", variant="secondary",
                      on_click=lambda: session.navigate("/contact"))


# ── Page: About ───────────────────────────────────────────────────────────────

@app.page("/about")
def about(session):
    nav(session, "about")

    with ui.Column(gap="var(--s-6)"):
        ui.Heading("About FluxUI", level=1)

        with ui.Tabs():
            with ui.Tab("Overview"):
                ui.Text(
                    "FluxUI uses WebSockets to deliver targeted HTML patches "
                    "when state changes. Only the components that actually changed "
                    "are updated — the rest of the page is untouched."
                )
                ui.Code(
                    "import fluxui as ui\n\n"
                    "app = ui.App('My App')\n\n"
                    "@app.page('/')\n"
                    "def home(session):\n"
                    "    count = ui.state(0)\n"
                    "    ui.Button('+', on_click=lambda: count.set(count.value + 1))\n"
                    "    ui.Text(lambda: str(count.value))\n\n"
                    "app.run(port=8000)",
                    language="python",
                )

            with ui.Tab("Architecture"):
                ui.Markdown("""
## Architecture

1. **Shell** — A minimal HTML page is served on the first request.
   It contains only the scripts and an empty `#flux-root` div.
2. **WebSocket** — The browser immediately opens a WS connection to `/ws`.
3. **Full render** — The server renders the page and sends the full HTML over WS.
4. **Events** — User interactions send `event` messages.
5. **Patches** — Only changed components are re-rendered and sent as patches.
                """)

            with ui.Tab("License"):
                ui.Text("FluxUI is released under the MIT License.", variant="muted")
                ui.Link(
                    "View license on GitHub",
                    href="https://github.com/Minato3000/FluxApp/blob/main/LICENSE",
                    external=True,
                )


# ── Page: Contact ─────────────────────────────────────────────────────────────

@app.page("/contact")
def contact(session):
    nav(session, "contact")

    name    = ui.state("")
    email   = ui.state("")
    message = ui.state("")
    sent    = ui.state(False)
    errors  = ui.dict_state({})

    def submit():
        errs = {}
        if not name.peek().strip():
            errs["name"] = "Name is required"
        if "@" not in email.peek():
            errs["email"] = "Enter a valid email address"
        if len(message.peek().strip()) < 10:
            errs["message"] = "Message must be at least 10 characters"
        errors.value.clear()
        errors.value.update(errs)
        if not errs:
            sent.set(True)

    with ui.Column(gap="var(--s-6)"):
        ui.Heading("Contact Us", level=1)

        if sent.value:
            ui.Alert("Thanks! We'll get back to you shortly.", variant="success")
            ui.Button("Send another message", variant="ghost",
                      on_click=lambda: (sent.set(False), name.set(""),
                                        email.set(""), message.set("")))
        else:
            with ui.Card(title="Send us a message"):
                ui.TextInput(
                    label="Full name",
                    value=name,
                    on_change=name.set,
                    error=lambda: errors.value.get("name", ""),
                )
                ui.TextInput(
                    label="Email",
                    value=email,
                    on_change=email.set,
                    error=lambda: errors.value.get("email", ""),
                )
                ui.Textarea(
                    label="Message",
                    value=message,
                    on_change=message.set,
                    rows=5,
                    error=lambda: errors.value.get("message", ""),
                )
                ui.Button("Send message", on_click=submit)


if __name__ == "__main__":
    app.run(port=8000)

Run it

pip install fluxui
python multi_page.py

Click the nav links to navigate between pages. Notice the URL changes in the address bar but the nav bar stays in place — FluxUI patches only the page content.