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¶
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.