Panel 1.4.0 Release

Release announcement for Panel 1.4

Philipp Rudiger


March 28, 2024

What is Panel?

Panel is an open-source Python library that lets you easily build powerful tools, dashboards and complex applications entirely in Python. It has a batteries-included philosophy, putting the PyData ecosystem, powerful data tables and much more at your fingertips. High-level reactive APIs and lower-level callback based APIs ensure you can quickly build exploratory applications, but you aren’t limited if you build complex, multi-page apps with rich interactivity. Panel is a member of the HoloViz ecosystem, your gateway into a connected ecosystem of data exploration tools.

New release!

We are very pleased to announce the 1.4.0 release of Panel! This release packs many exciting new features, specifically:

  1. Add EditableTemplate to support dashboard builder UI in Jupyter (#5802)
  2. Add ChatAreaInput as default text input widget for ChatInterface (#6379)
  3. Add NestedSelect widget (#5791, #6011)
  4. Improve --autoreload by using watchfiles and selectively reloading packages (#5894, #6459)
  5. Add Panel tutorials (#5525, #6208, #6212, #6388, #6425, #6466, #6491)
  6. Add DateRangePicker widget (#6027)
  7. Add Feed layout and use it as a layout for ChatFeed (#6031, #6296)
  8. Add WebP pane (#6035)
  9. Add ButtonIcon (#6138)
  10. Add Textual pane (#6181)

We really appreciate all the work that went into this release and especially want to call out @MarcSkovMadsen’s effort in putting together the new tutorial materials. There’s more work to do but it’s a huge step forward and we’re excited to hear your feedback. We want to extend a special thanks to our amazing new crop of new contributors including @atisor73, @OSuwaidi, @suryako, @Davide-sd, @doraaki, @mayonnaisecolouredbenz7, @CTPassion, @j01024, @l3ender and @Coderambling. Next we want to recognize our returning contributors @vaniisgh, @cdeil, @limx0m, and @TheoMaturin, and finally the dedicated crew of core contributors which include @maximlt, @Hoxbro, @MarcSkovMadsen, @ahuang11, @mattpap and @philippjfr.

If you are using Anaconda, you can get latest Panel with conda install panel , and using pip you can install it with pip install panel.


Last year we decided to embark on a project to completely re-architect our documentation leaning heavily on the Diataxis framework. In doing so we got rid of the long form user guides and migrated most of the material into How-to guides. Overall the feedback about this change was very positive as it became easier to find what you were looking for, but it also made it more difficult for new users to learn about Panel.

The new tutorials aim to address this gap by providing a guided set of lessons that aim to get you from a complete novice to an advanced user. The tutorial is split into Basic, Intermediate and Advanced sections and is structured as a series of lessons culminating in topic focused guides on building specific applications, e.g. a chat app, monitoring dashboard or a to-do app.

Designing meaningful and engaging lessons is a huge undertaking so we hope to gather your feedback to continue refining the material. We want to especially thank Marc Skov Madsen for putting such a tremendous amount of effort into these materials.

Dashboard Builder UI

The biggest new feature in this release is the addition of a dashboard builder UI that allows building a dashboard layout entirely using a drag and drop interface. Our aim has always been to empower users to quickly share their analyses and results with others and the dashboard builder UI allows building applications without even importing Panel at all. The UI is built on top of a new template component - the EditableTemplate. This template leverages interact.js and Muuri to provide a smooth user experience when laying out components on the page. Let us see it in action:

As the video demonstrates the dashboard builder allows us to take a notebook with or without Panel components re-arrange, delete and move around cell outputs and markdown cells to build a dashboard.


In previous versions, Panel’s autoreload feature was a handy tool for developers, allowing them to see changes in their applications in real time without the need for manual refreshes. In this release we have outsourced the detection of changes to the watchfiles, not only have we improved the speed of file detection changes, but we have also enhanced the reliability and responsiveness of the autoreload process. To get the benefits of this improved autoreload experience install watchfiles with pip or conda.

As part of the improvements we have already improved the handling around of reloading of local modules and entire packages. The improved autoreload mechanism now offers more robust support for detecting changes within your project’s modules and packages, ensuring that any update, no matter how small or where it occurs, will trigger a reload.

Chat Improvements

As the world of LLMs and chat bots built around them continues to advance we are working hard on building the simplest and most feature rich Chat interface with out-of-the-box support for multi-modal outputs and much more.

The two main enhancements of the ChatInterface in this release were the addition of two new base components:


The Feed layout added to Panel makes it possible to build a (near) infinitely scrollable layout making it possible for us to support chats with huge message histories containing 100s or 1000s of messages without having to render it all at once.


The old TextInput and TextArea used as the text entry widget for the ChatInterface were providing a sub-optimal user experience. Therefore we introduced the ChatAreaInput with behavior closer to what you’re used to from familar chat interfaces like OpenAI including send on <Enter> and multi-line input using <Shift>+<Enter> key combinations. We aim to continue refining this component over time:'Start a chat...')

Other enhancements

There are a whole host of other improvements for the chat components in this release:

  • Every part of a ChatMessage can now be styled
  • Addition of more default avatars
  • The ability to add custom objects to the header and footer of a ChatMessage
    pn.pane.Markdown(f'```css\n{stylesheet}```', margin=0, height=300, styles={'overflow-y': 'scroll'}),
        "Style me up!",
        header_objects=['I am a custom header'],
        footer_objects=['I am a custom footer'],
        stylesheets=[stylesheet], width=600

Reactive Expressions & References

In Panel 1.3.0 we introduced new concepts around reactive expressions using the param.rx API and the ability to bind reactive references to parameters. This release continues to build on this support in a number of ways:

(Asynchronous) Generator Expressions

Streaming in Panel has long been possible by registering periodic callbacks, but as we are moving more and more towards a more reactive approach to writing applications we have been improving the support for building applications with (asynchronous) generators. If you upgrade to Param 2.1 you can now use rx expressions that are driven by a generator.

To provide a simple demonstration of this approach let us build a generator that emits a value every 0.1 seconds. By wrapping the generator in pn.rx we can now perform standard arithmetic on this dynamic value, e.g. by adding an offset derived from a widget value:

def gen():
    while True:
        yield np.random.randn()

offset = pn.widgets.FloatSlider(value=0, end=5, name='Offset')

    pn.widgets.Number(value=pn.rx(gen)+offset, format='{value:.3f}', width=250, font_size='36pt')

The ability to perform operations on dynamic values derived from generators, parameter values and widgets in this way provides a concise way to express complex data pipelines in a declarative way and thanks to support for binding the reactive references you can easily tie these dynamic values to visual outputs. To discover more about this read some of the new explanation materials on reactivity in Panel and references in Param.

Skipping updates

One sticking point when working with reactive functions created using the pn.bind API was that there was no way to signal that you did not want to update the output until some condition was met. In Param 2.1 and Panel 1.4 it is now possible to Skip updates by raising a param.Skip exception.

A good example of this is a form that gathers some inputs but should not run until a button is clicked. In our add callback we return Skip until the button triggers a calculate event at which point we yield a message to say we are Calculating and then yield the result:

a_slider = pn.widgets.FloatSlider()
b_slider = pn.widgets.FloatSlider()
button = pn.widgets.Button(name='Calculate')

def add(a, b, calculate):
    if not calculate:
        return pn.param.Skip
    yield '**Calculating...**'
    yield f'**{a} + {b} = {a+b:.1f}**'

pn.Column(a_slider, b_slider, button, pn.bind(add, a_slider, b_slider, button));

New Components

No new Panel release would be complete without the addition of at least a few new components. In addition to some of the new Chat components we also have a few new panes and widgets for you.


Textual is a Rapid Application Development framework for Python allowing users to build so called TUIs. As the popularity of this framework has grown and more and a growing number of tools offer Textual UIs we decided to explore whether it would be possible to embed these apps inside Panel, and quickly the Textual pane was born - wrap any Textual application in Panel and use it from within a notebook or in your standalone application:

New widgets

Every new Panel release must come with at least a few new widgets, in this release we have three main new additions for you the NestedSelect widget to simplify selection of values with a hierarchical relationship, the DateRangePicker, a simplified version of the DatetimeRangePicker without the time selection and the ButtonIcon.


The NestedSelect widget makes it possible to express hierarchical relationship either using a static definition or by providing a callback to generate the valid values:

nested_select = pn.widgets.NestedSelect(
        "Africa": {
            "Ghana": ["Accra", "Kumasi", "Tamale"],
            "Nigeria": ["Abuja", "Kano", "Ibadan", "Lagos"],
        "Asia": {
            "Thailand": ["Bangkok", "Chiang Mai", "Nakhon Ratchasima"],
            "Vietnam": ["Da Nang", "Hanoi", "Ho Chi Minh City"],
    levels=["Continent", "Country", "City"],

pn.Row(nested_select, pn.pane.ParamRef(nested_select.param.value));


The DateRangePicker widget provides a simple but welcome addition to the widget pantheon as the smaller brother of the DatetimeRangePicker.

pn.widgets.DateRangePicker(value=(, 11, 17),, 3, 27)), name='DateRangePicker');


The new ButtonIcon is to the ToggleIcon what Button is to the Toggle widget. Sometimes you just want to be able to click on an icon and trigger an event:

    pn.widgets.ButtonIcon(icon="heart", size="8em", description="Favorite"),
    pn.widgets.ButtonIcon(icon="clipboard", active_icon="check", size="8em", description="Copy"),


The Panel 1.4.0 release was quite feature packed but we already have a number of major new features in the pipline.

ESM Components

In Panel 1.5.0 we plan to finally provide a more robust replacement for ReactiveHTML based on ESM modules with a modern development experience including hotreloading, ability to import JS modules and React based components.

Session Handling & Scaling

Recently we found various opportunities for optimizing and parallelizing session creation and session handling including moving the session creation to threads and opening up the ability to re-connect to a session if the websocket is closed.



  • Add EditableTemplate to support dashboard builder UI in Jupyter (#5802)
  • Add ChatAreaInput as default text input widget for ChatInterface (#6379)
  • Add NestedSelect widget (#5791, #6011)
  • Add Panel tutorials (#5525, #6208, #6212, #6388, #6425, #6466, #6491)
  • Add DateRangePicker widget (#6027)
  • Add Feed layout and use it as layout for ChatFeed (#6031, #6296)
  • Add WebP pane (#6035)
  • Add ButtonIcon (#6138)
  • Add Textual pane (#6181)


  • Improve --autoreload by using watchfiles and selectively reloading packages (#5894, #6459) Load loading indicator from file instead of inlining (#6112)
  • Allow providing additional stylesheets in card_params (#6242)
  • Add scroll options to permanently toggle on layouts (#6266)
  • Allow choosing position of frozen columns on Tabulator (#6309)
  • Add help message on ChatFeed (#6311)
  • Ensure CSS can be applied to every aspect of ChatMessage (#6346)
  • Add HoloViz logos as ChatMessage avatars (#6348)
  • Add gap parameter to FlexBox (#6354)
  • Set default step of DatetimeRangeSlider to 1 minute (#6373)
  • Add support for passing objects reference to FlexBox (#6387)
  • Allow editable sliders to be embedded (#6391)
  • Add message into css_classes to ChatMessage markup (#6407)
  • Allow appending objects to the ChatMessage header & footer (#6410)
  • Add ability to declare icon label (#6411)
  • Add title and settings and fix datetime to Perspective (#6482)
  • Warn user if loading extension in VSCode or Colab without jupyter_bokeh (#6485)
  • Throttle updates to Boolean indicators (#6481)
  • Add ParamRef baseclass for ParamFunction and ParamMethod (#6392)
  • Add ability to Skip Param<Ref|Function|Method> updates (#6396)
  • Add Param<Ref|Method|Function> and ReactiveExpr to panes module (#6432)
  • Set up param.rx display accessor on import (#6470)
  • Allow using Carto tiles in DeckGL (#6531)
  • Improve VTKJS binary serialization (#6559)
  • Ensure component CSS is pre-loaded if possible, avoiding flicker on load (#6563)


  • Ensure navbar toggle icon is correct color in BootstrapTemplate (#6111)
  • Change loading background filters to work better in dark themes (#6112)
  • Improve styling of FileInput widget (#6479)
  • Improve Jupyter Preview error handling and error template (#6496)
  • Add scale animation to icons on hover and click (#6376)
  • Redesign index pages (#6497)
  • Improve Tabulator editor text color in Fast design (#6512)
  • Ensure BootstrapTemplate hamburger icon is white (#6562)

Compatibility & Version Updates

  • Bump Perspective version to 2.9.0 (#5722)
  • Upgrade to Bokeh 3.4.x (#6072)
  • Upgrade Vizzu to 0.9.3 (#6476)
  • Bump JSONEditor version to 10.0.1 (#6477)
  • Upgrade to PyScript Next and Pyodide 0.25.0 in panel convert (#6490)
  • Bump vtk.js version to 30.1.0 (#6559)

Bug fixes

  • Add resize handler for FloatPanel (#6201)
  • Fix serving of global template in notebook (#6210)
  • Ensure Tabulator renders in collapsed Card (#6223)
  • Fix issues with VTK, VTKVolume and VTKJS due to webgpu renderer (#6244)
  • Ensure HTML and other markup panes can be emptied (#6303)
  • Ensure collapsed Card does not cause stretching (#6305)
  • Ensure notebook preview always uses server resources (#6317)
  • Remove animation from loading spinner without spin (#6324)
  • Ensure model is only added/removed from Document once (#6342)
  • Ensure loading_indicator resets when configured with context manager (#6343)
  • Fix modal overflow and resizing issues (#6355)
  • Ensure that ripple matches notification size (#6360)
  • Fully re-render CodeEditor on render calls ensuring it displays correctly (#6361)
  • Ensure FileDownload button has correct height (#6362)
  • Ensure HTML model is redrawn if stylesheets is emptied (#6365)
  • Allow providing custom template (#6383)
  • Ensure Debugger renders without error (#6423)
  • Ensure pending writes are dispatched in order and only from correct thread (#6443)
  • Ensure layout reuses model if available (#6446)
  • Improved exception handler in unlocked message dispatch (#6447)
  • Fix display of interactive Matplotlib (#6450)
  • Ensure Plotly pane renders and hides correctly in Card (#6468)
  • Fix issues rendering widget components with Fast design (#6474)
  • Fix binary serialization from JS -> Pyodide (#6490)
  • Avoid overeager garbage collection (#6518)
  • Fix floating point error in IntRangeSlider (#6516)
  • Load JS modules from relative path (#6526)
  • Ensure no events are dispatched before the websocket is open (#6528)
  • Ensure Markdown parsing does not choke on partial links (#6535)
  • Fixes to ensure larger PDFs can be rendered (#6538)
  • Ensure IPywidget comms are only opened once (#6542)
  • Fixes for message handling in Jupyter Preview context (#6547)
  • Fix unnecessary loading of ReactiveHTML resources (#6552)
  • Ensure Template.raw_css has higher precedence than default template CSS (#6554)
  • Avoid asyncio event loop startup issues in some contexts (#6555)
  • Ensure column subset is retained on (#6560)
  • Ensure bokeh mathjax bundle when mathjax extension is loaded in notebook (#6564)

Chat Components

  • Ensure ChatInterface respect supplied default user (#6290)
  • Ensure ChatMessage internals correctly respect Design (#6304)
  • Fix ChatInterface stop button for synchronous functions (#6312)
  • Include stylesheets downstream, including layouts in ChatMessage (#6405)
  • Ensure ChatMessage header updates dynamically (#6441)
  • Ensure streaming ChatMessage on ChatInterface and mention serialize (#6452)
  • Ensure ChatInterface supports chat input without value_input parameter (#6505)
  • Ensure word breaks to avoid overflow in ChatMessage (#6187, #6509)
  • Ensure nested disabled state stays disabled on ChatFeed (#6507)
  • Allow streaming None as the initial ChatMessage value (#6522)


  • Add Roadmap to documentation (#5443)
  • Refactor ReactiveHTML docs (#5448, #6358)
  • Improve HoloViews reference guide (#6065)
  • Improve the user experience for resetting Jupyterlite (#6198)
  • Add explanation docs about APIs (#6289, #6469)
  • Add section headers to Chat reference documentation (#6370)
  • Migrate gallery to new Anaconda DSP instance (#6413)
  • Improve home page (#6422)
  • Adding AWS deployment to documentation (#6434)
  • Update Streamlit comparison (#6467)
  • Add logging how-to guide (#6511)
  • Document pygments dependency for code syntax highlighting (#6519)
  • Add how-to guide on configuring PyCharm (#6525)

Deprecations & Removals

  • Remove Ace alias for CodeEditor
  • Remove ChatBox which has been replaced by components
  • Remove which is now replaced with HTML.styles
  • Remove Trend.title which is now replaced by
  • Remove which is now replaced with
  • Remove Viewable.background which is now replaced with Viewable(styles={'background': ...})
  • Remove Viewable.pprint which is now replaced with print(Viewable(...))
Back to top