Cookbook

Short, repeatable recipes for everyday Wijjit tasks. Each entry links to a working example so you can copy-paste with confidence.

Form validation with inline errors

Source: examples/advanced/login_form.py

  1. Bind text inputs to state keys (id="username"/"password").

  2. Use @app.on_action("login") to validate fields; populate state.form_errors with error messages.

  3. Render errors next to inputs using template conditionals:

    {% if state.form_errors.username %}
      {% text class="error" %}{{ state.form_errors.username }}{% endtext %}
    {% endif %}
    

    For a transient toast instead of inline text, call app.notify(state.form_errors["username"], severity="error") from your action handler.

  4. Clear errors when the form succeeds or users edit the fields.

Persisting data between sessions

Source: tutorial todo app (see Tutorial: Persistent Todo App)

  1. Load data from disk when constructing Wijjit; store a next_id counter in state.

  2. Define helper persist() that writes state["todos"] back to JSON.

  3. Call persist() inside @app.on_action handlers and/or state.watch("todos", ...) so every change is saved.

  4. Keep file paths under Path.home() or configurable via environment variables.

Multi-view navigation with keyboard shortcuts

Source: examples/widgets/centered_dialog.py (modal navigation) and examples/widgets/dropdown_demo.py (actions)

  1. Register multiple views with @app.view("home", default=True) and @app.view("settings").

  2. Use @app.on_action("go_settings") to call app.navigate("settings").

  3. Add keyboard shortcuts via @app.on_key("ctrl+,") to trigger the same action.

  4. Store breadcrumbs or parameters in app.current_view_params if you need to restore filters/search terms when returning.

Background tasks & progress indicators

Source: examples/basic/async_demo.py and examples/advanced/download_simulator.py

  1. Mark the handler async def (Wijjit awaits coroutines).

  2. Update state["progress"] inside the coroutine and call await asyncio.sleep(...) between iterations.

  3. Render progress via {% progressbar value=state.progress %}{% endprogressbar %}.

  4. For CPU-bound work, opt into the built-in executor with app.config["RUN_SYNC_IN_EXECUTOR"] = True (and optionally app.config["EXECUTOR_MAX_WORKERS"] = N) so blocking sync handlers run off the event loop and keep the UI responsive.

Data tables with selection

Source: examples/widgets/table_demo.py

  1. Prepare rows as list of dicts; include selected_row in state.

  2. Pass rows and columns to the table tag; use action="select_row_{{ row.id }}" on buttons/checkboxes inside cells.

  3. Handle selection in a generic EventType.ACTION handler by parsing the action id (select_row_ prefix).

  4. Reflect selection elsewhere (details pane, status bar, modal).

Notifications & status feedback

Source: examples/widgets/notification_demo.py and examples/widgets/statusbar_demo.py

  1. For transient toasts, call app.notify("Saved!", severity="success"). The notification auto-dismisses after duration seconds (pass duration=None to make it persistent).

  2. For persistent hints, bind a state.status string to a statusbar or text block.

  3. Combine with success/error tones to give clear guidance after actions.

Theming and styling tweaks

Source: examples/widgets/spinner_demo.py + Styling & Themes

  1. Create a custom Theme with overrides (button colors, frame borders).

  2. Register and activate it via app.renderer.theme_manager.register_theme(custom_theme) followed by app.renderer.theme_manager.set_theme(custom_theme.name) during startup or in response to user preference toggles.

  3. Give elements a class in the template (e.g. {% button class="danger" %}) and define that class in the theme — templates style by class, not by inline colour dicts.

  4. Snapshot the UI after theme changes to catch regressions.

Have a recipe to add? Drop a runnable snippet in examples/ and update this page so others can benefit. Refer back to Core Concepts for a deeper explanation of how these patterns plug into the runtime.