Page#
Download this notebook from GitHub (right-click to download).
import panel as pn
from panel_material_ui import Page, Tabs
pn.extension()
The Page component is the equivalent of a Template in Panel, defining the overall layout of an application.
Unlike a Template, the Page component is implemented entirely in JavaScript, allowing dynamic updates of its contents without re-rendering the entire layout.
Parameters:#
For details on other options for customizing the component see the layout and styling how-to guides.
Core#
config(Config): Configuration object declaring custom CSS and JS files to load specifically for this template.favicon(Path | str | dict[str, str | Path]): Favicon to render.logo(Path | str | dict[str, str | Path]): Logo to render in the header. Can be a string, a pathlib.Path, or a dictionary with breakpoints as keys, e.g. {‘sm’: ‘logo_mobile.png’, ‘md’: ‘logo.png’} or themes as keys, e.g.{'dark': 'logo_dark.png', 'light': 'logo.png'}.meta(Meta): Meta tags and other HTML head elements.template(str | Path | jinja2.Template): Overrides the default jinja2 template.title(str): Title of the application.
Layout#
header(Children): Items rendered in the header.main(Children): Items rendered in the main area.sidebar(Children): Items rendered in the sidebar.contextbar(Children): Items rendered in the contextbar.
Contextbar#
contextbar_open(boolean): Whether the contextbar is open or closed.contextbar_resizable(boolean): Whether the contextbar is resizable.contextbar_variant(Literal["persistent", "temporary", "permanent", "auto"]): Whether the contextbar is persistent, temporary, permanent or automatically adapts based on screen size.contextbar_width(int): Width of the contextbar.
Indicators#
busy(boolean, readonly): Linked to global busy state.busy_indicator(Literal["circular", "linear"] | None): Whether to render a linear, circular or no busy indicator.theme_toggle(boolean): Whether to show a theme toggle button.
Contextbar variants#
The contextbar_variant works the same way as sidebar_variant. By default it is set to "temporary", meaning the contextbar overlays the main content when opened. Setting it to "persistent" keeps it visible alongside the main content:
persistent_context = page.clone(
contextbar_variant='persistent',
contextbar_open=True
)
persistent_context.preview()
Both the sidebar and contextbar can be resized by dragging when sidebar_resizable / contextbar_resizable is True (the default). Set either to False to disable resizing:
fixed_page = page.clone(
sidebar_resizable=False,
contextbar_resizable=False,
contextbar_variant='persistent',
contextbar_open=True
)
fixed_page.preview()
🎨 Custom Theming with theme_config#
This example demonstrates how to apply a custom theme to the Page layout using the theme_config option.
HEADER_COLOR = "#2A3E5C"
PAPER_COLOR = "#f8f8f8"
themed = page.clone(theme_config={
'palette': {
'primary': {
'main': HEADER_COLOR # The header is styled by the primary palette
},
'background': {
'paper': PAPER_COLOR, # The remaining areas are paper colored
},
}
}, theme_toggle=False)
themed.preview()
🎯 Key Customizations#
Primary Palette (
primary.main): Sets the header color using a deep, modern blue-gray (#2A3E5C).Background (
background.paper): Applies a light neutral background (#f8f8f8) to content areas like the main, sidebar, and contextbar.
By setting theme_toggle=False, the user is not shown a button to switch between light and dark modes — keeping the design consistent.
🎨 Advanced Styling with sx#
This example builds on the previous themed layout, applying fine-grained custom styles using the sx parameter (which accepts CSS-like syntax).
styled = themed.clone(sx={
"& .header": {
"backgroundColor": "#673AB7"
},
"& .title": {
"fontSize: 2.5em"
},
"& .main": {
"backgroundColor": "#c3c3c3",
},
"&.mui-dark .main": {
"backgroundColor": "#3f3f3f",
},
"& .sidebar": {
"backgroundColor": "#e9e9e9"
},
"&.mui-dark .sidebar": {
"backgroundColor": "#2a2a2a",
},
"& .contextbar": {
"backgroundColor": "#525252",
"color": "white"
},
}, theme_toggle=True)
pn.Tabs(
('Theme: Default', styled.preview()),
('Theme: Dark', styled.clone(dark_theme=True).preview())
)
🌗 Theme Toggle Enabled#
Unlike the previous example, this version re-enables the theme toggle, letting users switch between light and dark modes. The sx rules adapt accordingly — as shown with the conditional ".mui-dark .main" style.
Configuring the page loader#
The Page component renders into a template, which, by default, includes a loading spinner. This loading spinner can be overridden with a custom template, e.g. below we define a custom loader generated with loading.io:
template = """
{% extends "base.html" %}
{% block loader_css %}
@keyframes ldio-yzaezf3dcmj-1 {
0% { transform: rotate(0deg) }
50% { transform: rotate(-45deg) }
100% { transform: rotate(0deg) }
}
@keyframes ldio-yzaezf3dcmj-2 {
0% { transform: rotate(180deg) }
50% { transform: rotate(225deg) }
100% { transform: rotate(180deg) }
}
.ldio-yzaezf3dcmj > div:nth-child(2) {
transform: translate(-15px,0);
}
.ldio-yzaezf3dcmj > div:nth-child(2) div {
position: absolute;
top: 40px;
left: 40px;
width: 120px;
height: 60px;
border-radius: 120px 120px 0 0;
background: #fee547;
animation: ldio-yzaezf3dcmj-1 1s linear infinite;
transform-origin: 60px 60px
}
.ldio-yzaezf3dcmj > div:nth-child(2) div:nth-child(2) {
animation: ldio-yzaezf3dcmj-2 1s linear infinite
}
.ldio-yzaezf3dcmj > div:nth-child(2) div:nth-child(3) {
transform: rotate(-90deg);
animation: none;
}@keyframes ldio-yzaezf3dcmj-3 {
0% { transform: translate(190px,0); opacity: 0 }
20% { opacity: 1 }
100% { transform: translate(70px,0); opacity: 1 }
}
.ldio-yzaezf3dcmj > div:nth-child(1) {
display: block;
}
.ldio-yzaezf3dcmj > div:nth-child(1) div {
position: absolute;
top: 92px;
left: -8px;
width: 16px;
height: 16px;
border-radius: 50%;
background: #000000;
animation: ldio-yzaezf3dcmj-3 1s linear infinite
}
.ldio-yzaezf3dcmj > div:nth-child(1) div:nth-child(1) { animation-delay: -0.67s }
.ldio-yzaezf3dcmj > div:nth-child(1) div:nth-child(2) { animation-delay: -0.33s }
.ldio-yzaezf3dcmj > div:nth-child(1) div:nth-child(3) { animation-delay: 0s }
.loadingio-spinner-bean-eater-2by998twmg8 {
width: 200px;
height: 200px;
display: inline-block;
overflow: hidden;
background: #ffffff;
}
.ldio-yzaezf3dcmj {
width: 100%;
height: 100%;
position: relative;
transform: translateZ(0) scale(1);
backface-visibility: hidden;
transform-origin: 0 0; /* see note above */
}
.ldio-yzaezf3dcmj div { box-sizing: content-box; }
.loading {
position: fixed;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1050;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.6);
font-size: 1.5rem;
color: #ffffff;
-webkit-animation: fadein 0.5s; /* Safari, Chrome and Opera > 12.1 */
-moz-animation: fadein 0.5s; /* Firefox < 16 */
-ms-animation: fadein 0.5s; /* Internet Explorer */
-o-animation: fadein 0.5s; /* Opera < 12.1 */
animation: fadein 0.5s;
}
{% endblock %}
{% block loader %}
<div class="loading" id="loader">
<div class="loadingio-spinner-bean-eater-2by998twmg8">
<div class="ldio-yzaezf3dcmj">
<div><div></div><div></div><div></div></div><div><div></div><div></div><div></div></div>
</div>
</div>
</div>
{% endblock %}
{% block loader_script %}
<!-- Ordinarily this would hide the loader --!>
{% endblock %}
"""
Page(template=template).preview()
Above we overrode the loader, loader_css and loader_script block. The loader_script is responsible for hiding the element with id="loader", ensuring that once the page is fully loaded the loading screen is hidden. Ordinarily you would only override the loader and loader_css blocks.
Controls#
The Page exposes a number of options which can be changed from both Python and Javascript. Try out the effect of these parameters interactively:
control_page = Page()
control_page.main = [control_page.controls(jslink=True)]
control_page.preview(height=1000)
Download this notebook from GitHub (right-click to download).