| name | nicegui |
| plugin | coding |
| description | Build Python web UIs with NiceGUI — covering layout, widgets, data binding, routing, and the AgGrid data grid. Use this skill whenever the user mentions NiceGUI, wants to build a Python web app, dashboard, or internal tool, asks about ui.aggrid, ui.page, or any NiceGUI component. Triggers on: "nicegui", "ui.aggrid", "ui.page", "NiceGUI app", "python web ui", "python dashboard", "aggrid python", "build a web interface in python".
|
NiceGUI
NiceGUI is a Python framework that renders a reactive web UI in the browser, backed by FastAPI and Vue/Quasar. Python code runs server-side; the browser is a thin client that re-renders on state changes.
Two modes
Script mode (prototypes/simple apps) — write top-level code, executed once per connection:
from nicegui import ui
ui.label('Hello world')
ui.run()
Page mode (multi-page apps) — each decorated function is a private page per user:
from nicegui import ui
@ui.page('/')
def index():
ui.label('Home')
@ui.page('/dashboard')
def dashboard():
ui.label('Dashboard')
ui.run()
Auto-context
Elements are automatically added to the currently active with-context — no explicit parent parameter needed:
with ui.card():
ui.label('Inside card')
ui.button('Click me', on_click=lambda: ui.notify('Hi!'))
Layout elements
| Element | Description |
|---|
ui.row() | Flex row (horizontal), wrap=True by default |
ui.column() | Flex column (vertical) |
ui.card() | Card with drop shadow |
ui.grid(columns=3) | CSS grid |
ui.tabs() / ui.tab_panels() | Tab navigation |
ui.splitter() | Resizable split panes (.before / .after slots) |
ui.expansion('Title') | Accordion |
ui.scroll_area() | Custom scrollbar container |
ui.dialog() | Modal dialog |
ui.header() / ui.footer() | Page header/footer |
ui.left_drawer() / ui.right_drawer() | Side drawers |
with ui.tabs().classes('w-full') as tabs:
one = ui.tab('One')
two = ui.tab('Two')
with ui.tab_panels(tabs, value=two).classes('w-full'):
with ui.tab_panel(one):
ui.label('First tab')
with ui.tab_panel(two):
ui.label('Second tab')
Styling
- Tailwind CSS:
.classes('text-xl font-bold text-red-500 w-full')
- Inline CSS:
.style('color: red; font-size: 20px')
- Quasar props:
.props('flat color=primary dense')
Common widgets
ui.label('text')
ui.button('Label', on_click=handler)
ui.input('Placeholder', on_change=handler)
ui.number('Label', min=0, max=100, value=50)
ui.checkbox('Label', on_change=handler)
ui.switch('Label')
ui.slider(min=0, max=10, step=0.5)
ui.select(['A', 'B', 'C'], label='Pick one')
ui.toggle({1: 'A', 2: 'B', 3: 'C'})
ui.date(on_change=handler)
ui.textarea('Notes')
ui.icon('home')
ui.image('https://...')
ui.markdown('**Bold** text')
ui.notify('Message', type='positive')
Data binding
Bind UI element properties to Python objects — updates propagate both ways automatically:
class State:
name: str = 'Alice'
state = State()
ui.input('Name').bind_value(state, 'name')
ui.label().bind_text_from(state, 'name', backward=lambda n: f'Hello {n}')
@binding.bindable_dataclass — all fields become reactive properties (most efficient):
from nicegui import binding, ui
@binding.bindable_dataclass
class State:
count: int = 0
s = State()
ui.number().bind_value(s, 'count')
ui.label().bind_text_from(s, 'count', backward=lambda v: f'Count: {v}')
bind_value — two-way binding
bind_value_from / bind_value_to — one-way
bind_visibility_from(obj, 'flag') — show/hide element
Bind to app.storage.user for persistence across page loads:
from nicegui import app, ui
ui.textarea().bind_value(app.storage.user, 'note')
Events
ui.button('Save', on_click=lambda: save())
async def handle():
rows = await grid.get_selected_rows()
ui.notify(str(rows))
ui.button('Selected', on_click=handle)
grid.on('cellClicked', lambda e: ui.notify(e.args['value']))
Timers
ui.timer(1.0, callback, once=False)
Navigation
ui.navigate.to('/other')
ui.navigate.to('https://example.com')
ui.navigate.back()
ui.navigate.reload()
Custom FastAPI integration
from nicegui import app
@app.get('/api/data')
def get_data():
return {'value': 42}
ui.aggrid — AG Grid
For detailed reference see references/aggrid.md. Key points:
grid = ui.aggrid({
'columnDefs': [
{'headerName': 'Name', 'field': 'name'},
{'headerName': 'Age', 'field': 'age'},
],
'rowData': [
{'name': 'Alice', 'age': 18},
{'name': 'Bob', 'age': 21},
],
'rowSelection': {'mode': 'multiRow'},
})
Update data — mutate grid.options['rowData'] and call grid.update():
grid.options['rowData'].append({'name': 'Carol', 'age': 42})
grid.update()
Selection (async):
async def show_selected():
rows = await grid.get_selected_rows()
row = await grid.get_selected_row()
Run AG Grid API methods:
grid.run_grid_method('selectAll')
grid.run_grid_method('setColumnsVisible', ['parent'], True)
await grid.run_grid_method('getSelectedRows')
Update a single row (preserves selection):
grid = ui.aggrid({
...,
':getRowId': '(params) => params.data.name',
})
grid.run_row_method('Alice', 'setDataValue', 'age', 99)
From DataFrame:
import pandas as pd
df = pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]})
ui.aggrid.from_pandas(df)
import polars as pl
df = pl.DataFrame({'col1': [1, 2]})
ui.aggrid.from_polars(df)
Filters:
{'headerName': 'Name', 'field': 'name',
'filter': 'agTextColumnFilter', 'floatingFilter': True}
Conditional cell styling (Tailwind classes):
{'field': 'age', 'cellClassRules': {
'bg-red-300': 'x < 21',
'bg-green-300': 'x >= 21',
}}
Render column as HTML:
ui.aggrid({...}, html_columns=[1])
Themes: 'quartz' (default), 'balham', 'material', 'alpine'
ui.aggrid({...}, theme='balham')
grid.theme = 'alpine'
Get/sync client edits (when 'editable': True):
data = await grid.get_client_data()
await grid.load_client_data()
Direct JS access:
row = await ui.run_javascript(
f'return getElement({grid.id}).api.getDisplayedRowAtIndex(0).data'
)
Enterprise (optional):
bundle_url = f'https://cdn.jsdelivr.net/npm/ag-grid-enterprise@{ui.aggrid.VERSION}/+esm'
ui.aggrid.set_module_source(bundle_url)
ui.aggrid({...}, modules='enterprise')
Gotcha: Row data keys must not contain periods — use nested objects and dot-notation field paths ('field': 'name.first') instead.
ui.run() parameters
ui.run(
host='0.0.0.0',
port=8080,
title='My App',
dark=None,
reload=True,
show=True,
storage_secret='secret_key',
binding_refresh_interval=0.1,
)