| name | panel-material-ui |
| description | Best practices for developing modern looking tools, dashboards and data apps using HoloViz Panel and Panel Material UI components. |
| metadata | {"version":"1.0.0","author":"holoviz","category":"web-development","difficulty":"intermediate"} |
Panel Material UI Development Skills
This guide provides best practices for using Panel Material UI. Optimized for LLMs.
Please develop code, tests and documentation as an expert Panel analytics app developer would do when working with a short time to market.
If not already loaded please get the 'panel' skill.
This guide focuses on panel-material-ui specific patterns. This guide takes precedence over the panel skills.
Installation
pip install panel-material-ui panel watchfiles hvplot hvsampledata
For development in .py files DO always include watchfiles for hotreload.
Best Practice Hello World App
Let's describe our best practices via a basic Hello World App:
import panel as pn
import panel_material_ui as pmui
import param
pn.extension(throttled=True)
@pn.cache(max_items=3)
def extract(n=5):
return "Hello World" + "⭐" * n
text = extract()
text_len = len(text)
def transform(data: str, count: int=5)->str:
"""
Transforms the input data by truncating it to the specified count of characters.
"""
count = min(count, len(data))
return data[:count]
class HelloWorld(pn.viewable.Viewer):
"""
A simple Panel app that displays a "Hello World" message with a slider to control the length of the message.
"""
characters = param.Integer(default=text_len, bounds=(0, text_len), doc="Number of characters to display")
def __init__(self, **params):
super().__init__(**params)
with pn.config.set(sizing_mode="stretch_width"):
self._characters_input = pmui.IntSlider.from_param(self.param.characters, margin=(10,20))
self._inputs = pmui.Column(self._characters_input, max_width=300)
self._outputs = pmui.Column(self.model)
self._panel = pmui.Row(self._inputs, self._outputs)
@pn.cache
@param.depends("characters")
def model(self):
"""
Returns the "Hello World" message truncated to the specified number of characters.
"""
return transform(text, self.characters)
def __panel__(self):
return self._panel
@classmethod
def create_app(cls, **params):
"""
Create the Panel app with the interactive model and slider.
"""
instance = cls(**params)
return pmui.Page(
title="Hello World App",
sidebar=list(instance._inputs),
main=list(instance._outputs),
)
if __name__ == "__main__":
HelloWorld.create_app().show(port=5007, autoreload=True, open=True)
elif pn.state.served:
HelloWorld.create_app().servable()
DO always create test in separate test files and DO run test via pytest:
import ...
def test_characters_reactivity():
"""
Always test that the reactivity works as expected.
Put tests in a separate test file.
"""
hello_world = HelloWorld()
assert hello_world.model() == text[:hello_world.characters]
hello_world.characters = 5
assert hello_world.model() == text[:5]
hello_world.characters = 3
assert hello_world.model() == text[:3]
Panel Material UI Guidelines
General Instructions
- DO use the new parameter names (e.g.
label, color) instead of legacy aliases (e.g. name, button_type) for pmui components
- DO use
sizing_mode parameter over sx css styling parameter
- DO use Material UI
sx parameter for all css styling over styles and stylesheets
- DO use panel-material-ui components instead of panel components for projects already using panel-material-ui and for new projects
- DON'T configure the
design, i.e. DO NOT pn.extension(design='material').
- DO prefer professional Material UI icons over emojies
Component Instructions
Page
- DO provide the title to the
Page.title argument. DON'T provide it in the Page.main area.
- DO make sure components in the
sidebar stretch width.
- DO provide an optional image, description, navigation menu to the
Page.sidebar argument. Normally DON't put them in the header or main areas.
- DO provide the input widgets as children to the
Page.sidebar argument
- DO not add advanced or high components to the
Page.header as it is only 100px high by default. Normally only buttons, indicators, text and navigation links go into the header.
- DON'T include
ThemeToggle or other widgets to toggle the theme when using the Page. A ThemeToggle is already built in.
- DO Add a little bit of
margin=10 to the outer layout component(s) in the main area. To make them stand out from the sidebar components: Grid(..., container=True, margin=15).
DO provide lists of children to the Page.sidebar, Page.main or Page.header arguments:
pmui.Page(
header=[component1, component2],
sidebar=[component3, component4],
main=[a_list_like_layout, a_grid],
)
DON'T provide non-lists as children to the Page.sidebar, Page.main or Page.header arguments:
pmui.Page(
header=component1,
sidebar=list(a_list_like_layout),
main=list(a_grid),
)
Linking Dashboard Theme with Page Theme
DO synchronize component themes with Page theme:
...
dark_theme = param.Boolean(
doc="""True if the theme is dark""",
allow_refs=True
)
@classmethod
def create_app(cls, **params):
"""Create app with synchronized theming."""
component = cls(**params)
page = pmui.Page(
...,
dark_theme=component.dark_theme,
)
component.dark_theme = page.param.dark_theme
return page
Grid
- DO set
spacing=2 or higher to separate sub components in the grid.
- DO not use
ncols keyword argument. It is not supported.
Column/ Row
- DO use
size parameter instead of xs, sm or md parameters - they do not exist.
- DO use
sx to set spacing instead of setting spacing directly. It does not exist.
List like layouts
For list-like layouts like Column and Row DO provide children as positional arguments:
pmui.Row(child1, child2, child3)
DON'T provide them as separate arguments:
pmui.Row([child1, child2, child3,])
Paper
DO change the default margin to 10px:
pmui.Paper.param.margin.default=10
Switch
- Do add
margin=(10, 20) when displaying in the sidebar.
Sliders
- DO add a little bit of margin left and right when displaying in the sidebar.
Cards
- DO use the
Paper component over the Card unless you need the Cards extra features.
- DO set
collapsible=False unless collapsible is needed.
Tabulator
- DO use "materialize"
theme instead of "material". The latter does not exist.
Non-Existing Components
- Do use
Column instead of Box. The Box component does not exist.
Material UI Examples
Standalone Icons
DO use Typography to make standalone icons without interactivity instead of IconButton:
pmui.Typography(
f'<span class="material-icons" style="font-size: 4rem;">lightbulb</span>',
sizing_mode="fixed", width=60, height=60, sx = {"color": "primary.main"},
)
pmui.IconButton(icon=icon, disabled=True, ...)
Static Components Pattern (Material UI)
import panel as pn
import panel_material_ui as pmui
import param
pn.extension()
pmui.Paper.param.margin.default=10
class HelloWorld(pn.viewable.Viewer):
characters = param.Integer(default=10, bounds=(1, 100), doc="Number of characters to display")
def _get_kpi_card(self):
return pmui.Paper(
pmui.Column(
pmui.Typography(
"📊 Key Performance Metrics",
variant="h6",
sx={
"color": "text.primary",
"fontWeight": 700,
"mb": 3,
"display": "flex",
"alignItems": "center",
"gap": 1
}
),
pmui.Row(
self.kpi_value
)
),
)
@param.depends("characters")
def kpi_value(self):
return f"The kpi is {self.characters}"
def __panel__(self):
return pmui.Paper(
pmui.Column(
self.param.characters,
self._get_kpi_card(),
),
sx={"padding": "20px", "borderRadius": "8px"},
sizing_mode="stretch_width"
)
if pn.state.served:
HelloWorld().servable()
For all other Panel patterns (parameter-driven architecture, reactive updates, serving, etc.), refer tot the 'panel' skill.