# Data Elements | NiceGUI

[Button: icon:menu]

[](/)

[Installation](/#installation)

[Features](/#features)

[Demos](/#demos)

[Documentation](/documentation)

[Examples](/examples)

[Why?](/#why)

[Button]

[Button]

[](https://github.com/zauberzeug/nicegui/)

[Button: icon:more_vert]

# *Data* Elements

## [Table](/documentation/table)

A table based on Quasar's `QTable <https://quasar.dev/vue-components/table>`_ component.
Updates can be pushed to the table by updating the ``rows`` or ``columns`` properties.

If ``selection`` is "single" or "multiple", then a ``selected`` property is accessible containing the selected rows.

Note:
Cells in ``rows`` must not contain lists because they can cause the browser to crash.
To display complex data structures, convert them to strings first (e.g., using ``str()`` or custom formatting).

:rows: list of row objects
:columns: list of column objects (defaults to the columns of the first row *since version 2.0.0*)
:column_defaults: optional default column properties, *added in version 2.0.0*
:row_key: name of the column containing unique data identifying the row (default: "id")
:title: title of the table
:selection: selection type ("single" or "multiple"; default: `None`)
:pagination: a dictionary correlating to a pagination object or number of rows per page (`None` hides the pagination, 0 means "infinite"; default: `None`).
:on_select: callback which is invoked when the selection changes
:on_pagination_change: callback which is invoked when the pagination changes

main.py

[Button]

````python
from nicegui import ui

columns = [
    {'name': 'name', 'label': 'Name', 'field': 'name', 'required': True, 'align': 'left'},
    {'name': 'age', 'label': 'Age', 'field': 'age', 'sortable': True},
]
rows = [
    {'name': 'Alice', 'age': 18},
    {'name': 'Bob', 'age': 21},
    {'name': 'Carol'},
]
ui.table(columns=columns, rows=rows, row_key='name')

ui.run()
````

[See more →](/documentation/table)

## [AG Grid](/documentation/aggrid)

An element to create a grid using `AG Grid <https://www.ag-grid.com/>`_.
Updates can be pushed to the grid by updating the ``options`` property.

The methods ``run_grid_method`` and ``run_row_method`` can be used to interact with the AG Grid instance on the client.

:options: dictionary of AG Grid options
:html_columns: list of columns that should be rendered as HTML (default: ``[]``)
:theme: AG Grid theme "quartz", "balham", "material", or "alpine" (default: ``options['theme']`` or "quartz")
:auto_size_columns: whether to automatically resize columns to fit the grid width (default: ``None``, i.e. fit to width unless columns use ``flex``)
:modules: either "community", "enterprise", or a list of `AG Grid Modules <https://www.ag-grid.com/javascript-data-grid/modules/>`_ (default: "community")

main.py

[Button]

````python
from nicegui import ui

grid = ui.aggrid({
    'columnDefs': [
        {'headerName': 'Name', 'field': 'name'},
        {'headerName': 'Age', 'field': 'age'},
        {'headerName': 'Parent', 'field': 'parent', 'hide': True},
    ],
    'rowData': [
        {'name': 'Alice', 'age': 18, 'parent': 'David'},
        {'name': 'Bob', 'age': 21, 'parent': 'Eve'},
        {'name': 'Carol', 'age': 42, 'parent': 'Frank'},
    ],
    'rowSelection': {'mode': 'multiRow'},
})

def update():
    grid.options['rowData'][0]['age'] += 1

ui.button('Update', on_click=update)
ui.button('Select all', on_click=lambda: grid.run_grid_method('selectAll'))
ui.button('Show parent', on_click=lambda: grid.run_grid_method('setColumnsVisible', ['parent'], True))

ui.run()
````

[See more →](/documentation/aggrid)

## [Highcharts chart](/documentation/highchart)

An element to create a chart using `Highcharts <https://www.highcharts.com/>`_.
Updates can be pushed to the chart by changing the ``options`` property.

Due to Highcharts' restrictive license, this element is not part of the standard NiceGUI package.
It is maintained in a `separate repository <https://github.com/zauberzeug/nicegui-highcharts/>`_
and can be installed with ``pip install nicegui[highcharts]``.

By default, a ``Highcharts.chart`` is created.
To use, e.g., ``Highcharts.stockChart`` instead, set the ``type`` property to "stockChart".

:options: dictionary of Highcharts options
:type: chart type (e.g. "chart", "stockChart", "mapChart", ...; default: "chart")
:extras: list of extra dependencies to include (e.g. "annotations", "arc-diagram", "solid-gauge", ...)
:on_point_click: callback function that is called when a point is clicked
:on_point_drag_start: callback function that is called when a point drag starts
:on_point_drag: callback function that is called when a point is dragged
:on_point_drop: callback function that is called when a point is dropped

main.py

[Button]

````python
from nicegui import ui
from random import random

chart = ui.highchart({
    'title': False,
    'chart': {'type': 'bar'},
    'xAxis': {'categories': ['A', 'B']},
    'series': [
        {'name': 'Alpha', 'data': [0.1, 0.2]},
        {'name': 'Beta', 'data': [0.3, 0.4]},
    ],
}).classes('w-full h-64')

def update():
    chart.options['series'][0]['data'][0] = random()

ui.button('Update', on_click=update)

ui.run()
````

[See more →](/documentation/highchart)

## [Apache EChart](/documentation/echart)

An element to create a chart using `ECharts <https://echarts.apache.org/>`_.
Updates can be pushed to the chart by changing the `options` property.

:options: dictionary of EChart options
:on_point_click: callback that is invoked when a point is clicked
:on_click: callback that is invoked when any component is clicked (*added in version 3.5.0*)
:enable_3d: enforce importing the echarts-gl library
:renderer: renderer to use ("canvas" or "svg", *added in version 2.7.0*)
:theme: an EChart theme configuration (dictionary or a URL returning a JSON object, *added in version 2.15.0*)

main.py

[Button]

````python
from nicegui import ui
from random import random

echart = ui.echart({
    'xAxis': {'type': 'value'},
    'yAxis': {'type': 'category', 'data': ['A', 'B'], 'inverse': True},
    'legend': {'textStyle': {'color': 'gray'}},
    'series': [
        {'type': 'bar', 'name': 'Alpha', 'data': [0.1, 0.2]},
        {'type': 'bar', 'name': 'Beta', 'data': [0.3, 0.4]},
    ],
})

def update():
    echart.options['series'][0]['data'][0] = random()

ui.button('Update', on_click=update)

ui.run()
````

[See more →](/documentation/echart)

## [Pyplot Context](/documentation/pyplot)

Create a context to configure a `Matplotlib <https://matplotlib.org/>`_ plot.

:close: whether the figure should be closed after exiting the context; set to `False` if you want to update it later (default: `True`)
:kwargs: arguments like `figsize` which should be passed to `pyplot.figure <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.figure.html>`_

main.py

[Button]

````python
import numpy as np
from matplotlib import pyplot as plt
from nicegui import ui

with ui.pyplot(figsize=(3, 2)):
    x = np.linspace(0.0, 5.0)
    y = np.cos(2 * np.pi * x) * np.exp(-x)
    plt.plot(x, y, '-')

ui.run()
````

[See more →](/documentation/pyplot)

## [Matplotlib](/documentation/matplotlib)

Create a `Matplotlib <https://matplotlib.org/>`_ element rendering a Matplotlib figure.
The figure is automatically updated when leaving the figure context.

:kwargs: arguments like `figsize` which should be passed to `matplotlib.figure.Figure <https://matplotlib.org/stable/api/figure_api.html#matplotlib.figure.Figure>`_

main.py

[Button]

````python
import numpy as np
from nicegui import ui

with ui.matplotlib(figsize=(3, 2)).figure as fig:
    x = np.linspace(0.0, 5.0)
    y = np.cos(2 * np.pi * x) * np.exp(-x)
    ax = fig.gca()
    ax.plot(x, y, '-')

ui.run()
````

[See more →](/documentation/matplotlib)

## [Line Plot](/documentation/line_plot)

Create a line plot using pyplot.
The `push` method provides live updating when utilized in combination with `ui.timer`.

:n: number of lines
:limit: maximum number of datapoints per line (new points will displace the oldest)
:update_every: update plot only after pushing new data multiple times to save CPU and bandwidth
:close: whether the figure should be closed after exiting the context; set to `False` if you want to update it later (default: `True`)
:kwargs: arguments like `figsize` which should be passed to `pyplot.figure <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.figure.html>`_

main.py

[Button]

````python
import math
from datetime import datetime
from nicegui import ui

line_plot = ui.line_plot(n=2, limit=20, figsize=(3, 2), update_every=5) \
    .with_legend(['sin', 'cos'], loc='upper center', ncol=2)

def update_line_plot() -> None:
    now = datetime.now()
    x = now.timestamp()
    y1 = math.sin(x)
    y2 = math.cos(x)
    line_plot.push([now], [[y1], [y2]], y_limits=(-1.5, 1.5))

line_updates = ui.timer(0.1, update_line_plot, active=False)
line_checkbox = ui.checkbox('active').bind_value(line_updates, 'active')

ui.run()
````

[See more →](/documentation/line_plot)

## [Plotly Element](/documentation/plotly)

Renders a Plotly chart.
There are two ways to pass a Plotly figure for rendering, see parameter `figure`:

* Pass a `go.Figure` object, see https://plotly.com/python/

* Pass a Python `dict` object with keys `data`, `layout`, `config` (optional), see https://plotly.com/javascript/

For best performance, use the declarative `dict` approach for creating a Plotly chart.

:figure: Plotly figure to be rendered. Can be either a `go.Figure` instance, or
               a `dict` object with keys `data`, `layout`, `config` (optional).

main.py

[Button]

````python
import plotly.graph_objects as go
from nicegui import ui

fig = go.Figure(go.Scatter(x=[1, 2, 3, 4], y=[1, 2, 3, 2.5]))
fig.update_layout(margin=dict(l=0, r=0, t=0, b=0))
ui.plotly(fig).classes('w-full h-40')

ui.run()
````

[See more →](/documentation/plotly)

## [Altair Chart](/documentation/altair)

Wrap an Altair chart in NiceGUI via anywidget.

Refer to the `altair documentation <https://altair-viz.github.io/user_guide/interactions/jupyter_chart.html#accessing-variable-params>`_
for more information about synchronizing Altair parameters with Python.

*Added in version 3.5.0*

:chart: the chart to wrap
:throttle: minimum time (in seconds) between widget updates to Python (default: 0.0)

main.py

[Button]

````python
import altair as alt
from altair.datasets import data
from nicegui import ui

cars = data.cars()

chart = alt.Chart(cars).mark_point() \
    .encode(x='Horsepower', y='Miles_per_Gallon', color='Origin') \
    .interactive()

ui.altair(chart)

ui.run()
````

[See more →](/documentation/altair)

## [AnyWidget](/documentation/anywidget)

`anywidget <https://anywidget.dev/en/getting-started/>`_ is a library that allows you to
embed arbitrary JavaScript widgets in a cross-frontend friendly manner.

There are many publicly available examples of anywidget widgets
in the `anywidget gallery <https://try.anywidget.dev/>`_, including
`altair.JupyterChart <https://altair-viz.github.io/user_guide/interactions/jupyter_chart.html>`_,
and `quak <https://github.com/manzt/quak>`_.

Implementation: The ``nicegui.anywidget`` element takes an ``AnyWidget`` and observes all ``sync=True`` traits
of the widget, trigger JS updates when the traits change.
Conversely, changes on the frontend will be synced back to the widget,
using ``ValueElement``'s handling to listen to changes on ``traits``.

*Added in version 3.5.0*

:widget: the ``anywidget.AnyWidget`` to wrap
:throttle: minimum time (in seconds) between widget updates to Python (default: 0.0)

main.py

[Button]

````python
import anywidget
import traitlets
from nicegui import ui

class CounterWidget(anywidget.AnyWidget):
    _esm = '''
        function render({ model, el }) {
            const button = document.createElement("button");
            button.innerHTML = `Count is ${model.get("value")}`;
            button.addEventListener("click", () => {
                model.set("value", model.get("value") + 1);
                model.save_changes();
            });
            model.on("change:value", () => {
                button.innerHTML = `Count is ${model.get("value")}`;
            });
            el.classList.add("counter-widget");
            el.appendChild(button);
        }
        export default { render };
    '''
    _css = '''
        .counter-widget button {
            color: white;
            background-color: DarkOrange;
            padding: 0.5rem 1rem;
            border-radius: 0.25rem;
            cursor: pointer;

            &:hover {
                opacity: 0.8;
            }
        }
    '''
    value = traitlets.Int(0).tag(sync=True)

    def increment(self) -> None:
        self.value += 1

counter = CounterWidget(value=42)
ui.anywidget(counter)

ui.label('↑ AnyWidget')
ui.separator()
ui.label('↓ NiceGUI')

ui.button(on_click=counter.increment) \
  .bind_text_from(counter, 'value', backward=lambda c: f'Count is {c}')

ui.run()
````

[See more →](/documentation/anywidget)

## [Linear Progress](/documentation/linear_progress)

A linear progress bar wrapping Quasar's
`QLinearProgress <https://quasar.dev/vue-components/linear-progress>`_ component.

:value: the initial value of the field (from 0.0 to 1.0)
:size: the height of the progress bar (default: "20px" with value label and "4px" without)
:show_value: whether to show a value label in the center (default: `True`)
:color: color (either a Quasar, Tailwind, or CSS color or `None`, default: "primary")

main.py

[Button]

````python
from nicegui import ui

slider = ui.slider(min=0, max=1, step=0.01, value=0.5)
ui.linear_progress().bind_value_from(slider, 'value')

ui.run()
````

[See more →](/documentation/linear_progress)

## [Circular Progress](/documentation/circular_progress)

A circular progress bar wrapping Quasar's
`QCircularProgress <https://quasar.dev/vue-components/circular-progress>`_.

:value: the initial value of the field
:min: the minimum value (default: 0.0)
:max: the maximum value (default: 1.0)
:size: the size of the progress circle (default: "xl")
:show_value: whether to show a value label in the center (default: `True`)
:color: color (either a Quasar, Tailwind, or CSS color or `None`, default: "primary")

main.py

[Button]

````python
from nicegui import ui

slider = ui.slider(min=0, max=1, step=0.01, value=0.5)
ui.circular_progress().bind_value_from(slider, 'value')

ui.run()
````

[See more →](/documentation/circular_progress)

## [Spinner](/documentation/spinner)

This element is based on Quasar's `QSpinner <https://quasar.dev/vue-components/spinners>`_ component.

:type: type of spinner (e.g. "audio", "ball", "bars", ..., default: "default")
:size: size of the spinner (e.g. "3em", "10px", "xl", ..., default: "1em")
:color: color of the spinner (either a Quasar, Tailwind, or CSS color or `None`, default: "primary")
:thickness: thickness of the spinner (applies to the "default" spinner only, default: 5.0)

main.py

[Button]

````python
from nicegui import ui

with ui.row():
    ui.spinner(size='lg')
    ui.spinner('audio', size='lg', color='green')
    ui.spinner('dots', size='lg', color='red')

ui.run()
````

[See more →](/documentation/spinner)

## [3D Scene](/documentation/scene)

Display a 3D scene using `three.js <https://threejs.org/>`_.
Currently NiceGUI supports boxes, spheres, cylinders/cones, extrusions, straight lines, curves and textured meshes.
Objects can be translated, rotated and displayed with different color, opacity or as wireframes.
They can also be grouped to apply joint movements.

:width: width of the canvas
:height: height of the canvas
:grid: whether to display a grid (boolean or tuple of ``size`` and ``divisions`` for `Three.js' GridHelper <https://threejs.org/docs/#api/en/helpers/GridHelper>`_, default: 100x100)
:camera: camera definition, either instance of ``ui.scene.perspective_camera`` (default) or ``ui.scene.orthographic_camera``
:on_click: callback to execute when a 3D object is clicked (use ``click_events`` to specify which events to subscribe to)
:click_events: list of JavaScript click events to subscribe to (default: ``['click', 'dblclick']``)
:on_drag_start: callback to execute when a 3D object is dragged
:on_drag_end: callback to execute when a 3D object is dropped
:drag_constraints: comma-separated JavaScript expression for constraining positions of dragged objects (e.g. ``'x = 0, z = y / 2'``)
:background_color: background color of the scene (default: "#eee")
:control_type: type of controls to use for navigating the scene, one of "orbit", "trackball", "map" (default: "orbit", *added in version 3.9.0*)
:fps: target frame rate for the scene in frames per second (default: 20, *added in version 3.2.0*)
:show_stats: whether to show performance stats (default: ``False``, *added in version 3.2.0*)

main.py

[Button]

````python
from nicegui import ui

with ui.scene().classes('w-full h-64') as scene:
    scene.axes_helper()
    scene.sphere().material('#4488ff').move(2, 2)
    scene.cylinder(1, 0.5, 2, 20).material('#ff8800', opacity=0.5).move(-2, 1)
    scene.extrusion([[0, 0], [0, 1], [1, 0.5]], 0.1).material('#ff8888').move(2, -1)

    with scene.group().move(z=2):
        scene.box().move(x=2)
        scene.box().move(y=2).rotate(0.25, 0.5, 0.75)
        scene.box(wireframe=True).material('#888888').move(x=2, y=2)

    scene.line([-4, 0, 0], [-4, 2, 0]).material('#ff0000')
    scene.curve([-4, 0, 0], [-4, -1, 0], [-3, -1, 0], [-3, 0, 0]).material('#008800')

    logo = 'https://avatars.githubusercontent.com/u/2843826'
    scene.texture(logo, [[[0.5, 2, 0], [2.5, 2, 0]],
                         [[0.5, 0, 0], [2.5, 0, 0]]]).move(1, -3)

    teapot = 'https://upload.wikimedia.org/wikipedia/commons/9/93/Utah_teapot_(solid).stl'
    scene.stl(teapot).scale(0.2).move(-3, 4)

    avocado = 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/Avocado/glTF-Binary/Avocado.glb'
    scene.gltf(avocado).scale(40).move(-2, -3, 0.5)

    scene.text('2D', 'background: rgba(0, 0, 0, 0.2); border-radius: 5px; padding: 5px').move(z=2)
    scene.text3d('3D', 'background: rgba(0, 0, 0, 0.2); border-radius: 5px; padding: 5px').move(y=-2).scale(.05)

ui.run()
````

[See more →](/documentation/scene)

## [Leaflet map](/documentation/leaflet)

This element is a wrapper around the `Leaflet <https://leafletjs.com/>`_ JavaScript library.

:center: initial center location of the map (latitude/longitude, default: (0.0, 0.0))
:zoom: initial zoom level of the map (default: 13)
:draw_control: whether to show the draw toolbar (default: False)
:options: additional options passed to the Leaflet map (default: {})
:hide_drawn_items: whether to hide drawn items on the map (default: False, *added in version 2.0.0*)
:additional_resources: additional resources like CSS or JS files to load (default: None, *added in version 2.11.0*)

main.py

[Button]

````python
from nicegui import ui

m = ui.leaflet(center=(51.505, -0.09))
ui.label().bind_text_from(m, 'center', lambda center: f'Center: {center[0]:.3f}, {center[1]:.3f}')
ui.label().bind_text_from(m, 'zoom', lambda zoom: f'Zoom: {zoom}')

with ui.grid(columns=2):
    ui.button('London', on_click=lambda: m.set_center((51.505, -0.090)))
    ui.button('Berlin', on_click=lambda: m.set_center((52.520, 13.405)))
    ui.button(icon='zoom_in', on_click=lambda: m.set_zoom(m.zoom + 1))
    ui.button(icon='zoom_out', on_click=lambda: m.set_zoom(m.zoom - 1))

ui.run()
````

[See more →](/documentation/leaflet)

## [Tree](/documentation/tree)

Display hierarchical data using Quasar's `QTree <https://quasar.dev/vue-components/tree>`_ component.
Updates can be pushed to the tree by updating ``.props['nodes']``.

If using IDs, make sure they are unique within the whole tree.

To use checkboxes and ``on_tick``, set the ``tick_strategy`` parameter to "leaf", "leaf-filtered" or "strict".

:nodes: hierarchical list of node objects
:node_key: property name of each node object that holds its unique id (default: "id")
:label_key: property name of each node object that holds its label (default: "label")
:children_key: property name of each node object that holds its list of children (default: "children")
:on_select: callback which is invoked when the node selection changes
:on_expand: callback which is invoked when the node expansion changes
:on_tick: callback which is invoked when a node is ticked or unticked
:tick_strategy: whether and how to use checkboxes ("leaf", "leaf-filtered" or "strict"; default: ``None``)

main.py

[Button]

````python
from nicegui import ui

ui.tree([
    {'id': 'numbers', 'children': [{'id': '1'}, {'id': '2'}]},
    {'id': 'letters', 'children': [{'id': 'A'}, {'id': 'B'}]},
], label_key='id', on_select=lambda e: ui.notify(e.value))

ui.run()
````

[See more →](/documentation/tree)

## [Log View](/documentation/log)

Create a log view that allows to add new lines without re-transmitting the whole history to the client.

:max_lines: maximum number of lines before dropping oldest ones (default: `None`)

main.py

[Button]

````python
from datetime import datetime
from nicegui import ui

log = ui.log(max_lines=10).classes('w-full h-20')
ui.button('Log time', on_click=lambda: log.push(datetime.now().strftime('%X.%f')[:-5]))

ui.run()
````

[See more →](/documentation/log)

## [Editor](/documentation/editor)

A WYSIWYG editor based on `Quasar's QEditor <https://quasar.dev/vue-components/editor>`_.
The value is a string containing the formatted text as HTML code.

:value: initial value
:on_change: callback to be invoked when the value changes

main.py

[Button]

````python
from nicegui import ui

editor = ui.editor(placeholder='Type something here')
ui.markdown().bind_content_from(editor, 'value',
                                backward=lambda v: f'HTML code:\n```\n{v}\n```')

ui.run()
````

[See more →](/documentation/editor)

## [Code](/documentation/code)

This element displays a code block with syntax highlighting.

In secure environments (HTTPS or localhost), a copy button is displayed to copy the code to the clipboard.

:content: code to display
:language: language of the code (default: "python")

main.py

[Button]

````python
from nicegui import ui

ui.code('''
    from nicegui import ui

    ui.label('Code inception!')

    ui.run()
''').classes('w-full')

ui.run()
````

[See more →](/documentation/code)

## [JSONEditor](/documentation/json_editor)

An element to create a JSON editor using `JSONEditor <https://github.com/josdejong/svelte-jsoneditor>`_.
Updates can be pushed to the editor by changing the `properties` property.

:properties: dictionary of JSONEditor properties
:on_select: callback which is invoked when some of the content has been selected
:on_change: callback which is invoked when the content has changed
:schema: optional `JSON schema <https://json-schema.org/>`_ for validating the data being edited (*added in version 2.8.0*)

main.py

[Button]

````python
from nicegui import ui

json = {
    'array': [1, 2, 3],
    'boolean': True,
    'color': '#82b92c',
    None: None,
    'number': 123,
    'object': {
        'a': 'b',
        'c': 'd',
    },
    'time': 1575599819000,
    'string': 'Hello World',
}
ui.json_editor({'content': {'json': json}},
               on_select=lambda e: ui.notify(f'Select: {e}'),
               on_change=lambda e: ui.notify(f'Change: {e}'))

ui.run()
````

[See more →](/documentation/json_editor)

**Nice**GUI

The Python UI framework that shows up in your browser.

[](https://github.com/zauberzeug/nicegui/)

[](https://discord.gg/TEpFeAaF4f)

[](https://www.reddit.com/r/nicegui/)

Resources

[Documentation](/documentation)

[Examples](/examples)

[LLM reference](/llms.txt)

[GitHub](https://github.com/zauberzeug/nicegui/)

[PyPI](https://pypi.org/project/nicegui/)

Community

[Discussions](https://github.com/zauberzeug/nicegui/discussions)

[Discord](https://discord.gg/TEpFeAaF4f)

[Reddit](https://www.reddit.com/r/nicegui/)

[Contributing](https://github.com/zauberzeug/nicegui/blob/main/CONTRIBUTING.md)

[Sponsors](https://github.com/sponsors/zauberzeug)

Legal

[Imprint](/imprint_privacy#imprint)

[Privacy](/imprint_privacy#privacy)

Made with NiceGUI by [Zauberzeug](https://zauberzeug.com)

© 2026 [Zauberzeug GmbH](https://zauberzeug.com)