NiceGUI

repository·main·Indexed 12 days ago

https://github.com/zauberzeug/nicegui

A Python-based UI framework for building web-based graphical user interfaces, ideal for micro web apps, dashboards, robotics, and machine learning configuration tools. It supports integration with FastAPI, custom Vue components, and AG Grid, and provides capabilities for 3D WebGL scenes, AI interfaces, and IoT device control.

Tokens
52.6K
Snippets
170
Records
244
Agent score
97%

What's inside NiceGUI

  1. Integrate ZeroMQ with NiceGUI using asyncio

    main

    To stream data from a ZeroMQ socket into a NiceGUI application, use the zmq.async library. This allows the ZeroMQ subscriber to operate within the asyncio event loop required by the NiceGUI server.

    In this pattern, a ZeroMQ publisher (e.g., zmq-server.py) sends data to a socket, and a NiceGUI client (e.g., main.py) uses an asynchronous subscriber to receive that data and update UI elements like plots in real-time.

    # Conceptual pattern for integration
    import zmq.asyncio
    
    # Use zmq.async to create a subscriber compatible with NiceGUI's asyncio loop
    ctx = zmq.asyncio.Context()
    subscriber = ctx.socket(zmq.SUB)
    # ... subscribe and update NiceGUI components ...
  2. Avoid rebuilding UI elements to prevent state loss

    main

    NiceGUI does not use a virtual DOM. When you delete and recreate elements, the framework destroys the corresponding Vue components on the client. This causes the loss of focus, scroll position, animations, and client-side state.

    Best Practices:

    • For simple value changes: Update elements in place using .set_text(), .set_value(), or via bindings instead of clearing a container and recreating elements.
    • For complex updates: Use @ui.refreshable to handle clearing and rebuilding specific subtrees safely.
    # BAD: destroys and recreates on every data change
    def show_count():
        container.clear()
        ui.label(f'Count: {count}')
    
    # GOOD: updates the existing element in place
    label = ui.label('Count: 0')
    # then: label.set_text(f'Count: {count}') or bind_text_from
  3. Manage multi-user state correctly

    main

    NiceGUI runs as a single Python process. Module-level variables are shared across ALL users and all connected browser tabs. If you store user-specific data in a global or module-level variable, the last user to write to it will overwrite the data for everyone else.

    Correct ways to handle state:

    • Per-user state (temporary): Use local variables defined inside a @ui.page function.
    • Per-user state (persistent): Use app.storage.user to persist data across page reloads using session cookies.
    • Per-user state (session-based): Use app.storage.client or app.storage.tab depending on the required scope.
    # BUG: all users see the same data, last writer wins
    items = []
    
    @ui.page('/')
    def index():
        ui.button('Add', on_click=lambda: items.append('x'))  # shared!
        ui.label(str(items))
    
    # CORRECT: per-user state in app.storage or local variables inside @ui.page
    @ui.page('/')
    def index():
        items = []  # local to this page invocation = per user
        # or: app.storage.user['items'] for persistence across reloads
  4. Use make_sortable() for simple drag-and-drop sorting

    main
    If your goal is simply to allow users to reorder items within a list or container, use the built-in make_sortable() method provided by NiceGUI. This is the recommended approach for standard sorting tasks as it abstracts away the manual HTML5 drag event handling shown in the Trello Cards example.
  5. Build a custom element using a third-party NPM module

    main

    To create a custom UI element in NiceGUI based on an NPM module (like signature_pad), you follow a pattern of wrapping the JavaScript module in a custom element definition.

    1. Define the JavaScript side: Create a .js file (e.g., signature_pad.js) that interfaces with the NPM module and a .py file (e.g., signature_pad.py) that defines the NiceGUI component class.
    2. Bundle the module: Use a bundler like rollup to minify the module and place the output in a dist directory. The entry point (e.g., src/index.mjs) typically re-exports the module.
    3. Integrate in Python: Import the custom component in your main.py to use it within your NiceGUI layout.
  6. How @ui.page and UI updates work

    main

    A function decorated with @ui.page runs exactly once when a user navigates to that route to build the initial page structure. It is not re-executed when the state changes.

    To update the UI after the initial page load, you must use one of the following:

    • Bindings: Automatic synchronization between Python attributes and UI elements.
    • @ui.refreshable: Explicitly rebuilding a specific part of the UI.
    • Direct mutation: Calling methods like .set_text(), .set_value(), or changing properties like .visible = False.
    • Timers: Using ui.timer() to trigger updates periodically.
  7. Organize layouts with Context Managers

    main

    NiceGUI uses Python context managers (with statements) to define layout structures. Children defined within the block are automatically placed inside the layout element.

    Common Layout Elements

    • ui.row(): Horizontal flex row (wraps by default).
    • ui.column(): Vertical flex column.
    • ui.card(): Quasar QCard with shadow.
    • ui.grid(columns=n): CSS grid.
    • ui.expansion(title, icon): Collapsible section.
    • ui.scroll_area(): Scrollable container.
    • ui.splitter(): Two-pane layout with a draggable divider (use with splitter.before: and with splitter.after:).

    Page Structure

    • ui.header(): Top header area.
    • ui.left_drawer(): Sidebar area.
    • ui.footer(): Bottom footer area.
    • ui.page_sticky(position, ...): Floating content at a specific position.
    • ui.skip_link(text, target): Accessibility link to jump to a stable container (e.g., a ui.column()).
    with ui.row().classes('w-full justify-between items-center'):
        ui.label('Left')
        ui.label('Right')
    
    with ui.splitter() as splitter:
        with splitter.before:
            ui.label('Left pane')
        with splitter.after:
            ui.label('Right pane')
  8. How the binding system works

    main

    NiceGUI bindings use two mechanisms to keep Python and the UI in sync:

    1. Push (Synchronous): When you assign a value to a BindableProperty (the descriptor used by attributes like value, text, or visible), the change propagates immediately through all linked bindings.
    2. Pull (Asynchronous): A background refresh loop runs every binding_refresh_interval (default 0.1s) to check active_links. This allows bindings to work against plain Python objects (like dict keys or standard class attributes) that do not have built-in setters.

    Key takeaways:

    • Binding between two NiceGUI elements is synchronous.
    • Binding to a plain dataclass or object attribute is subject to the ~100ms refresh interval.
    • You generally do not need to call .update() after a bound assignment.
  9. Understand the slot stack and element placement

    main

    NiceGUI uses a thread-local slot stack to manage element hierarchy via Python context managers (with blocks).

    • with element: pushes that element's default slot onto the stack.
    • Any element created inside that with block is automatically registered as a child of that element.
    • __exit__ pops the slot off the stack.

    Crucial Rule: Element creation order matters. You cannot place an element into a parent after it has already been created unless you enter that parent's context using a with block.

    row = ui.row()
    # Too late to add to row here without entering its context:
    with row:
        ui.label('This works')
    # ui.label('This would go to page root, NOT inside row')
  10. Handle Async and Blocking code in NiceGUI

    main

    NiceGUI runs in a single asyncio event loop shared by all users. Blocking this loop (e.g., using time.sleep() or heavy CPU work) freezes the entire application for every connected user.

    Guidelines for non-blocking code:

    • I/O operations: Use async libraries (e.g., httpx, aiofiles, asyncio.sleep()).
    • Blocking I/O: Wrap blocking calls with await run.io_bound(fn, *args) to run them in a thread.
    • CPU-heavy work: Use await run.cpu_bound(fn, *args) to run the task in a separate process.
    • Fire-and-forget tasks: Use background_tasks.create() instead of asyncio.create_task(). This ensures the task is tracked by NiceGUI, preventing garbage collection from cancelling it prematurely and ensuring exceptions are routed to the NiceGUI handler.

    Note: run.io_bound and run.cpu_bound are imported from nicegui.run.

    from nicegui import run, background_tasks
    
    # For I/O bound tasks
    await run.io_bound(blocking_io_function, arg1)
    
    # For CPU bound tasks
    await run.cpu_bound(heavy_computation_function, arg1)
    
    # For fire-and-forget
    background_tasks.create(my_coroutine())