Customize Themes & Styles#
Customization of Panel Mui components inherits all the benefits of having a consistent design language that Mui provides. Styling can be applied using the sx parameter, while theming is achieved through the inheritable theme_config. Let us walk through these two different approaches through a series of examples.
This how-to guide was adapted from the Mui How to Customize guide.
Styling Layers Overview#
Panel provides multiple tools for styling, each suited to different needs:
Tool |
Purpose |
Scope |
|---|---|---|
|
Style component internals (one-off tweaks, experiments) |
Local to one instance |
|
Define app-wide palette, typography, and defaults |
Global (flows to all children) |
|
Style the outer container box (spacing, borders, backgrounds) |
Local to one instance |
|
Style widget internals via CSS selectors |
Local to one instance |
Quick mental model: Start with sx for local customizations, use theme_config for consistency, use styles for container wrapping, and stylesheets only for classic Panel internals.
One-off Customizations with sx#
To change the styles of a single panel-material-ui component instance, use the sx parameter. This is ideal for quick, local customizations like tweaking padding, changing colors, or adding hover effects.
The sx Parameter#
All panel-material-ui components accept an sx parameter that allows you to pass in style overrides.
from panel_material_ui import Button
Button(
label="Click Me!",
sx={
"color": "white",
"backgroundColor": "black",
"&:hover": {
"backgroundColor": "gray",
}
}
).servable()
Dark and Light Mode Selectors#
If you need to apply styling only in dark or light mode, use the .mui-dark or .mui-light class:
from panel_material_ui import Button, Row, ThemeToggle
Row(
Button(
label="Click Me!",
sx={
"color": "white",
"backgroundColor": "black",
"&:hover": {
"backgroundColor": "pink",
},
"&.mui-dark:hover": {
"backgroundColor": "orange",
}
}
),
ThemeToggle(),
).preview()
Targeting Nested Component Parts#
Sometimes you need to style a nested part of a component—for instance, the thumb of a slider or the label of a checkbox. Use the & selector to target nested Material UI class names:
from panel_material_ui import FloatSlider
FloatSlider(
sx={
"& .MuiSlider-thumb": {
"borderRadius": 0 # square instead of round
}
}
).servable()
You can also combine nested selectors with dark/light mode:
from panel_material_ui import FloatSlider, ThemeToggle, Row
Row(
FloatSlider(
sx={
"& .MuiSlider-thumb": {
"borderRadius": 0,
"backgroundColor": "white",
},
"&.mui-dark .MuiSlider-thumb": {
"backgroundColor": "black",
}
}
),
ThemeToggle(),
).preview()
Note
Note: Even though Panel Mui components reuse Material UI’s internal class names, these names are subject to change. Make sure to k eep an eye on release notes if you override nested classes.
Learn more about the sx parameter in the Mui docs.
Global Theming with theme_config#
For app-wide consistency, use theme_config to define defaults (colors, typography, shape) that flow down to all child components. This is the most maintainable approach for larger apps.
Basic Theme Configuration#
Define a theme at the parent level and all children inherit it:
from panel_material_ui import Button
theme_config = {
"palette": {
"primary": {"main": "#d219c9"},
"secondary": {"main": "#dc004e"},
}
}
Button(
label="Themed Button", theme_config=theme_config, button_type="primary"
).servable()
Mode-Specific Themes#
Provide distinct theme configs for dark and light modes using "light" and "dark" keys:
from panel_material_ui import Button, Row, ThemeToggle
theme_config = {
"light": {
"palette": {
"primary": {"main": "#d219c9"},
"secondary": {"main": "#dc004e"},
}
},
"dark": {
"palette": {
"primary": {"main": "#dc004e"},
"secondary": {"main": "#d219c9"},
}
}
}
Row(
Button(
label="Global Button", theme_config=theme_config, button_type="primary"
),
ThemeToggle(),
).preview()
Theme Inheritance#
The most powerful feature is that child components automatically inherit the parent’s theme. This allows you to style your entire app from one top-level configuration:
from panel_material_ui import Card, Button
Card(
Button(label="Child Button", button_type="primary"), # Inherits parent's theme
title="Parent Card",
theme_config={
"palette": {
"primary": {"main": "#d219c9"},
}
}
).servable()
Here, the child Button automatically inherits the parent’s primary color. We recommend styling your top-level container (e.g., Page, Container, or Row) so the theme cascades throughout your app.
Comparison: sx vs theme_config#
Both approaches can achieve the same visual result, but serve different purposes:
Using sx (local):
from panel_material_ui import Button
Button(
label="Local Style",
sx={"backgroundColor": "#d219c9", "color": "white"}
).servable()
Using theme_config (global):
from panel_material_ui import Button, Row
theme = {
"palette": {
"primary": {"main": "#d219c9"},
}
}
Row(
Button(label="Global Style", button_type="primary"),
theme_config=theme,
).servable()
When to choose:
Use
sxfor one-off exceptions or quick experimentsUse
theme_configwhen you want consistency across many components or the entire appCombine both: set global defaults with
theme_config, then usesxfor local overrides
Container Styling with styles#
The styles parameter styles the outer container of any Panel component—perfect for spacing, borders, backgrounds, and shadows around the component itself (not its internals).
from panel_material_ui import Button, Row
container_style = {
"background": "#f8fafc",
"border": "1px solid #dce3eb",
"border-radius": "10px",
"padding": "12px",
"box-shadow": "0 2px 8px rgba(0,0,0,0.08)",
}
Row(
Button(label="Button in styled container", button_type="primary", styles=container_style),
).preview()
Difference Between sx and styles#
sx: Styles the component internals (text, background inside the Button, Slider handle, etc.)styles: Styles the outer container box (wraps around the component)
Example showing the difference:
from panel_material_ui import Button, Row
# styles: changes the wrapper box around the button
container_style = {
"background": "#e3f2fd",
"border": "2px solid #1976d2",
"border-radius": "8px",
"padding": "16px",
}
# sx: changes the button itself
button_sx = {
"backgroundColor": "#ff6b6b",
"color": "white",
"&:hover": {"backgroundColor": "#ff5252"}
}
Row(
Button(
label="Styled Button",
sx=button_sx,
styles=container_style
),
).preview()
Putting It All Together#
A real app typically combines multiple approaches:
import panel as pn
import panel_material_ui as pmui
pn.extension()
# Global theme applied at the top level
app_theme = {
"light": {
"palette": {
"primary": {"main": "#6a1b9a"},
"secondary": {"main": "#1565c0"},
},
"shape": {"borderRadius": 12},
},
"dark": {
"palette": {
"primary": {"main": "#9575cd"},
"secondary": {"main": "#64b5f6"},
},
"shape": {"borderRadius": 12},
},
}
# Local container styling
card_container = {
"padding": "8px",
"background": "rgba(0,0,0,0.02)",
}
# Local component styling
button_sx = {
"fontWeight": 700,
"&:hover": {"transform": "scale(1.02)"}
}
pmui.Row(
pmui.Card(
pmui.Button(
label="Submit",
button_type="primary",
sx=button_sx, # Local exception
),
title="Form",
styles=card_container, # Container styling
),
pmui.ThemeToggle(),
theme_config=app_theme, # Global theme
).preview(height=300)