Panel 1.10.0 is a major step towards Panel 2.0. The headline is panel.ui, a single namespace that combines the Material UI components from panel-material-ui with the classic Panel components that have no Material equivalent, together with a panel migrate codemod that rewrites existing apps to use it. Panel now also ships its own native ASGI server, replacing bokeh-fastapi and bokeh-django, so panel serve --server fastapi|asgi works out of the box and OAuth behaves identically across transports.
Components no longer have to be declared up front: a component that wraps a JavaScript library now loads its resources on demand the first time it renders, so forgetting pn.extension('tabulator') no longer results in a blank component. The server pages (index, login, logout and error pages) and the /admin dashboard have been restyled to match panel-material-ui and can be branded with the new pn.config.page_config, and the admin dashboard can now be secured independently of the apps it monitors.
The release also contains significant performance improvements: rendering of JSComponent and ReactComponent (and therefore all of panel-material-ui) no longer scales quadratically with the number of components, streaming into Markdown and HTML panes has a roughly constant rendering cost per token so long responses stream at a steady rate, and Tabulator no longer pulls in the 500 KB bokeh-tables bundle. It also includes a long list of robustness fixes, including for Tabulator edits, busy state tracking, notebook resource loading under RequireJS, caching and periodic callbacks.
This release requires Bokeh 3.10 and drops support for Python 3.10 and 3.11, while adding support for Python 3.15. It also deprecates the REST providers, pn.state.publish and pn.widgets.DataFrame, and removes the long-deprecated LangChain callback handler.
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, @Coderambling, @Jojoussef and @wolfgang-aura.
🚀 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 using it selects theMaterialUIDesignfor the current session unless a design was chosen explicitly (#8752, #8877) - Add Material implementations of
Modal,WidgetBoxandDebuggerthat retain the classic API (#8752) - 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 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.--checkand--diffpreview the changes without writing them (#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)
✨ 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) - 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) - Expose the
generate_paletteandlinear_gradientpalette helpers inpanel.ui.theme(#8868) - 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)
⚡ 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) - Re-measure
Tabulatorcolumns once web fonts have loaded, so headers are no longer truncated in apps with a custom font, and stop thecontainer_popupspacer column from showing a sort arrow (#8896) - 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)
- Stop the reset of a
param.Eventon aReactiveESMcomponent from re-triggering watchers, which made functions bound to apn.ui.Buttonrun a second time withFalse(#8890) - Freeze
ReactiveHTML_scriptsat class creation so instances no longer share and overwrite each other's scripts (#8861) - 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) - Allow setting
DateRangeSliderandDatetimeRangeSlidervaluetoNone(#8901) - Serialize string values of
LiteralInputas JSON whenserializer='json'(#8894) - 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) - Raise a
ValueErrorwhenpn.state.location.syncis asked to sync a constant parameter such asvalue_throttled, instead of silently ignoring the URL value (#8902) - 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)
- 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)
- Only load resources from the Jupyter server extension endpoint when Panel's server extension launched the kernel, fixing 404s for Panel's JS and component resources 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 with nonsensical dimensions, 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) - Fix the inverted
apple_iconcondition in the base template that emitted an emptyapple-touch-iconlink (#8752)
⚙️ 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 compatibility with the upcoming Bokeh 3.10.2 (#8898)
- Require
panel-material-ui0.16.6 or later (#8899) - Add support for Python 3.15 (#8767, #8850)
- 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)
⚠️ 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)- 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) - Calling
.servable()with an unknowntargeton thepagetemplate now raises aValueErrorinstead of being silently dropped (#8808)
📚 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) - Migrate the tutorials to
pn.uiand fix broken examples (#8891) - Add
pn.uihow-to guides for theming, palettes, typography, dark mode, branding, icons and building Material UI components (#8892) - 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) - Bring the parameter lists of the pane and layout reference notebooks in line with the code and add examples for many undocumented features (#8882)
- 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 (#8773)
- Fix unresolved cross-page anchor links (#8836)
- Fix the broken Troubleshooting card icon (#8865)