NiceGUI 4.0 is a major release focused on cleaning up: it removes the APIs deprecated during 3.x, corrects a few defaults and catches up on the bundled JavaScript libraries. Nothing changes as fundamentally as the auto-index page did in 3.0, so most apps should only need small adjustments. Some changes don't raise any error, though, so please read the migration guide before upgrading.
How to upgrade
- Update to the latest 3.19 release, run your app and tests, and fix all deprecation warnings. They name the replacement for most of what is removed in 4.0.
- Check the new minimum requirements below.
- Upgrade and run a type checker like mypy or pyright. It flags many leftovers, e.g.
sanitize=None, before the first run. - Go through the behavior changes below, because most of them won't show up as an error.
New features and enhancements
- Support Python 3.15 (#6394 by @falkoschindler, @evnchn)
- Reduce the memory per connected client by about 14% and another 13% on a measured page, using
__slots__on observable collections and dropping the bindable property registry (#6409, #6410, #6412, #6434 by @evnchn, @falkoschindler) - Give text inputs a single empty state, so that the value type of
ui.inputand related elements narrows tostr(#6421, #6435 by @falkoschindler, @evnchn, @emmanuelprecieuxargent) - Add a
pagination_controlsparameter toui.tablethat controls the pagination bar independently ofpagination, so that assigningtable.paginationlater now shows the controls as well, and addon_pagination_changetoui.table.from_pandasandui.table.from_polars(#6235, #6275 by @evnchn, @SaadZahem, @falkoschindler) - Use "spawn" as the default start method of the
run.cpu_boundprocess pool, because "fork" is unsafe in a threaded process (#6117, #6419 by @falkoschindler, @evnchn) - Let
run.io_boundandrun.cpu_boundkeep working inapp.on_shutdownhandlers and stop them from swallowingtask.cancel()(#6420 by @falkoschindler, @evnchn) - Allow plotly.py 7 for
ui.plotly(#6327, #6441 by @falkoschindler, @evnchn) - Add the Quasar language packs
ckb,lb,sr-Cyrl,sqandur-PK(#6440 by @falkoschindler, @evnchn)
Bugfixes
- Prevent custom
Object3DandLayerobjects created after anawaitfrom landing in another client's scene or map (#6345, #6361, #6401 by @evnchn, @falkoschindler, @wolfgang-aura) - Fix router pages in
app.remove_route()andendpoint_documentationwith current FastAPI versions (#6373, #6416 by @falkoschindler, @evnchn, @Melvin0hd) - Make objects with bindable properties picklable, e.g. as arguments to
run.cpu_bound, and stop keeping their classes alive (#6412, #6434 by @evnchn, @falkoschindler) - Let
ui.leafletadd its layers beforeawait map.initialized()returns, so that layer methods no longer returnNone(#6470 by @falkoschindler)
⚠️ Migration guide
Minimum requirements
| Requirement | 3.19 | 4.0 | Ticket |
|---|---|---|---|
| Python | 3.10 | 3.11 | #6393 by @falkoschindler, @evnchn |
| FastAPI | 0.109.1 | 0.143.1 | #6373, #6416 by @falkoschindler, @evnchn, @Melvin0hd |
Pillow (matplotlib extra)
| 12.3 | 13.0 | #6467 by @falkoschindler |
| Chrome / Edge | 111 | 121 | #6440 by @falkoschindler, @evnchn |
| Safari | 16.4 | 17.2 | #6440 by @falkoschindler, @evnchn |
| HTTP client | httpx
| httpx2
| #5876, #6024, #6398 by @falkoschindler, @evnchn, @KrilleGH, @cervelas, @samuller |
httpxis no longer installed. Apps importing it have to declare it themselves or switch tohttpx2, which has the same API. The testing API now useshttpx2types:User(client: httpx2.AsyncClient), andUserDownload.http_responsesandUserDownload.next()hold or returnhttpx2.Response. In general, please declare every package you import instead of relying on what NiceGUI happens to pull in.- Python 3.15 on glibc-based Linux needs glibc 2.39 or newer (e.g. Ubuntu 24.04+, Debian 13+) for orjson's wheels. Older systems need a source build with Rust and a C compiler.
- The Docker image now ships pandas 3, polars 2 and numpy 2.5, and no longer includes
pytz. Install it yourself or switch tozoneinfo.
Behavior changes
These changes mostly don't raise an error, so your code won't tell you about them.
-
Old prop names are ignored (#6397 by @falkoschindler, @evnchn)
Snake_case and camelCase prop names are no longer migrated to kebab-case.
.props('html_columns=[0]'), for example, now sets a prop nobody reads, so the feature silently stops working and nothing warns about it anymore. Constructor arguments are not affected. Please search your code for the old names:All removed prop names
Element Old New all elements resource_pathresource-pathall elements dynamic_resource_pathdynamic-resource-pathui.aggridhtml_columnshtml-columnsui.codemirrorhighlightWhitespacehighlight-whitespaceui.codemirrorlineWrappingline-wrappingui.colorsdark_pagedark-pageui.colorscustom_colorscustom-colorsui.echartenable_3denable-3dui.fullscreenrequireEscapeHoldrequire-escape-holdui.headeradd_scroll_paddingadd-scroll-paddingui.leafletdraw_controldraw-controlui.leaflethide_drawn_itemshide-drawn-itemsui.leafletadditional_resourcesadditional-resourcesui.markdownresource_nameresource-nameui.markdownuse_mermaiduse-mermaidui.scenebackground_colorbackground-colorui.sceneclick_eventsclick-eventsui.scenedrag_constraintsdrag-constraintsui.scene,ui.scene_viewcamera_paramscamera-paramsui.scene,ui.scene_viewcamera_typecamera-typeui.scene,ui.scene_viewshow_statsshow-statsui.scene_viewscene_idscene-id -
Cleared text inputs hold
''instead ofNone(#6421, #6435 by @falkoschindler, @evnchn, @emmanuelprecieuxargent)This affects
ui.input,ui.textarea,ui.date_input,ui.time_inputandui.color_input. Replacevalue is Nonewithnot value, which works before and after. A model usingNoneas "empty" can keep it withinput.bind_value(model, 'x', forward=lambda v: v or None, backward=lambda v: v or '').ui.numberandui.selectkeepNone. -
run.cpu_bounduses "spawn" by default (#6117, #6419 by @falkoschindler, @evnchn)Workers start from a fresh import and don't inherit module globals. The function must be importable in the worker: functions defined under
if __name__ == '__main__':, viapython -c, stdin, a zipapp or a package's__main__.pynow fail withBrokenProcessPool, and the actual error only appears in the worker's log. To keep the old behavior on Linux, setrun.process_pool_start_method = 'fork'beforeui.run(). -
run.io_boundandrun.cpu_boundraiseasyncio.CancelledErrorinstead of returningNone(#6420 by @falkoschindler, @evnchn)Code checking
if result is None:to detect a shutdown or cancellation has to catch the exception or let it propagate. -
Element IDs in the DOM change from
c12tong-12(#6375, #6378, #6395 by @falkoschindler, @evnchn)Replace hard-coded IDs in CSS, JavaScript or tests with
element.html_idorgetHtmlElement(element.id). Custom components stripping the prefix withid.slice(1)have to useparseHtmlId(id). The implicit browser globals likec12are gone, and the global ofui.scenefor Selenium tests is nowscene_<id>. -
Spot lights in
ui.scenedefault todecay=2.0(#6243, #6366, #6417 by @falkoschindler, @evnchn, @python-and-novella)They now fall off like in Three.js with 1/r² instead of 1/r, so at a distance of 5 units they are five times dimmer. Pass
decay=1.0to restore the old look, or increaseintensity. -
table.paginationshows the pagination controls (#6235, #6275 by @evnchn, @SaadZahem, @falkoschindler)This also applies to tables created without pagination, and
.props('hide-pagination')no longer survives a later assignment. Usepagination_controls=Falseinstead. -
Custom scene objects and map layers need a
withblock in handlers and timers (#6345, #6361, #6401 by @evnchn, @falkoschindler, @wolfgang-aura)Constructing a custom
Object3DorLayersubclass outside ofwith scene:used to find the last scene or map touched anywhere in the process. Now it raises aRuntimeError. The same applies toon_click=scene.box, which fetches the constructor early: useon_click=lambda: scene.box().Object3D.current_sceneandLayer.current_leafletare now private. -
Screen.should_not_containwaits for the text to disappear (#6432 by @falkoschindler, @evnchn)It used to fail immediately if the text was still shown, and then observed for 0.5 s. A check that something must not show up after an action now needs
duration=0.5to keep its strength. The parameterwaitis a deprecated alias forduration. -
Observable collections have no
__dict__(#6409, #6410 by @evnchn, @falkoschindler)vars()and setting arbitrary attributes fail onObservableDict,ObservableList,ObservableSetand onelement.props,classesandstyle. Subclasses without their own__slots__, likePersistentDict, are not affected. -
Bindings no longer poll bindable properties written past
BindableProperty.__set__(#6412, #6434 by @evnchn, @falkoschindler)This only concerns values set via
__dict__.update, a custom__copy__or a descriptor subclass that skipssuper().__set__. Regular assignments still propagate. -
WEB_CONCURRENCYabove 1 raises aValueError(#6467 by @falkoschindler)NiceGUI doesn't support multiple workers. With
reload=True, the default, uvicorn used to ignore the variable with a warning, and withreload=False, the app logged a message and exited. Please remove the variable from the environment of your app. -
ui.timercallbacks must not swallow the cancellation (#6470 by @falkoschindler)A callback catching
asyncio.CancelledErrorwithout re-raising it can delay the shutdown, which 3.19 tolerated.run.io_boundandrun.cpu_boundno longer do this themselves (#6420), so this only concerns your own code.
Removed APIs
These fail with an error or are flagged by a type checker (#6397 by @falkoschindler, @evnchn):
| Removed | Use instead |
|---|---|
sanitize=None in ui.chat_message and ui.interactive_image
| True (default), False or a callable
|
nicegui.dataclasses.KWONLY_SLOTS
| kw_only=True, slots=True
|
coroutine=... in background_tasks.create() and create_lazy()
| awaitable=...
|
Awaitables in lifecycle registrations like app.on_startup(startup())
| The function itself: app.on_startup(startup)
|
'checkboxRenderer' in ui.aggrid
| 'cellDataType': 'boolean' and 'editable': True
|
ui.tab outside of ui.tabs
| Nest it, it now raises a RuntimeError
|
ValueChangeEventArguments without previous_value
| Pass it, as custom elements now have to |
Object3D subclasses without component=
| Add component= or use a built-in base class
|
Object3D instantiated directly or with a type string
| The built-in objects or a custom subclass |
Object3D.type and Object3D.data
| No replacement |
nicegui.elements.scene.scene_objects
| nicegui.elements.scene.objects
|
Positional optional arguments of ui.scene and ui.scene_view
| Keywords (width and height of ui.scene and scene of ui.scene_view stay positional)
|
ui.add_scss and ui.add_sass, including the bundled Sass compiler
| ui.add_css, as CSS supports nesting natively
|
Props.add_rename (#6464, #6471 by @shi-felix, @falkoschindler)
| Map the old name yourself; add_warning only drops the key and warns
|
ui.codemirror(language='Jinja2') still works, but is deprecated and logs a warning. Please use language='Jinja' (#6467 by @falkoschindler).
Bundled libraries
If you use these elements, or target their internals with custom CSS or JavaScript, please check the respective changes:
Quasar 2.18.5 → 2.35.2 (#6440 by @falkoschindler, @evnchn)
ui.menu and ui.tooltip keep their placement while scrolling and stay attached to their anchor.
click listener on ui.item or ui.chip implies clickable.
1–2 of 3).
AG Grid 34.2.0 → 36.2.0 (#6444 by @falkoschindler, @evnchn)
'ValidationModule' to modules to get them back, now also as an overlay on the grid.
ag-body-viewport, ag-center-cols-container), and theme and RTL classes moved to a parent element, so custom CSS may need an update.
fontWeight theme parameter defaults to 400. Set fontWeight: 'inherit' to inherit the page's font weight.
content-visibility: auto is no longer applied by default (enableContentVisibilityAuto brings it back).
.ag-center-cols-container no longer exists. Custom CSS or tests selecting rows through it have to use .ag-row directly.
plotly.js 3.1.1 → 4.2.0 (#6327, #6441, #6467 by @falkoschindler, @evnchn)
scattermapbox, choroplethmapbox and densitymapbox traces, the mapbox subplot and mapboxAccessToken are removed. Use the *map equivalents.
rgb() with 0–1 fractions and hsv() strings are no longer accepted, and invalid CSS color strings are rejected.
layout.geo.fitbounds defaults to 'locations', and scattermap and densitymap auto-fit center and zoom.
splom.axis.matches defaults to true, overlaying axes default to tickmode: 'sync', and the double-click delay increased from 300 to 500 ms.
hoveranywhere and clickanywhere events return actual data values for xvals and yvals.
ui.plotly({'data': ..., 'config': {'showSendToCloud': True}}) brings it back.
three.js r180 → r187 (#6443, #6467 by @falkoschindler, @evnchn)
scene.gltf() for most models, conserve energy better: rough materials look slightly brighter.
matrixAutoUpdate = false and writing matrix directly must also set matrixWorldNeedsUpdate = true.
Object3D has its own dispose(), so custom subclasses with a dispose() should call super.dispose().
xterm.js 5.5.0 → 6.0.0 (#6445 by @falkoschindler, @evnchn)
{'fontFamily': 'courier-new, courier, monospace'} to restore the old look. As the line height may differ, fit() can yield a different number of rows.
.xterm-viewport or .xterm-scroll-area may need an update.
overviewRulerWidth moved to overviewRuler: {'width': ...}, and windowsMode and fastScrollModifier were removed.
nipplejs 0.10.2 → 1.0.4 (#6446 by @falkoschindler, @evnchn)
ui.joystick is no longer rendered at half opacity. Pass a semi-transparent back color like color={'front': 'blue', 'back': 'rgba(0, 0, 255, 0.5)'} to restore the old look.
maxNumberOfNipples was renamed to maxNumberOfJoysticks.
move event data no longer contains identifier but gains baseDelta, and the raw start and end events now contain data.
Dependencies
-
Update the remaining Python and JavaScript dependencies, including uvicorn 0.54, Starlette 1.10 and websockets 17 in the lock file (#6467 by @falkoschindler)
Other JavaScript dependencies
- @tailwindcss/browser: 4.1.13 → 4.3.4
- @unocss/reset and @unocss/runtime: 66.6.0 → 66.12.0
- socket.io: 4.8.1 → 4.8.4
- vue: 3.5.22 → 3.5.44
- @codemirror/language-data: 6.5.1 → 6.5.2
- @uiw/codemirror-themes-all: 4.25.2 → 4.25.12
- @babel/runtime: 7.28.4 → 7.29.10
- vanilla-jsoneditor: 3.10.0 → 3.13.0
- mermaid: 11.16.1 → 11.17.2
- d3-transition: 3.0.2 (new)
- sass: 1.94.0 (removed)
Documentation
- Fix the docstring markup, which rendered code in italics, and check docstrings in pre-commit (#6451 by @falkoschindler, @evnchn)
Testing
- Speed up the test suite and add
Screen.SCREENSHOT_PASSED_TESTSto skip screenshots of passed tests (#6432 by @falkoschindler, @evnchn) - Let
Screen.start_server()wait until the server accepts connections (#6398 by @falkoschindler, @evnchn)
Infrastructure
- Update the development tooling, including mypy 2, isort 9 and Ruff 0.17 (#6450 by @falkoschindler, @evnchn)
Special thanks to our top sponsors DiscoLike Inc., TestMu AI and Lechler GmbH ✨
and all our other sponsors and contributors for supporting this project!
🙏 Want to support this project? Check out our GitHub Sponsors page to help us keep building amazing features!