Skip to content

Example: Counter with Dark Mode

A classic counter that demonstrates reactive state, button variants, and runtime theme switching — all in one self-contained page.


counter.py
import fluxui as ui

app = ui.App("Counter")


@app.page("/")
def home(session):
    count      = ui.state(0)           # (1) per-session reactive integer
    dark_mode  = ui.state(False)

    async def toggle_theme(v: bool) -> None:   # (2) async on_change handler
        dark_mode.set(v)
        await session.set_theme("dark" if v else "light")

    # ── Top bar ──────────────────────────────────────────────────────────
    with ui.Row(justify="space-between", align="center"):
        ui.Heading("Counter Demo", level=1)
        ui.Switch("Dark mode", checked=dark_mode, on_change=toggle_theme)

    # ── Counter card ─────────────────────────────────────────────────────
    with ui.Card(title="Click the buttons", description="Each click updates only the metric"):
        with ui.Row(gap="var(--s-3)", align="center", justify="center"):
            ui.Button(
                "−",
                variant="secondary",
                on_click=lambda: count.update(lambda v: v - 1),
            )
            ui.Metric(                 # (3) only this component re-renders on count change
                label="Count",
                value=lambda: str(count.value),
                delta=lambda: f"+{count.value}" if count.value > 0
                              else (str(count.value) if count.value < 0 else ""),
            )
            ui.Button("+", on_click=lambda: count.update(lambda v: v + 1))

        ui.Button("Reset", variant="ghost", on_click=lambda: count.set(0))

    # ── Progress bar ─────────────────────────────────────────────────────
    with ui.Card(title="Progress"):
        ui.Progress(
            lambda: max(0.0, min(100.0, count.value * 10.0)),  # (4) clamp 0-100
            label="10 × count",
        )

    # ── Status badge ─────────────────────────────────────────────────────
    def badge_variant():                        # (5) computed variant
        if count.value > 0:   return "success"
        if count.value < 0:   return "danger"
        return "neutral"

    with ui.Row(gap="var(--s-2)"):
        ui.Text("Status:", variant="label")
        ui.Badge(
            lambda: "Positive" if count.value > 0
                    else ("Negative" if count.value < 0 else "Zero"),
            variant=badge_variant,
        )

    ui.Alert(
        "Try opening this page in two browser tabs — each tab gets its own counter.",
        variant="info",
    )


if __name__ == "__main__":
    app.run(port=8000)
  1. ui.state(0) creates a ReactiveVar[int] scoped to this session.
  2. async def toggle_theme — FluxUI calls async handlers natively. session.set_theme() is a coroutine that sends a theme-switch message over the WebSocket.
  3. ui.Metric(value=lambda: ...) — the lambda is called inside the metric's render context, so only the metric subscribes to count. Clicking a button does not re-render the buttons themselves.
  4. max(0.0, min(100.0, ...)) clamps the progress value to the 0–100 range expected by ui.Progress.
  5. Reactive badge variant — the lambda is evaluated during render, so it subscribes to count and re-renders when the value crosses zero.

Run it

pip install fluxui
python counter.py

Open http://127.0.0.1:8000.