Panel 1.10.0 Release

What is Panel?
Panel is an open-source Python library that allows you to easily create powerful tools, dashboards, and complex applications entirely in Python. With its “batteries-included” philosophy, Panel brings the full PyData ecosystem, advanced data tables, and much more to your fingertips. It offers both high-level reactive APIs and lower-level callback-based APIs, enabling you to quickly build exploratory applications or develop complex, multi-page apps with rich interactivity. As a member of the HoloViz ecosystem, Panel provides seamless integration with a suite of tools designed for data exploration.
New Release!
We are excited to announce the 1.10.0 release of Panel! This is the biggest step towards Panel 2.0 so far. It changes how you build Panel apps, with a new Material UI based component namespace, and where you can deploy them, with a native ASGI server. Here’s a high-level overview of the highlights:
panel.ui: A single component namespace that combines the Material UI components frompanel-material-uiwith the classic Panel components that have no Material equivalent, plus apanel migratecommand to move existing apps over.- Native ASGI server support: Panel now ships its own ASGI application.
panel serve --server fastapiand--server asgirun apps on uvicorn, and the FastAPI and Django integrations no longer depend onbokeh-fastapi,bokeh-djangoor Django Channels. - Themed server pages and a new admin dashboard: The index, login and error pages match
panel.uiand can be branded withpn.config.page_config, and the/admindashboard has been rebuilt and can be secured on its own. - On-demand resource loading: Components fetch the JavaScript libraries they need the first time they render, so
pn.extension('tabulator')is now an optional preloading hint. - Performance:
JSComponentandReactComponentrendering no longer scales quadratically with the number of components, LLM-style streaming intoMarkdownhas a constant cost per token, andTabulatorno longer pulls in the 500 KB bokeh-tables bundle.
Panel 1.10 requires Bokeh 3.10 and Python 3.12 or later, and adds support for Python 3.15.
Many thanks to the contributors to this release: @philippjfr, @hoxbro, @chiruu12, @MohammadHijjawi97, @ebarkhordar, @Kayvan-Zahiri, @ghostiee-11, @Azaya89, @rimsha152007-dotcom, @EltonChang1, @Bortlesboat, @ATL2001, @ZainnQureshii, @vignesh-chaturvedi, @IDavinciCode, @mvanhorn, @SatvikMishra08, @YashAnand69, @Cherith1222, @anshurajbisoyi98-ctrl and @Coderambling.
If you’re using Anaconda, you can install the latest version of Panel with conda install panel. If you prefer pip, use pip install panel.
panel.ui
Over the past year panel-material-ui has grown into a complete Material UI based component library for Panel, and in Panel 1.9 we aligned the classic widget API with it by introducing label, color and variant. Panel 1.10 completes the next step: the Material components are now part of Panel itself under panel.ui (#8752).
panel.ui exports every component it provides both flat and through widgets, layout, pane, chat, indicators, template and wrappers submodules, so pn.ui.Button and pn.ui.widgets.Button are the same class. Where Material UI has an equivalent, you get the Material component. Where it does not, such as Tabulator, CodeEditor, Perspective, Plotly, HoloViews or Terminal, panel.ui re-exports the classic component unchanged:
pages = {
'Inputs': ('edit_note', inputs),
'Selection': ('checklist', selection),
'Sliders': ('tune', sliders),
'Feedback': ('notifications', feedback),
'Data': ('table_chart', table),
'Code': ('code', editor),
}
menu = pn.ui.MenuList(
items=[{'label': name, 'icon': icon} for name, (icon, _) in pages.items()],
active=(0,), sizing_mode='stretch_width',
)
pn.ui.Page(
logo='images/panel_logo.png', sidebar=[menu], sidebar_width=140,
main=show_selected(menu, pages), height=440,
).servable()To see what a complete pn.ui application looks like, here is the Storm Surge Studio, one of the new apps in the gallery. It is an interactive coastal atlas for a synthetic storm, built from a pn.ui.Page with Material widgets in the sidebar, Typography, Paper and Grid layouts, and three Bokeh figures that are updated in place as you change the scenario.
The app below is rendered live in this post. There is no Python process behind this page, so the controls will not recompute the flood, but everything that runs in the browser works: open the menu in the top left to see the controls, and hover over the map, the coastal profile and the timeline. To run the full app yourself, download the notebook and run panel serve storm_surge_studio.ipynb.
An app no longer has to decide, component by component, whether to import from pn.widgets or from panel_material_ui. Every name is an explicit import, so Pyright, mypy and IDE autocompletion resolve pn.ui.Button to a real class, and together with the typing work in Panel 1.9 you get parameter completion and type checking for the whole namespace.
The classic components re-exported under panel.ui also adopt the Material look. Under the Material UI design Tabulator, CodeEditor, JSONEditor, Markdown, Terminal and others get stylesheets mapped onto the MUI palette, and the Dial, LinearGauge and Gauge indicators derive their label, needle and track colors from the theme, so they follow light and dark mode instead of rendering black on white. Modal, WidgetBox and Debugger, which had no counterpart in panel-material-ui, have new Material implementations that keep the classic API.
Opt-in, and the classic API stays
panel.ui is opt-in. import panel on its own does not import panel-material-ui or change the design, and pn.widgets, pn.layout, pn.pane and the classic templates continue to work as before. pn.ui is resolved lazily the first time you access it, and at that point Panel selects the MaterialUIDesign unless you have already chosen a design. You can also select it explicitly with pn.extension(design='material-ui').
Material and classic components can be mixed in one app, so you can migrate incrementally.
Page as the default template
pn.ui.Page is now also available as a template name, so apps written in the .servable(target=...) style can use it without constructing it explicitly (#8808):
import panel as pn
pn.extension(template='page')
slider = pn.ui.IntSlider(label='Stars', start=0, end=10, value=3)
slider.servable(target='sidebar')
pn.panel(pn.bind(lambda n: '⭐' * n, slider)).servable(target='main')The main, sidebar, header and contextbar areas are supported, and an unknown target now raises an error instead of being silently dropped. The getting started, core concepts and templates guides have been rewritten around Page.
Generated widgets follow the design
Panel generates widgets for you in several places: pn.Param for Parameterized classes, pn.interact, and the widgets created for the dimensions of a HoloViews HoloMap or DynamicMap. Previously panel-material-ui had to monkeypatch Panel’s internal mappings to make those produce Material widgets. Designs can now declare a component_mapping and widget_mapping, so under the Material UI design a param.Number becomes a Material FloatSlider and a param.Selector becomes a Material Select:
import param
class Settings(param.Parameterized):
threshold = param.Number(default=0.5, bounds=(0, 1))
mode = param.Selector(objects=['fast', 'accurate'])
pn.ui.Card(pn.Param(Settings(), show_name=False), title='Settings')An explicit widget_type always takes precedence over the mapping.
The same design hooks cover the loading spinner and the page template. A design can declare its own spinner and template variables, and the loading overlay now follows the theme’s background color, so dark themes no longer flash white while a component loads.
Migrating with panel migrate
To make moving existing apps easier, Panel 1.10 ships a codemod. Install the optional dependency and point it at a file or directory:
pip install 'panel[migrate]'
panel migrate my_app/ # report what would change
panel migrate --diff my_app/ # also print a unified diff
panel migrate --fix my_app/ # write the changesWithout --fix the command exits with a non-zero status if any file would change, so you can also use it as a CI check. Given a classic app like this:
import panel as pn
pn.extension(design='bootstrap')
button = pn.widgets.Button(name='Run', button_type='primary')
slider = pn.widgets.IntSlider(name='Stars', start=0, end=10, value=3)
pn.template.FastListTemplate(
title='Stars',
sidebar=[button],
main=[slider.rx() * '⭐'],
).servable()panel migrate --diff proposes:
-pn.extension(design='bootstrap')
+pn.extension()
-button = pn.widgets.Button(name='Run', button_type='primary')
-slider = pn.widgets.IntSlider(name='Stars', start=0, end=10, value=3)
+button = pn.ui.Button(label='Run', color='primary')
+slider = pn.ui.IntSlider(label='Stars', start=0, end=10, value=3)
-pn.template.FastListTemplate(
+pn.ui.Page(The tool rewrites classic imports to pn.ui, name= to label=, button_type and button_style to color and variant, MenuButton(split=True) to SplitButton, size to icon_size on ToggleIcon and ButtonIcon, and compatible classic templates to pn.ui.Page. Imports from panel_material_ui are rewritten to the same classes under pn.ui. Anything it cannot confirm, such as unsupported arguments, argument unpacking or template options without a Page equivalent, is left untouched and listed for manual review.
The codemod only changes Python source. Custom CSS that targets classic class names, custom Design subclasses, Jinja templates and jslink/jscallback code referencing classic model properties need to be updated by hand, and a handful of components differ in their defaults: a Material Button defaults to color='primary' and variant='contained', for example. The upgrade guide lists every parameter and default that differs between the classic and panel.ui components.
Documentation and gallery
The Panel documentation has a new homepage, is now versioned, and uses pn.ui throughout. The Component Gallery is generated from the panel-material-ui reference notebooks, the classic reference has moved to its own section, and each replaced component links back to its classic page.
The app gallery has been modernized as well, and has a few new additions, including a Gaia million star atlas that renders 1.25 million real stars across three linked views, a model serving monitor that simulates traffic surges, drift and outages against live SLO alerts, and a storm surge studio for exploring coastal flooding scenarios.
What comes next
For now the Material components are implemented in panel-material-ui, which Panel depends on and checks against a supported version range at import. In Panel 2.0 the implementation moves into Panel itself. The imports under panel.ui will stay the same, so code written against pn.ui today will not need to change.
Native ASGI Server Support
Panel apps have always run on the Bokeh server, which is built on Tornado. For many deployments that is fine, but a growing number of teams want to serve Panel apps from an existing FastAPI or Django project, or run them on the same ASGI infrastructure as the rest of their Python services.
Panel has supported this for a while, but through workarounds. The FastAPI integration added in Panel 1.5 was built on the external bokeh-fastapi package and needed several monkeypatches into its internals, and the Django integration relied on bokeh-django and Django Channels, with Panel patching their consumers. Bokeh 3.10 split the Bokeh server into a transport-independent core and added a framework-neutral ASGI application. Panel 1.10 builds its own ASGI application on top of that, and uses it for panel serve, FastAPI and Django alike (#8722).
panel serve --server
panel serve has a new --server option:
pip install uvicorn fastapi
panel serve app.py --server fastapiThe choices are tornado (the default, unchanged), asgi, which runs Panel’s ASGI application on uvicorn without importing FastAPI, and fastapi, which mounts the same application on a FastAPI app. The ASGI implementations support the same options as the Tornado server, including the admin dashboard, --static-dirs, --index, --liveness, wildcard routes, TLS and all of the authentication options. A few options that are implemented as Tornado request handlers (--rest-provider, --enable-xsrf-cookies and --num-procs) fail with an error naming the option. Session info, which used to be served by the param REST provider, now comes from a built-in /session_info endpoint on every server (#8828).
Custom endpoints declared with --plugins work on --server fastapi, where the plugin module declares a FastAPI APIRouter in a ROUTER variable instead of a list of Tornado handlers:
# plugin.py
from fastapi import APIRouter, HTTPException
ROUTER = APIRouter()
@ROUTER.get('/sum')
def sum_values(a: int = 0, b: int = 0):
if a < 0 or b < 0:
raise HTTPException(400, 'Arguments must be positive.')
return a + bpanel serve app.py --server fastapi --plugins pluginA module can declare both ROUTES and ROUTER and be served on either implementation.
To run the ASGI application under a server of your choice, or mount it in another ASGI framework such as Starlette, build it with build_asgi_app:
import panel as pn
from panel.io.asgi import build_asgi_app
def stars():
slider = pn.ui.IntSlider(label='Stars', start=0, end=10, value=3)
return slider.rx() * '⭐'
app = build_asgi_app({'/stars': stars})uvicorn app:app --workers 4The Serving on ASGI guide covers the supported options, mounting, multiple workers and running behind a proxy.
FastAPI
The public API of panel.io.fastapi is unchanged, but bokeh-fastapi is no longer needed and pip install panel[fastapi] no longer installs it:
import panel as pn
from fastapi import FastAPI
from panel.io.fastapi import add_application
app = FastAPI()
@app.get("/")
async def read_root():
return {"Hello": "World"}
@add_application('/panel', app=app, title='My Panel App')
def create_panel_app():
slider = pn.ui.IntSlider(label='Slider', start=0, end=10, value=3)
return slider.rx() * '⭐'Panel now installs itself as a middleware that claims only the paths it owns and hands everything else to FastAPI, so it no longer matters whether you call add_applications before or after declaring your own routes, and you can call it more than once. The integration serves the same endpoints as panel serve, including the autoload.js, metadata and static resource endpoints that bokeh-fastapi could not serve, as well as /liveness, static_dirs and the index page. Wildcard routes populate pn.state.route_params, and a prefix argument serves all apps below a common path.
Authentication on every server
Authentication is configured with the same arguments everywhere. basic_auth, oauth_provider and the related options are accepted by panel serve, pn.serve, panel.io.fastapi.add_applications and panel.io.django.get_asgi_application:
add_applications(
{"/panel_app": create_panel_app},
app=app,
oauth_provider='azure',
oauth_key='...',
oauth_secret='...',
oauth_encryption_key='...',
cookie_secret='...',
)On the ASGI server Panel runs its existing OAuth handlers unmodified, so all of the supported providers, PKCE and token refresh behave the same as on Tornado. Session cookies are signed the same way on both servers, so with a shared cookie_secret and oauth_encryption_key a user logged in on a Tornado process is accepted by an ASGI process and vice versa. You can run both behind one load balancer, or move a deployment from one to the other without logging everyone out.
Django
The Django integration has been rewritten around Django’s own ASGI application. Panel serves the app documents, their websockets and its static resources, and hands every other request to Django, including your views, the admin and your own static files. channels, daphne and bokeh-django are no longer involved:
# project/asgi.py
import django
django.setup()
import myapp.pn_app as pn_app
from panel.io.django import autoload, get_asgi_application
application = get_asgi_application([
autoload('myapp', pn_app.app),
])With panel.io.django added to INSTALLED_APPS, python manage.py runserver serves the project with uvicorn during development, and in production you run it with any ASGI server, e.g. uvicorn project.asgi:application. Existing projects keep the document, autoload, directory, with_request and with_url_args helpers; the routing.py module, the bokeh_apps URL patterns and STATICFILES_DIRS = [bokehjsdir()] can be deleted. The Django how-to guide has a step-by-step section on migrating from bokeh-django.
Server Pages and the Admin Dashboard
Besides your applications, the Panel server renders a few pages of its own: the index page listing the applications, the login and logout pages of the basic auth provider, and the error pages. These have been restyled to match panel.ui, share one stylesheet that follows the operating system’s dark mode, and the index page search now also matches paths (#8802).
They can also be branded. If your apps already set class defaults on pn.ui.Page, such as a theme_config, logo or title, in a module passed to --setup, the server pages pick those up automatically. To configure them separately, pass a dictionary to pn.config.page_config, or a JSON string or file to panel serve --page-config:
{
"title": "Acme Analytics",
"logo": {"light": "assets/logo.svg", "dark": "assets/logo_dark.svg"},
"theme_config": {
"palette": {"primary": {"main": "#00695c"}},
"shape": {"borderRadius": 12}
}
}panel serve apps/*.py --page-config page_config.json
The /admin dashboard has been rebuilt with panel.ui as well (#8805). It is now a Page with sidebar navigation between the overview, timeline, user profiling and log pages, which are only built when you open them. The log view gained per-session filters, case-insensitive text filtering and buttons to clear the filters and download the log, and the dashboard follows the same page_config theming as the other server pages.

Previously the admin dashboard was only as protected as the apps it monitored. It can now be secured on its own: --admin-password (or PANEL_ADMIN_PASSWORD) puts only the admin endpoint behind a login page, and --admin-users (or PANEL_ADMIN_USERS) restricts it to specific users authenticated via --basic-auth or --oauth-provider:
panel serve app.py --admin --oauth-provider github ... --admin-users philippjfr hoxbroOn-demand Resource Loading
Until now, a component that wraps a JavaScript library only rendered if you declared it up front, e.g. with pn.extension('tabulator'). Forgetting the argument left you with an empty component and a warning in the log, and it was impossible to get right for components created in a callback after the page loaded. Components now carry the list of resources they need, and the browser fetches whatever is missing the first time the component renders (#8745). pn.extension('tabulator') still works and preloads the library, but it is no longer required.
Performance
Panel 1.10 contains a number of rendering performance improvements that you get without changing any code:
JSComponentandReactComponent: Rendering previously did layout work that scaled with the square of the number of components, and re-renders could remount entire React subtrees. Both are gone, which benefits everypanel-material-uiand therefore everypn.uiapp (#8795, #8786).- Streaming text: Streaming into a
MarkdownorHTMLpane, as chat interfaces do with LLM responses, now coalesces updates, re-renders only the trailing Markdown block and sends HTML splices instead of re-sending the tail. The rendering cost per token is roughly constant, so long responses keep streaming at a steady rate instead of slowing down as they grow (#8800). Tabulator: The table no longer requires the 507 KB bokeh-tables bundle, which is now only loaded when aDataTableorDataCubeis rendered (#8837).
Other Highlights
DataFrame pane
The DataFrame pane now renders any DataFrame supported by Narwhals, including Polars, PyArrow, Modin and cuDF, and has a refreshed Material style that adapts to light and dark themes (#8744):
import polars as pl
pn.pane.DataFrame(pl.DataFrame({
'component': ['Button', 'Select', 'Tabulator', 'CodeEditor'],
'pn.ui implementation': ['Material', 'Material', 'classic', 'classic'],
}))/Users/philippjfr/development/panel/panel/util/__init__.py:157: UserWarning: Comparisons with None always result in null. Consider using `.is_null()` or `.is_not_null()`.
equal = bool(v==default)
Notebooks
Component libraries that register as anonymous AMD modules (Tabulator, ECharts, Vega, KaTeX, DeckGL and JSONEditor) now load correctly in environments that use RequireJS, such as classic Jupyter notebooks and VS Code. Panel also adds a Jupyter server extension endpoint so a notebook can load Panel’s resources without reaching a public CDN, falling back to the CDN with a visible warning if the endpoint is unreachable (#8762, #8848).
Air-gapped Pyodide apps
Apps converted with panel convert can now load Pyodide from a CDN of your choice with pn.config.pyodide_cdn_root or the PANEL_PYODIDE_CDN_ROOT environment variable, which makes them usable in air-gapped environments (#8784). Converted apps also no longer install and patch pyodide-http, since current Pyodide supports requests and urllib3 natively (#8777).
Robustness
This release fixes a long list of edge cases. Tabulator edits now send the old and new value with the edit event, fixing on_edit callbacks that reported stale values and edits under remote pagination that could corrupt a sibling row (#8731). Busy state no longer echoes between server and client or leaks across threads, which could leave the Page progress bar visible while the server was idle (#8780). pn.cache now takes row order, dtypes and value types into account when hashing, so reordered DataFrames, same-named closures and values like 0 and False no longer share cache entries, and a new approximate=False option hashes all of a large input instead of a sample (#8759, #8821). A component rendered in several places now shares one reference-counted model per root, so every view keeps receiving updates (#8830).
Deprecations and Removals
--rest-provider,--rest-endpoint,pn.state.publishand thepanel.io.restmodule are deprecated and will be removed in Panel 2.0. Use--pluginsto add custom endpoints instead (#8828).pn.widgets.DataFrameis deprecated in favor ofTabulatorand will be removed in Panel 2.0.pn.Paramnow mapsparam.DataFrametoTabulator.bokeh-fastapiandbokeh-djangoare no longer used.RoutingConfiguration,DjangoBokehConfigand the Channels consumers raise an error pointing to the new API.- The LangChain callback handler, deprecated since 1.8.0, has been removed (#8689).
- The
--disable-http-patchoption ofpanel converthas been removed (#8777). - Support for Bokeh 3.8 and 3.9 and for Python 3.10 and 3.11 has been dropped (#8716).
Changelog
For the full list of changes, see the Panel 1.10.0 changelog.
🚀 Features
- Add the
panel.uinamespace, which exports every Material component frompanel-material-uiand the classic components without a Material equivalent (e.g.Tabulator,CodeEditor,Terminal), both at the top level (pn.ui.Button) and throughwidgets,layout,pane,chat,indicators,templateandwrapperssubmodules. The namespace is loaded lazily soimport panelstays as light-weight as before, and importing it selects theMaterialUIDesignunless a design was chosen explicitly (#8752) - Add Material implementations of
Modal,WidgetBoxandDebuggerthat retain the classic API (#8752) - Add the
panel migratecommand (installed with thepanel[migrate]extra), which rewrites classic imports topn.ui,name=tolabel=,button_type/button_styletocolor/variant,MenuButton(split=True)toSplitButtonand compatible templates topn.ui.Page, and reports anything it cannot rewrite safely. Without--fixit only reports the changes, and--diffprints them as a unified diff (#8752) - Add native ASGI server implementations for FastAPI and Django built on Bokeh 3.10’s transport-neutral server layer, dropping the
bokeh-fastapiandbokeh-djangodependencies.panel servegains a--serveroption acceptingtornado(the default),fastapiandasgi, and wildcard routes,pn.state.route_params, static directories and OAuth work the same on every server (#8722) - Load component resources on demand, so components such as
Tabulator,EChartsorPerspectiverender without first being declared viapn.extension(...), including when they are created in a callback after the page has loaded. Declaring an extension is now only a preloading and bundling hint (#8745) - Support
template="page"inpn.extensionandpn.config.template, resolving topn.ui.Page, with.servable(target=...)routing into itsmain,sidebar,headerandcontextbarareas (#8808) - Add
pn.config.page_config(also settable withpanel serve --page-configand thePANEL_PAGE_CONFIGenvironment variable) to enable branding of the server index, login, logout and error pages with the same theme, logo, favicon and title options aspn.ui.Page. Pages also fall back to the class defaults ofPage, so a brand module passed to--setupthemes apps and server pages at once (#8802) - Add access control for the admin dashboard with
--admin-password(PANEL_ADMIN_PASSWORD), which secures only the admin endpoint behind a login page, and--admin-users(PANEL_ADMIN_USERS), which restricts it to specific users of--basic-author--oauth-provider(#8805) - Render any DataFrame-like object supported by Narwhals (polars, pyarrow, modin, cuDF, …) in the
DataFramepane (#8744) - Add a Jupyter server extension resource endpoint so notebooks running a plain kernel can load Panel and component resources without access to a public CDN, falling back to the CDN with a visible warning if the endpoint is unreachable (#8762)
- Allow configuring the CDN that converted apps load Pyodide from with
pn.config.pyodide_cdn_rootand thePANEL_PYODIDE_CDN_ROOTenvironment variable, enablingpanel convertapps in air-gapped environments (#8784) - Add an
approximateargument topn.cache; settingapproximate=Falsehashes all of a large DataFrame or array instead of a sample of 100,000 rows (#8759) - Add
pn.ui.Param,pn.ui.HoloViewsandpn.ui.panel, which always generate Material UI widgets regardless of the active design, whilepn.Paramandpn.pane.HoloViewskeep generating widgets for the active design (#8883) - Add a
notebook_resourcesconfig option (also settable with thePANEL_NOTEBOOK_RESOURCESenvironment variable) to choose where notebooks load resources from, for example when the kernel runs in a different environment than the Jupyter server (#8889)
✨ Enhancements
- Rebuild the
/admindashboard withpanel.uias aPagewith sidebar navigation, lazily built pages, per-session log filters, literal case-insensitive text filtering, Clear filters and Download log buttons, andpage_configtheming (#8805) - Add
Design.component_mappingandDesign.widget_mappingso designs can substitute the widgets Panel generates inParam,interactandHoloViewswidgets (#8752) - Route the loading indicator through the
Design, allowing designs to declare their own spinner, and add amaterialspinner whose overlay follows the theme so dark themes no longer flash white (#8752) - Add
config.set_if_unset(),panel.theme.resolve_design()and string values forconfig.design(e.g.config.design = 'material-ui') (#8752) - Rename
sizetoicon_sizeonToggleIconandButtonIconinpanel migrate, flagging calls that size the icon throughwidthorheightfor review (#8854) - Theme classic components such as
Tabulator,CodeEditor,JSONEditor,Markdown,Terminal,Dial,GaugeandLinearGaugefrom the MUI palette under theMaterialUIDesign(#8752) - Share and reference count a single model per root when the same component is rendered in several places, so every view keeps receiving updates and removing it from one parent no longer breaks the others (#8830)
- Serve session info from a built-in
/session_infoendpoint on every server implementation instead of via theparamREST provider (#8828) - Update
value_throttledwhen a slider value is set from Python (#8712) - Yield to the event loop when a periodic callback overruns its period (#8755)
- Keep the
countandtimeoutbudgets of a periodic callback across a period change (#8757) - Converted Pyodide apps no longer install and patch
pyodide-http, since current Pyodide supportsrequestsandurllib3natively; the--disable-http-patchoption was removed (#8777) - Select the
MaterialUIDesignper session when an app usespanel.ui, so classic apps served from the same process keep their design, and resolve the design and theme of components without an explicit design when they render rather than when they are created (#8877) - Load BokehJS, Panel’s component CSS and extension bundles such as
panel-material-uifrom the Jupyter server extension endpoint in notebooks, instead of the CDN (#8884) - Expose the
generate_paletteandlinear_gradientpalette helpers inpanel.ui.theme(#8868)
⚡ Performance
- Speed up
ReactiveESMandReactComponentrendering, eliminating layout work that scaled with the square of the number of components and React subtree remounts on re-render; this benefits everypanel-material-uiapp (#8795) - Make streaming into
MarkdownandHTMLpanes roughly constant in rendering cost per token by coalescing updates, re-rendering only the trailing Markdown block and sending HTML splices rather than re-sending the tail (#8800) - Render
Tabulatorwithout the bokeh-tables bundle (507 KB minified), which is now only loaded when aDataTableorDataCubeis rendered, in notebooks or when requested withpn.extension('bokeh-tables')(#8837) - Lay out ESM children once per mount instead of once per child (#8786)
- Replace the
gl-matrixdependency in the VTK axes with two small helpers (#8835)
🎨 UI Improvements
- Restyle the server index,
panel convertindex, error, kernel error, login and logout pages to matchpanel-material-ui, so they share one stylesheet that follows the operating system’s dark mode. Search on the index page now also matches paths and?search=is applied on load (#8802) - Restyle the
DataFramepane following Material Design table conventions with theme-aware hover and divider colors (#8744) - Improve
Dialrendering and theming (#8796) - Keep
Tabulatorhidden until it is built and styled to avoid a flash of unstyled content (#8799) - Use a single loading indicator in converted apps (#8827)
🐛 Bug Fixes
- Make
Tabulatoredits robust by sending the edited value and old value with the edit event, fixingon_editcallbacks that reported stale values and edits under remote pagination that could corrupt a sibling row (#8731) - Ignore stale
Tabulatordata and server-side stream echoes (#8797) - Prevent
Tabulatorpage reset when clicking Last on a shrunk container (#8738) - Fix the
Tabulatorkeywords header filter raising an error whenheader_filters=True(#8726) - Honor
matchAllinTabulatorkeywords header filters when pagination is not remote (#8842) - Show the
Tabulatorloading spinner when the table has no columns (#8807) - Fix component libraries (Tabulator, ECharts, Vega, KaTeX, DeckGL, JSONEditor) failing to load in notebooks with RequireJS, i.e. classic Jupyter notebook and VS Code, including on networks that block the public CDNs, where the bundled copies of the libraries are now loaded instead (#8762, #8848)
- Stop busy state from echoing between server and client and from leaking across threads, which left busy indicators such as the
Pageprogress bar visible while the server was idle (#8780) - Make busy state cleanup robust to state resets, missing event loops and failed submits (#8838)
- Fix several cache hashing collisions: reordered DataFrame rows, frames differing only in dtypes, same-named closures and scalars of different types (e.g.
0andFalse) no longer share cache entries (#8759) - Hash value-type arguments such as
UUID,Decimal,Fraction, IP addresses, enums and dataclasses by value inpn.cacheinstead of byid(), which could return another call’s result (#8821, #8843) - Cache functions that have no module file (#8770)
- Ensure stylesheet models are not reused across sessions (#8740)
- Scope reactive stylesheets to their document (#8769)
- Keep notebook stylesheets inline after updates (#8787)
- Stop shared ESM watchers when they are no longer used (#8775)
- Skip JS callbacks on properties the rendered model does not have (#8825)
- Ignore click events on disabled buttons (#8823)
- Notify
MenuButtoncallbacks when the same item is selected repeatedly (#8810) - Keep
TextInputvalue_inputin sync whenvalueis set from Python, so pressing Enter no longer reverts the value (#8782) - Fix
DatetimeSliderwhenstartandendfall on the same day (#8743) - Restore rejected boundary values on repeated throttled
Paramslider changes (#8851) - Fix nested Plotly arrays being lost when syncing across clients (#8758)
- Clean up
GridSpecchildren when deleting cells (#8736) - Parse negative integers in URL query parameters as integers (#8790)
- Keep URL query values as strings when syncing
Stringparameters (#8814) - Abbreviate
Parameterizedobjects inside lists in reprs (#8792) - Reject unparsable
schedule_taskperiods instead of looping (#8812) - Strip IPython line magics when serving notebooks (#8781)
- Use CDN resources in exported notebooks (#8776)
- Treat converted Pyodide apps as served (#8833)
- Deliver every message, including binary data, to Python in apps converted with
--to pyodide-worker(#8844) - Handle the Windows
ProactorEventLoop(#8815) - Ensure the server thread shuts down fully before exiting (#8741)
- Fix the inverted
apple_iconcondition in the base template that emitted an emptyapple-touch-iconlink (#8752) - Resolve the design from the document each model was rendered into when a parameter changes, so a component shown in sessions with different designs no longer receives another session’s stylesheets (#8879)
- Keep the configured design on panes rendered by
ParamFunction,ParamMethodandinteract, which silently fell back to theNativedesign (#8895) - Omit the import map of components loaded from a bundle URL and detect Material UI use from the bundle’s exports, so
panel-material-uicomponents keep their styles (#8886) - Prevent resetting a
param.Eventon aReactiveESMcomponent from running its watchers a second time withFalse, which madepn.bind(fn, button)callfntwice per click (#8890) - Re-measure
Tabulatorcolumns after web fonts load so headers are no longer truncated (#8896) - Serialize
LiteralInputstring values as JSON whenserializer='json'(#8894) - Freeze the
_scriptsdeclarations ofReactiveHTMLclasses at class creation, so mutating them on an instance raises anAttributeErrorinstead of leaking into other instances (#8861) - Only load resources from the Jupyter server extension endpoint when Panel’s server extension launched the kernel, fixing 404s in other notebook frontends such as nteract (#8863)
- Dispatch changes made inside
hold()to subscribers of a sessionless Document, so embedders that sync the Document over their own transport receive updates toReactiveESMcomponents and layout children (#8862) - Detect the format of raw image bytes from the file signature, so JPEG, GIF and WebP bytes passed to
pn.pane.Imageare no longer encoded as PNG, and fix the dimensions of lossy WebP images (#8875) - Detect local media files with uppercase extensions, e.g.
pn.pane.Video('CLIP.MP4')(#8867) - Fix an off-by-one error in
Playerin notebooks (#8860)
⚙️ Compatibility
- Add compatibility with Bokeh 3.10, which is now the minimum supported version, and drop support for Python 3.10 and 3.11 (#8716)
- Add support for Python 3.15 (#8767)
- Upgrade to the latest JupyterLite and Pyodide (v314.0.7) (#8763)
- Ignore the NumPy 2.5
ndarray.shapedeprecation warning raised by VTK (#8729) - Annotate subclasses in
_descendentsfor mypy with Param 2.4 (#8801) - Support Bokeh 3.10.2 (#8898)
- Raise the minimum
panel-material-uiversion to 0.16.6 (#8899)
⚠️ Deprecations and Removals
--rest-provider,--rest-endpoint,pn.state.publishand thepanel.io.restmodule are deprecated and will be removed in Panel 2.0; use--pluginsto add custom endpoints instead./rest/session_infois kept as a deprecated alias of/session_info, and--rest-provideris not supported on the ASGI servers (#8828)pn.widgets.DataFrameis deprecated in favor ofTabulatorand will be removed in Panel 2.0;Paramnow always mapsparam.DataFrameparameters toTabulator(#8752)- The LangChain callback handler, deprecated since 1.8.0, has been removed (#8689)
- The
bokeh-fastapiandbokeh-djangodependencies have been dropped; see the upgrade guide for migrating Django integrations (#8722) - The
--disable-http-patchoption ofpanel converthas been removed (#8777) PercentEditorandTimeEditoronTabulatornow use Tabulator’s native input editor, and in served apps aDataFramewidget orhv.Tablethat first appears after the page has loaded requirespn.extension('bokeh-tables')(#8837)- Calling
.servable()with an unknowntargeton thepagetemplate now raises aValueErrorinstead of being silently dropped (#8808) - Components without an explicit design now have
design=Noneand use the configured design when they render, so a design configured after a component is created but before it is rendered applies to it (#8877)
📚 Documentation
- Add a new homepage and versioned documentation (#8753)
- Use
pn.uithroughout the docs and gallery, generate the Component Gallery from thepanel-material-uireference notebooks and move the classic reference toreference/classic/(#8752) - Move the narrative docs to
pn.ui.Pageand rewrite the getting started, core concepts and templates guides around it (#8808) - Modernize the gallery examples and add new apps, including a Gaia million star atlas, a model serving monitor and a storm surge studio (#8798)
- Add a how-to guide for theming server pages with
page_config(#8802) - Document the admin dashboard pages, plugins and access control (#8805)
- Document the
--serveroption and per-server differences for endpoints, wildcard routes and static files (#8722) - Document approximate hashing of large inputs in the caching guide (#8759)
- Document the Markdown docs and
llms.txt(#8715, #8730) - Add a note on configuring the Param mypy plugin (#8734)
- Surface the guide on using Panel with an AI assistant in the How-to section (#8846)
- Explain running UI tests with Firefox and WebKit in the developer guide (#8809)
- Note that cell editing takes precedence over row-click selection in the
Tabulatorreference (#8817) - Only show the Material version banner on classic reference pages (#8845)
- Fix links to the Tabler icons site (#8765)
- Fix dead Extensions links in the gallery and use
label=in the README example (#8855) - Fix broken relative links in how-to guides (c3fa877)
- Fix unresolved cross-page anchor links (#8836)
- Add
panel.uitheming, styling and Material UI component how-tos (#8892) - Migrate the tutorials to
pn.ui.Pageand fix broken examples (#8891) - Bring the parameter lists of the pane and layout reference notebooks in line with the code and add examples for many undocumented features (#8882)
- Fix the broken Troubleshooting card icon (#8865)