Using Panel¶
Skill version 1.0.2
Panel is a Python library for building interactive dashboards, data apps, and tools entirely in Python — no JavaScript required. It connects widgets to plots, tables, and text with reactive callbacks, and serves the result as a web application.
Always use a pn.viewable.Viewer class to structure apps. This keeps state, layout, and logic organized and avoids flickering from recreated components. Once an app outgrows one class — multiple views over shared data, state several components touch — split it into composable classes (see Designing Panel Architecture).
Contents¶
- References — iterative development, Material UI, plotting, custom components, Playwright testing, app structure & scaling, review
- Viewer Class Pattern
- Widgets and Extensions
- Templates and Layouts
- Serving Workflow
- Performance
References¶
Read these for specialized topics. Each is a standalone document you can load with the view tool.
- Iterating on Panel Apps — serve with logging, screenshot with Playwright, review and debug agentic loop
- Designing Panel Architecture — composing larger apps (State/DataStore/View/App,
param.ClassSelector, cross-object@param.depends,from_data,pn.rx) and runtime/scale (per-session model,pn.statescheduling, generator streaming, caching tiers,nthreads, profiling) - Building Custom Components — Python-vs-JS decision ladder; pure-Python
Viewer/PyComponent; JSComponent, ReactComponent, AnyWidgetComponent, MaterialUIComponent; CDN selection, event handling, state sync lifecycle - Using Material UI — building pmui apps (
pmui.Page,Container/Gridlayouts, centering, component gotchas) and theming (theme_configpalette, typography, icons, brand assets, chart theming) - Migrating to Material UI — converting an existing plain-Panel app to pmui: template→Page, widget swaps, pane/interaction upgrades, what to leave alone
- Converting Designs to Material UI — workflow for turning a screenshot/design/React app into a pmui app: capture references, map to components, build component-first with mock data, assemble, theme last
- Plotting in Panel — embedding plots from any library: HoloViews/hvPlot (DynamicMap zoom/pan, responsive sizing), Matplotlib, Plotly, ECharts, Bokeh toolbar tools
- Using Tabulator —
add_filterwith widgets, checkbox selection, row content, function-based filtering - Using Pytest Playwright —
serve_component/wait_untilutilities, JS↔Python sync tests, complete test patterns for custom components - Reviewing Panel Apps — anti-pattern checklist for code review: flickering, missing hold, watcher gaps, bind vs watch, mutation bugs
Viewer Class Pattern¶
- Recreating panes or layouts inside
@param.dependscauses flickering. Create them once in__init__, bind to reactive content. on_init=Truewatchers fire duringsuper().__init__(). Create any panes they reference before thesuper().__init__(**params)call.- Use
pn.pane.Placeholderwhen the content type varies (string → plot → widget). Swap with.update()or.object =. - Implement
__panel__to return the layout. When served, wrap inpmui.Page(see Using Material UI); otherwise return the bare component. - Shared UI state: Add a param (
disabled,loading,visible) to a base class and bind widgets to it (e.g.,disabled=self.param.disabled). Set once to update all widgets — useful for form submit, loading states, or toggling visibility. - Organize
__init__: Separate component instantiation from wiring. First create all widgets/panes, then groupon_click,pn.bind, and.watch()calls together. Makes it clear what exists vs. how it's connected. - Method naming:
_on_*for event handlers (_on_click,_on_submit),_update_*for watchers that sync state (_update_view,_update_button_state),_sync_*for bidirectional syncs. - Wizard/pipeline pattern: For multi-step flows, see
examples/wizard.py—pmui.StepperMenudriving navigation and per-step state (completed/error/active,non_linear),pn.pane.Placeholderstep swapping, shareddisabledstate,pn.io.hold()batching, inlinepmui.Alertvalidation,pmui.Tooltip, andpmui.Page. - KPI dashboard pattern: For metric dashboards, see
examples/dashboard.py—pn.indicators.TrendKPI cards,pmui.Badgeselection counter,pmui.SpeedDialquick actions,pmui.Alertempty-state,pmui.Tooltiphints,pmui.Gridresponsive layout, DynamicMap with trigger pattern, Tabulatoradd_filter+ checkbox selection cross-filtering,pn.bind(watch=True)wiring,param.DataFrameas single source of truth, andpmui.Page.
import hvplot.pandas # noqa
import panel as pn
import panel_material_ui as pmui
import param
pn.extension(throttled=True)
penguins = hvplot.sampledata.penguins("pandas").dropna()
species_list = sorted(penguins["species"].unique())
# ✅ Static panes, reactive content
class Dashboard(pn.viewable.Viewer):
species = param.ListSelector(default=species_list, objects=species_list)
def __init__(self, **params):
# Create panes before super().__init__ — on_init=True watchers fire during super()
self._species_widget = pmui.CheckButtonGroup.from_param(self.param.species)
self._chart_pane = pn.pane.HoloViews(sizing_mode="stretch_width")
super().__init__(**params)
with pn.config.set(sizing_mode="stretch_width"):
self._sidebar = pmui.Column(self._species_widget)
self._main = pmui.Column(self._summary, self._chart_pane)
def _filtered(self):
return penguins[penguins["species"].isin(self.species)]
@param.depends("species")
def _summary(self):
return f"**{len(self._filtered())}** penguins selected"
@param.depends("species", watch=True, on_init=True)
def _update_chart(self):
self._chart_pane.object = self._filtered().hvplot.scatter(
x="bill_length_mm", y="bill_depth_mm", by="species",
)
def __panel__(self):
if pn.state.served:
return pmui.Page(
title="Penguin Explorer",
sidebar=[self._sidebar],
main=[self._main],
)
return self._main
# ❌ Recreates layout on every change — causes flickering
class BadDashboard(pn.viewable.Viewer):
species = param.ListSelector(default=species_list, objects=species_list)
@param.depends("species")
def view(self):
filtered = penguins[penguins["species"].isin(self.species)]
return pn.Column(
pn.pane.Markdown(f"**{len(filtered)}** penguins selected"),
pn.pane.HoloViews(filtered.hvplot.scatter(x="bill_length_mm", y="bill_depth_mm", by="species")),
)
Widgets and Extensions¶
- Call
pn.extension(throttled=True)with any needed JS extensions ("tabulator","plotly"). Never add"bokeh". .from_param()auto-creates the right widget type from a parameter — syncs value, bounds, and objects. (Button-group widgets are an exception that needs an explicit watcher — see the from_param write-back gap in Reviewing Panel Apps.)- Prefer
pn.bind(self._update, widget1.param.value, widget2.param.value, watch=True)over lambda-based.param.watch()for wiring multiple widgets to a single update method. - Default to
sizing_mode="stretch_width"viapn.config.set.
Templates and Layouts¶
For new apps, use pmui.Page from panel-material-ui (see Using Material UI for Page rules, sidebar order, and layout helpers). If an existing codebase already uses a different template (e.g. FastListTemplate), keep it rather than migrating. Use FlexBox/GridSpec/GridBox for complex layouts, and set min_*/max_* sizing to prevent layout collapse.
Serving Workflow¶
- Keep a dev server running:
panel serve app.py --dev --show. Don't restart after edits. - Don't use
--autoreload(legacy). Don't usepython app.py.
Performance¶
- Batch multiple updates into one redraw with
pn.io.hold(); defer heavy components withpn.extension(defer_load=True, loading_indicator=True); memoize expensive work with@pn.cache. - For caching tiers, automatic threading, generator streaming, profiling, the loading-spinner pattern, and memory management, see Designing Panel Architecture.
Lookup¶
Component Reference¶
Look up component docs at https://panel.holoviz.org/reference/{section}/{Component}.html
Sections: panes, widgets, layouts, chat, global, indicators, templates, custom_components
Search¶
Search the web at https://panel.holoviz.org/search.html?q=<topic> for additional information.