
Dmc Py
- 10 installs
- 5 repo stars
- Updated August 5, 2026
- bjornmelin/dev-skills
dmc-py is a skill that guides building Dash applications with Dash Mantine Components v2.x, including theming, callbacks, and charts.
About
This skill provides guidance for building Dash applications with Dash Mantine Components v2.x. Developers use it when creating dashboards, forms, and data-visualization apps to pick the right component and wire up callbacks. It covers MantineProvider theming, style props, pattern-matching and clientside callbacks, multi-page apps, and charts across 100+ components.
- Expert guidance for building Dash apps with Dash Mantine Components v2.x
- Covers 100+ components, theming, callbacks, multi-page apps, and charts
- Includes component decision tables and quick-start patterns
Dmc Py by the numbers
- 10 all-time installs (skills.sh)
- Ranked #1,689 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
dmc-py capabilities & compatibility
- Capabilities
- frontend · ui design
- Use cases
- frontend · ui design · data analysis
What dmc-py says it does
Build modern Dash applications with 100+ Mantine UI components.
**Critical**: All DMC components MUST be inside `dmc.MantineProvider`.
This skill targets DMC 2.x (Mantine 8.x). Run `pip show dash-mantine-components` to check your installed version.
npx skills add https://github.com/bjornmelin/dev-skills --skill dmc-pyAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 10 |
|---|---|
| repo stars | ★ 5 |
| Last updated | August 5, 2026 |
| Repository | bjornmelin/dev-skills ↗ |
What it does
Building a Dash dashboard or form app with Dash Mantine Components v2.x, choosing components and wiring callbacks.
Who is it for?
Developers building dashboards, forms, or data-viz apps with Dash Mantine Components v2.x.
Skip if: Non-Dash UIs or projects not using Dash Mantine Components.
When should I use this skill?
Creating a Dash dashboard, form, or data-visualization app with Dash Mantine Components.
By the numbers
- 100+ Mantine UI components
- targets DMC 2.x on Mantine 8.x
Files
Dash Mantine Components (DMC) v2.x
Build modern Dash applications with 100+ Mantine UI components.
Quick Start
Minimal DMC app requiring MantineProvider wrapper:
from dash import Dash, callback, Input, Output
import dash_mantine_components as dmc
app = Dash(__name__)
app.layout = dmc.MantineProvider([
dmc.Container([
dmc.Title("My DMC App", order=1),
dmc.TextInput(label="Name", id="name-input", placeholder="Enter name"),
dmc.Button("Submit", id="submit-btn", mt="md"),
dmc.Text(id="output", mt="md"),
], size="sm", py="xl")
])
@callback(Output("output", "children"), Input("submit-btn", "n_clicks"), Input("name-input", "value"))
def update_output(n_clicks, name):
if not n_clicks:
return ""
return f"Hello, {name or 'World'}!"
if __name__ == "__main__":
app.run(debug=True)Critical: All DMC components MUST be inside dmc.MantineProvider.
Version Note: This skill targets DMC 2.x (Mantine 8.x). Runpip show dash-mantine-componentsto check your installed version. For the latest features and API changes, usefetch_docs.pyto query the official documentation at https://www.dash-mantine-components.com/assets/llms.txt
---
Workflow Decision Tree
Select components by use case:
Form Inputs
| Need | Component | Key Props |
|---|---|---|
| Text input | TextInput | label, placeholder, value, debounce |
| Dropdown | Select | data, value, searchable, clearable |
| Multi-select | MultiSelect | data, value, searchable |
| Checkbox | Checkbox | label, checked |
| Toggle | Switch | label, checked, onLabel, offLabel |
| Number | NumberInput | value, min, max, step |
| Date | DatePickerInput | value, type, minDate, maxDate |
| Rich text | Textarea | label, value, autosize, minRows |
| File upload | FileInput | value, accept, multiple |
Layout
| Need | Component | Key Props |
|---|---|---|
| Content wrapper | Container | size, px, py |
| Vertical stack | Stack | gap, align, justify |
| Horizontal row | Group | gap, justify, wrap |
| CSS Grid | Grid, GridCol | columns, gutter, span |
| Full app shell | AppShell | header, navbar, aside, footer |
| Card container | Card | shadow, padding, radius, withBorder |
| Flex layout | Flex | direction, wrap, gap, align |
Navigation
| Need | Component | Key Props |
|---|---|---|
| Nav item | NavLink | label, href, active, leftSection |
| Tabs | Tabs, TabsList, TabsPanel | value, orientation |
| Breadcrumb | Breadcrumbs | separator |
| Stepper | Stepper, StepperStep | active, onStepClick |
| Pagination | Pagination | value, total, siblings |
| Table of contents | TableOfContents | links, variant, active |
Feedback & Overlays
| Need | Component | Key Props |
|---|---|---|
| Modal dialog | Modal | opened, onClose, title, centered |
| Side panel | Drawer | opened, onClose, position, size |
| Toast | Notification | title, message, color, icon |
| Alert banner | Alert | title, color, variant, icon |
| Loading | Loader, LoadingOverlay | size, type, visible |
| Progress | Progress, RingProgress | value, size, sections |
| Tooltip | Tooltip | label, position, withArrow |
| Copy button | CopyButton | value, timeout |
Data Display
| Need | Component | Key Props |
|---|---|---|
| Data table | Table | data, striped, highlightOnHover |
| Accordion | Accordion, AccordionItem | value, multiple, variant |
| Timeline | Timeline, TimelineItem | active, bulletSize |
| Badge | Badge | color, variant, size |
Charts
| Need | Component | Key Props |
|---|---|---|
| Line | LineChart | data, dataKey, series |
| Bar | BarChart | data, dataKey, series, orientation |
| Area | AreaChart | data, dataKey, series |
| Pie/Donut | DonutChart, PieChart | data, chartLabel |
| Scatter | ScatterChart | data, dataKey, series |
What's New in Recent Versions
v2.5.x:
TableOfContents- Auto-generated table of contents from headingsselectFirstOptionOnDropdownOpenprop for Select/MultiSelect/AutocompleteopenOnFocusprop for Combobox components- AppShell
mode="static"for nested shells window.MantineCore/window.MantineHooksfor custom component building
v2.4.x:
CopyButton/CustomCopyButton- Clipboard operationsgetEditor(id)- Access RichTextEditor TipTap instance in clientside callbacks- Function props for chart axis/grid customization
v2.3.x:
MiniCalendar- Compact calendar componentScrollAreaAutoheight- Auto-sizing scroll areaDirectionProvider- RTL text direction support
→ Full component reference: references/components-quick-ref.md
---
Core Patterns
Theming
Configure theme via MantineProvider:
theme = {
"primaryColor": "blue",
"fontFamily": "Inter, sans-serif",
"defaultRadius": "md",
"colors": {
"brand": ["#f0f9ff", "#e0f2fe", "#bae6fd", "#7dd3fc", "#38bdf8",
"#0ea5e9", "#0284c7", "#0369a1", "#075985", "#0c4a6e"]
},
"components": {
"Button": {"defaultProps": {"size": "md", "radius": "md"}},
"TextInput": {"defaultProps": {"size": "sm"}},
}
}
app.layout = dmc.MantineProvider(
theme=theme,
forceColorScheme="light", # or "dark", or None for auto
children=[...]
)Theme Toggle Pattern (clientside callback):
from dash import clientside_callback, ClientsideFunction
app.layout = dmc.MantineProvider(
id="mantine-provider",
children=[
dcc.Store(id="theme-store", storage_type="local", data="light"),
dmc.Switch(id="theme-switch", label="Dark mode", checked=False),
# ... rest of layout
]
)
clientside_callback(
"""(checked) => checked ? "dark" : "light" """,
Output("mantine-provider", "forceColorScheme"),
Input("theme-switch", "checked"),
)→ Full theming guide: references/theming-patterns.md
Styling
Style Props - Universal props on all DMC components:
| Prop | CSS Property | Values |
|---|---|---|
m, mt, mb, ml, mr, mx, my | margin | xs, sm, md, lg, xl or number (px) |
p, pt, pb, pl, pr, px, py | padding | same as margin |
c | color | "blue", "red.6", "dimmed", "var(--mantine-color-text)" |
bg | background | same as color |
w, h | width, height | "100%", "50vw", number (px) |
maw, mah, miw, mih | max/min width/height | same as w, h |
fw | font-weight | 400, 500, 700 |
fz | font-size | xs, sm, md, lg, xl or number |
ta | text-align | "left", "center", "right" |
td | text-decoration | "underline", "line-through" |
Responsive Values - Dict with breakpoints:
dmc.Button("Click", w={"base": "100%", "sm": "auto", "lg": 200})
dmc.Stack(gap={"base": "xs", "md": "lg"})Styles API - Target nested elements:
dmc.Select(
data=["A", "B", "C"],
classNames={"input": "my-input", "dropdown": "my-dropdown"},
styles={"label": {"fontWeight": 700}, "input": {"borderColor": "blue"}},
)→ Full styling guide: references/styling-guide.md
Callbacks
Basic Pattern:
from dash import callback, Input, Output, State
@callback(
Output("output", "children"),
Input("button", "n_clicks"),
State("input", "value"),
prevent_initial_call=True,
)
def update(n_clicks, value):
return f"Clicked {n_clicks} times with value: {value}"Pattern-Matching (dynamic components):
from dash import ALL, MATCH, callback_context as ctx
# ALL: Respond to any button with type "dynamic-btn"
@callback(
Output("output", "children"),
Input({"type": "dynamic-btn", "index": ALL}, "n_clicks"),
)
def handle_all(n_clicks_list):
triggered = ctx.triggered_id # {"type": "dynamic-btn", "index": X}
return f"Button {triggered['index']} clicked"
# MATCH: Update the output matching the triggered input
@callback(
Output({"type": "item-output", "index": MATCH}, "children"),
Input({"type": "item-btn", "index": MATCH}, "n_clicks"),
prevent_initial_call=True,
)
def handle_match(n):
return f"Clicked {n} times"Clientside Callback (browser-side JavaScript):
from dash import clientside_callback
clientside_callback(
"""(n) => n ? `Clicked ${n} times` : "Not clicked" """,
Output("output", "children"),
Input("button", "n_clicks"),
)DMC-Specific Props:
debounce=300- Delay callback trigger (ms) for TextInput, Textareapersistence=True- Persist value across page reloadspersistence_type="local"- Storage type: memory, local, session
→ Full callbacks reference: references/callbacks-advanced.md
---
Multi-Page Apps
Use Dash Pages with DMC AppShell:
# app.py
import dash
from dash import Dash
import dash_mantine_components as dmc
app = Dash(__name__, use_pages=True, pages_folder="pages")
app.layout = dmc.MantineProvider([
dmc.AppShell(
[
dmc.AppShellHeader(dmc.Group([
dmc.Title("My App", order=3),
dmc.Switch(id="theme-switch"),
], h="100%", px="md")),
dmc.AppShellNavbar([
dmc.NavLink(label=page["name"], href=page["path"], active=page["path"] == "/")
for page in dash.page_registry.values()
], p="md"),
dmc.AppShellMain(dash.page_container),
],
header={"height": 60},
navbar={"width": 250, "breakpoint": "sm", "collapsed": {"mobile": True}},
padding="md",
)
])
if __name__ == "__main__":
app.run(debug=True)# pages/home.py
import dash
import dash_mantine_components as dmc
dash.register_page(__name__, path="/", name="Home")
layout = dmc.Container([
dmc.Title("Welcome", order=2),
dmc.Text("Home page content"),
], py="xl")# pages/analytics.py
import dash
import dash_mantine_components as dmc
dash.register_page(__name__, path="/analytics", name="Analytics")
layout = dmc.Container([
dmc.Title("Analytics", order=2),
# Charts, tables, etc.
], py="xl")Variable Paths:
# pages/user.py
dash.register_page(__name__, path_template="/user/<user_id>")
def layout(user_id=None):
return dmc.Container([
dmc.Title(f"User: {user_id}", order=2),
])→ Full multi-page guide: references/multi-page-apps.md
---
Component Categories
Quick links to reference documentation:
| Category | Components | Reference |
|---|---|---|
| All Components | 90+ components with props/events | components-quick-ref.md |
| Theming | MantineProvider, theme object, colors | theming-patterns.md |
| Styling | Style props, Styles API, CSS variables | styling-guide.md |
| Callbacks | Pattern-matching, clientside, background | callbacks-advanced.md |
| Multi-Page | Dash Pages, routing, AppShell | multi-page-apps.md |
| Charts | Data formats, series config | charts-data-formats.md |
| Date Pickers | DatePicker, DatesProvider, localization | date-pickers-guide.md |
| Dash Core | dcc.Store, caching, performance | dash-fundamentals.md |
| Migration | v1.x to v2.x breaking changes | migration-v2.md |
Asset Templates
Copy and adapt these templates:
| Template | Description |
|---|---|
| app_single_page.py | Complete single-page DMC app with theme toggle |
| app_multi_page.py | Multi-page app with Dash Pages and AppShell |
| callbacks_patterns.py | All callback pattern examples |
| theme_presets.py | Pre-built theme configurations |
Utility Scripts
| Script | Usage |
|---|---|
| fetch_docs.py | python fetch_docs.py "Select" - Fetch/search official llms.txt |
| scaffold_app.py | python scaffold_app.py myapp --type multi --shell |
| generate_theme.py | python generate_theme.py --primary "#0ea5e9" |
| component_search.py | python component_search.py "select" |
---
Common Tasks
Form with Validation
@callback(
Output("submit-btn", "disabled"),
Output("error-text", "children"),
Input("email-input", "value"),
Input("password-input", "value"),
)
def validate_form(email, password):
errors = []
if not email or "@" not in email:
errors.append("Valid email required")
if not password or len(password) < 8:
errors.append("Password must be 8+ characters")
return bool(errors), ", ".join(errors)Modal Open/Close
app.layout = dmc.MantineProvider([
dmc.Button("Open Modal", id="open-modal-btn"),
dmc.Modal(
id="my-modal",
title="Confirm Action",
children=[
dmc.Text("Are you sure?"),
dmc.Group([
dmc.Button("Cancel", id="cancel-btn", variant="outline"),
dmc.Button("Confirm", id="confirm-btn", color="red"),
], justify="flex-end", mt="md"),
],
),
])
@callback(
Output("my-modal", "opened"),
Input("open-modal-btn", "n_clicks"),
Input("cancel-btn", "n_clicks"),
Input("confirm-btn", "n_clicks"),
prevent_initial_call=True,
)
def toggle_modal(open_clicks, cancel, confirm):
from dash import ctx
if ctx.triggered_id == "open-modal-btn":
return True
return FalseLoading State
from dash import dcc
app.layout = dmc.MantineProvider([
dmc.Button("Load Data", id="load-btn"),
dcc.Loading(
id="loading",
type="circle",
children=dmc.Container(id="data-container"),
),
])
@callback(Output("data-container", "children"), Input("load-btn", "n_clicks"))
def load_data(n):
import time
time.sleep(2) # Simulate slow operation
return dmc.Text("Data loaded!")Chart with Data
data = [
{"month": "Jan", "sales": 100, "profit": 20},
{"month": "Feb", "sales": 150, "profit": 35},
{"month": "Mar", "sales": 120, "profit": 25},
]
dmc.BarChart(
data=data,
dataKey="month",
series=[
{"name": "sales", "color": "blue.6"},
{"name": "profit", "color": "green.6"},
],
h=300,
withLegend=True,
withTooltip=True,
)---
Troubleshooting
Common Errors
| Error | Cause | Fix |
|---|---|---|
MantineProvider is required | Component outside provider | Wrap entire layout in dmc.MantineProvider([...]) |
Invalid theme color | Color not in theme | Use built-in colors (blue, red) or add to theme["colors"] |
Callback output not found | Component not in layout | Ensure component with ID exists in layout |
Circular callback detected | Output also used as Input | Use State instead of Input for non-triggering values |
Pattern-matching ID mismatch | Dict keys don't match | Ensure type and index keys match exactly |
Duplicate callback outputs | Same output in multiple callbacks | Add allow_duplicate=True to additional callbacks |
Debug Tips
1. Check browser console for JavaScript errors 2. Use `debug=True` in app.run() for detailed Python errors 3. Print `ctx.triggered_id` to see which input fired 4. Validate JSON-serializable callback returns (no Python objects) 5. Test with `prevent_initial_call=True` to avoid startup errors
DMC v2.x Gotchas
DateTimePicker: UsetimePickerPropsnottimeInputPropsCarousel: Embla options need{"containScroll": "trimSnaps"}wrapper- Default
reuseTargetNode=Truemay cause Portal issues - set toFalseif overlays misbehave - Use
MantineProvidernotMantineProviderV2(deprecated)
→ Full migration guide: references/migration-v2.md
"""Multi-Page Dash Mantine Components Application Template
Complete template for a multi-page DMC app with:
- Dash Pages integration
- AppShell layout (header, navbar, main content)
- NavLink navigation from page_registry
- Theme toggle with persistence
- Responsive sidebar
Directory structure:
app_multi_page.py (this file)
pages/
__init__.py
home.py
analytics.py
settings.py
Example page file (pages/home.py):
```python
import dash
from dash import html, callback, Input, Output
import dash_mantine_components as dmc
dash.register_page(__name__, path="/", name="Home", icon="radix-icons:home")
layout = dmc.Container(
size="lg",
py="xl",
children=[
dmc.Title("Home Page", order=1),
dmc.Text("Welcome to the home page!", mt="md"),
],
)
```
Run with: python app_multi_page.py
"""
import dash_mantine_components as dmc
from dash import (
Dash,
Input,
Output,
State,
clientside_callback,
dcc,
page_container,
)
from dash_iconify import DashIconify
# Initialize app with pages
app = Dash(
__name__,
title="DMC Multi-Page App",
use_pages=True,
suppress_callback_exceptions=True,
)
# Server for deployment
server = app.server
def create_navbar():
"""Create navigation sidebar with links from page_registry."""
from dash import page_registry
nav_links = []
for page_path, page_info in page_registry.items():
# Skip the not_found_404 page
if page_path == "dash.page_registry.not_found_404":
continue
nav_links.append(
dmc.NavLink(
label=page_info.get("name", "Page"),
href=page_info.get("path", "/"),
leftSection=DashIconify(
icon=page_info.get("icon", "radix-icons:dot-filled"),
width=20,
),
variant="subtle",
active="exact",
)
)
return dmc.Stack(
gap="xs",
p="md",
children=[
dmc.Title("Navigation", order=5, c="dimmed", mb="sm"),
*nav_links,
],
)
def create_header():
"""Create app header with title and theme toggle."""
return dmc.Group(
justify="space-between",
h="100%",
px="md",
children=[
dmc.Group(
gap="sm",
children=[
dmc.ActionIcon(
id="navbar-toggle",
variant="subtle",
color="gray",
size="lg",
hiddenFrom="sm",
children=DashIconify(
icon="radix-icons:hamburger-menu", width=20
),
),
dmc.Title("DMC Multi-Page App", order=3),
],
),
dmc.Group(
gap="sm",
children=[
dmc.Switch(
id="theme-switch",
onLabel=DashIconify(icon="radix-icons:sun", width=20),
offLabel=DashIconify(icon="radix-icons:moon", width=20),
size="lg",
persistence=True,
persistence_type="local",
),
],
),
],
)
# App layout with AppShell
app.layout = dmc.MantineProvider(
id="mantine-provider",
forceColorScheme="light",
children=[
# Theme persistence
dcc.Store(id="theme-store", storage_type="local", data={"theme": "light"}),
dmc.AppShell(
id="app-shell",
children=[
# Header
dmc.AppShellHeader(create_header()),
# Navbar (sidebar)
dmc.AppShellNavbar(
id="navbar",
children=create_navbar(),
),
# Main content area
dmc.AppShellMain(
dmc.Container(
size="xl",
py="md",
children=page_container,
)
),
],
header={"height": 60},
navbar={
"width": 250,
"breakpoint": "sm",
"collapsed": {"mobile": True},
},
padding="md",
),
],
)
# Theme toggle callback
@app.callback(
Output("mantine-provider", "forceColorScheme"),
Output("theme-store", "data"),
Input("theme-switch", "checked"),
prevent_initial_call=True,
)
def toggle_theme(checked):
"""Toggle between light and dark theme."""
theme = "dark" if checked else "light"
return theme, {"theme": theme}
# Initialize theme from storage
clientside_callback(
"""
function(data) {
if (data && data.theme) {
return data.theme === 'dark';
}
return false;
}
""",
Output("theme-switch", "checked"),
Input("theme-store", "data"),
)
# Navbar toggle for mobile
clientside_callback(
"""
function(n_clicks, opened) {
if (n_clicks) {
return !opened;
}
return opened || false;
}
""",
Output("navbar", "collapsed"),
Input("navbar-toggle", "n_clicks"),
State("navbar", "collapsed"),
prevent_initial_call=True,
)
if __name__ == "__main__":
# Import pages (create these files in a pages/ directory)
# The pages will be automatically registered if they use dash.register_page()
app.run(debug=True, port=8050)
# ============================================================================
# Example Page Files
# ============================================================================
"""
Create these files in a 'pages/' directory:
--- pages/__init__.py ---
(empty file)
--- pages/home.py ---
import dash
from dash import html, callback, Input, Output
import dash_mantine_components as dmc
from dash_iconify import DashIconify
dash.register_page(
__name__,
path="/",
name="Home",
icon="radix-icons:home",
)
layout = dmc.Container(
size="lg",
py="xl",
children=[
dmc.Stack(
gap="md",
children=[
dmc.Title("Welcome Home", order=1),
dmc.Text(
"This is a multi-page Dash Mantine Components application.",
size="lg",
),
dmc.Card(
withBorder=True,
shadow="sm",
radius="md",
p="xl",
children=[
dmc.Text("Quick Stats", fw=500, size="lg", mb="md"),
dmc.SimpleGrid(
cols={"base": 1, "sm": 3},
children=[
dmc.Paper(
p="md",
radius="md",
withBorder=True,
children=[
dmc.Group(gap="xs", mb="xs", children=[
DashIconify(icon="radix-icons:dashboard", width=20),
dmc.Text("Metric 1", size="sm", c="dimmed"),
]),
dmc.Title("1,234", order=3),
],
),
dmc.Paper(
p="md",
radius="md",
withBorder=True,
children=[
dmc.Group(gap="xs", mb="xs", children=[
DashIconify(icon="radix-icons:activity-log", width=20),
dmc.Text("Metric 2", size="sm", c="dimmed"),
]),
dmc.Title("567", order=3),
],
),
dmc.Paper(
p="md",
radius="md",
withBorder=True,
children=[
dmc.Group(gap="xs", mb="xs", children=[
DashIconify(icon="radix-icons:bar-chart", width=20),
dmc.Text("Metric 3", size="sm", c="dimmed"),
]),
dmc.Title("89", order=3),
],
),
],
),
],
),
],
),
],
)
--- pages/analytics.py ---
import dash
from dash import html, callback, Input, Output, dcc
import dash_mantine_components as dmc
import plotly.express as px
import pandas as pd
dash.register_page(
__name__,
path="/analytics",
name="Analytics",
icon="radix-icons:bar-chart",
)
# Sample data
df = pd.DataFrame({
"Month": ["Jan", "Feb", "Mar", "Apr", "May"],
"Sales": [100, 150, 120, 180, 200],
})
layout = dmc.Container(
size="lg",
py="xl",
children=[
dmc.Stack(
gap="md",
children=[
dmc.Title("Analytics Dashboard", order=1),
dmc.Card(
withBorder=True,
shadow="sm",
radius="md",
p="xl",
children=[
dmc.Text("Sales Over Time", fw=500, size="lg", mb="md"),
dcc.Graph(
figure=px.line(
df,
x="Month",
y="Sales",
markers=True,
).update_layout(
template="plotly_white",
margin=dict(l=0, r=0, t=0, b=0),
)
),
],
),
],
),
],
)
--- pages/settings.py ---
import dash
from dash import html, callback, Input, Output
import dash_mantine_components as dmc
from dash_iconify import DashIconify
dash.register_page(
__name__,
path="/settings",
name="Settings",
icon="radix-icons:gear",
)
layout = dmc.Container(
size="lg",
py="xl",
children=[
dmc.Stack(
gap="md",
children=[
dmc.Title("Settings", order=1),
dmc.Card(
withBorder=True,
shadow="sm",
radius="md",
p="xl",
children=[
dmc.Stack(
gap="lg",
children=[
dmc.TextInput(
label="Username",
placeholder="Enter username",
leftSection=DashIconify(icon="radix-icons:person"),
),
dmc.TextInput(
label="Email",
placeholder="Enter email",
leftSection=DashIconify(icon="radix-icons:envelope-closed"),
),
dmc.Switch(
label="Email notifications",
description="Receive email updates",
),
dmc.Switch(
label="Marketing emails",
description="Receive promotional content",
),
dmc.Button(
"Save Settings",
leftSection=DashIconify(icon="radix-icons:check"),
),
],
),
],
),
],
),
],
)
"""
"""Single-Page Dash Mantine Components Application Template
Compatible with: DMC 2.x (dash-mantine-components>=2.5.0)
Complete template for a single-page DMC app with:
- MantineProvider wrapper with theme persistence
- Theme toggle functionality (clientside callback)
- Basic layout with Container, Stack, Group
- Example form with inputs and submit callback
- Notification system
- Loading states for async operations
Run with: python app_single_page.py
"""
import dash_mantine_components as dmc
from dash import Dash, Input, Output, State, callback, clientside_callback, dcc, html
from dash_iconify import DashIconify
# Initialize app
app = Dash(
__name__,
title="DMC Single Page App",
update_title="Loading...",
suppress_callback_exceptions=True,
)
# App layout
app.layout = dmc.MantineProvider(
id="mantine-provider",
forceColorScheme="light",
children=[
# Theme persistence store
dcc.Store(id="theme-store", storage_type="local", data={"theme": "light"}),
# Notification container
dmc.NotificationContainer(position="top-right"),
html.Div(id="notifications-container"),
# Main content
dmc.Container(
size="md",
px="lg",
py="xl",
children=[
dmc.Stack(
gap="lg",
children=[
# Header with theme toggle
dmc.Group(
justify="space-between",
mb="xl",
children=[
dmc.Title("DMC Single Page App", order=1),
dmc.ActionIcon(
id="theme-toggle",
variant="subtle",
color="gray",
size="lg",
children=DashIconify(
id="theme-icon",
icon="radix-icons:moon",
width=20,
),
),
],
),
# Welcome card
dmc.Card(
withBorder=True,
shadow="sm",
radius="md",
children=[
dmc.Text(
"Welcome to your DMC app!",
size="lg",
fw=500,
mb="xs",
),
dmc.Text(
"This template includes theme switching, form handling, and notifications.",
size="sm",
c="dimmed",
),
],
),
# Example form
dmc.Paper(
withBorder=True,
shadow="xs",
radius="md",
p="xl",
children=[
dmc.Stack(
gap="md",
children=[
dmc.Title("Example Form", order=3, mb="sm"),
dmc.TextInput(
id="name-input",
label="Name",
placeholder="Enter your name",
required=True,
leftSection=DashIconify(
icon="radix-icons:person"
),
),
dmc.Select(
id="category-select",
label="Category",
placeholder="Select a category",
data=[
{
"label": "Development",
"value": "dev",
},
{"label": "Design", "value": "design"},
{
"label": "Marketing",
"value": "marketing",
},
],
required=True,
),
dmc.Textarea(
id="message-input",
label="Message",
placeholder="Enter your message",
minRows=3,
autosize=True,
),
dmc.Button(
id="submit-button",
children="Submit",
leftSection=DashIconify(
icon="radix-icons:paper-plane"
),
fullWidth=True,
),
],
),
],
),
# Results area with loading wrapper
dcc.Loading(
id="loading",
type="default",
children=html.Div(id="results-area"),
),
],
),
],
),
],
)
# Theme toggle clientside callback
clientside_callback(
"""
function(n_clicks, data) {
if (!n_clicks) {
return window.dash_clientside.no_update;
}
const newTheme = data.theme === 'light' ? 'dark' : 'light';
return [
newTheme,
{ theme: newTheme },
newTheme === 'dark' ? 'radix-icons:sun' : 'radix-icons:moon'
];
}
""",
[
Output("mantine-provider", "forceColorScheme"),
Output("theme-store", "data"),
Output("theme-icon", "icon"),
],
Input("theme-toggle", "n_clicks"),
State("theme-store", "data"),
)
# Form submit callback
@callback(
Output("notifications-container", "children"),
Output("results-area", "children"),
Input("submit-button", "n_clicks"),
State("name-input", "value"),
State("category-select", "value"),
State("message-input", "value"),
prevent_initial_call=True,
)
def handle_submit(n_clicks, name, category, message):
"""Handle form submission and display results."""
if not all([name, category]):
notification = dmc.Notification(
id="error-notification",
title="Validation Error",
message="Please fill in all required fields",
color="red",
action="show",
icon=DashIconify(icon="radix-icons:cross-circled"),
)
return notification, ""
# Success notification
notification = dmc.Notification(
id="success-notification",
title="Success!",
message=f"Form submitted by {name}",
color="green",
action="show",
icon=DashIconify(icon="radix-icons:check-circled"),
)
# Display results
results = dmc.Card(
withBorder=True,
shadow="sm",
radius="md",
children=[
dmc.Stack(
gap="xs",
children=[
dmc.Title("Submission Results", order=4, mb="sm"),
dmc.Text([dmc.Text("Name: ", fw=500, span=True), name]),
dmc.Text([dmc.Text("Category: ", fw=500, span=True), category]),
dmc.Text(
[
dmc.Text("Message: ", fw=500, span=True),
message or "No message",
]
),
],
),
],
)
return notification, results
if __name__ == "__main__":
app.run(debug=True, port=8050)
"""Dash Mantine Components - Callback Patterns Reference
Complete collection of callback patterns for DMC apps:
1. Standard callbacks (Input/Output/State)
2. Pattern-matching callbacks (ALL, MATCH, ALLSMALLER)
3. Clientside callbacks
4. Background callbacks with progress
5. Debounced inputs
6. Modal/Drawer patterns
7. PreventUpdate and no_update
8. callback_context (ctx) usage
Each pattern is a complete, runnable example.
"""
import dash_mantine_components as dmc
from dash import (
ALL,
MATCH,
Dash,
DiskcacheManager,
Input,
Output,
State,
callback,
clientside_callback,
ctx,
no_update,
)
from dash.exceptions import PreventUpdate
from dash_iconify import DashIconify
# ============================================================================
# 1. STANDARD CALLBACKS - Input/Output/State
# ============================================================================
"""
Basic callback with Input, Output, and State.
Inputs trigger the callback, States are read but don't trigger.
"""
@callback(
Output("result-text", "children"),
Input("submit-btn", "n_clicks"),
State("name-input", "value"),
State("age-input", "value"),
prevent_initial_call=True,
)
def standard_callback_example(n_clicks, name, age):
"""Standard callback pattern.
Args:
n_clicks: Triggers callback when button clicked
name: State value, read when callback triggered
age: State value, read when callback triggered
"""
if not name:
raise PreventUpdate
return f"Hello {name}, you are {age} years old!"
# Example layout for standard callback
def standard_callback_layout():
return dmc.Stack(
[
dmc.TextInput(id="name-input", label="Name", placeholder="Enter name"),
dmc.NumberInput(id="age-input", label="Age", min=0, max=120),
dmc.Button(id="submit-btn", children="Submit"),
dmc.Text(id="result-text"),
]
)
# ============================================================================
# 2. PATTERN-MATCHING CALLBACKS - ALL, MATCH, ALLSMALLER
# ============================================================================
"""
Pattern-matching callbacks allow dynamic components with dynamic callbacks.
- ALL: Matches all components with the pattern
- MATCH: Links components with the same index
- ALLSMALLER: Matches components with indices less than current
"""
# Example: Dynamic list with individual delete buttons
@callback(
Output({"type": "list-item", "index": MATCH}, "style"),
Input({"type": "delete-btn", "index": MATCH}, "n_clicks"),
prevent_initial_call=True,
)
def delete_item_match(n_clicks):
"""MATCH pattern: Delete button affects only its corresponding item.
Both components share the same index value.
"""
return {"display": "none"}
# Example: Update all items at once
@callback(
Output({"type": "counter", "index": ALL}, "children"),
Input("increment-all-btn", "n_clicks"),
State({"type": "counter", "index": ALL}, "children"),
prevent_initial_call=True,
)
def increment_all_counters(n_clicks, current_values):
"""ALL pattern: Updates all matching components.
Args:
n_clicks: Single input
current_values: List of all matching component values
Returns:
List of new values (same length as current_values)
"""
return [int(val or 0) + 1 for val in current_values]
# Example layout for pattern-matching
def pattern_matching_layout():
return dmc.Stack(
[
dmc.Button(id="increment-all-btn", children="Increment All"),
dmc.Stack(
[
dmc.Group(
[
dmc.Badge(
id={"type": "counter", "index": i},
children="0",
),
dmc.Paper(
id={"type": "list-item", "index": i},
children=f"Item {i}",
p="sm",
),
dmc.ActionIcon(
id={"type": "delete-btn", "index": i},
children=DashIconify(icon="radix-icons:cross-2"),
color="red",
variant="subtle",
),
]
)
for i in range(5)
]
),
]
)
# ============================================================================
# 3. CLIENTSIDE CALLBACKS
# ============================================================================
"""
Clientside callbacks run in the browser (JavaScript) for better performance.
Use for simple operations like theme toggles, UI updates, keyboard shortcuts.
"""
# Theme toggle (runs in browser, no server roundtrip)
clientside_callback(
"""
function(n_clicks, current_theme) {
if (!n_clicks) {
return window.dash_clientside.no_update;
}
return current_theme === 'light' ? 'dark' : 'light';
}
""",
Output("mantine-provider", "forceColorScheme"),
Input("theme-toggle-btn", "n_clicks"),
State("mantine-provider", "forceColorScheme"),
)
# Keyboard shortcut handler
clientside_callback(
"""
function(n_events) {
if (!n_events) {
return window.dash_clientside.no_update;
}
const key = event.key;
if (event.ctrlKey && key === 's') {
event.preventDefault();
return 'Save triggered (Ctrl+S)';
}
return window.dash_clientside.no_update;
}
""",
Output("keyboard-output", "children"),
Input("keyboard-listener", "n_events"),
)
# Example layout for clientside callbacks
def clientside_layout():
return dmc.Stack(
[
dmc.ActionIcon(
id="theme-toggle-btn",
children=DashIconify(icon="radix-icons:moon"),
),
html.Div(id="keyboard-listener", tabIndex=0),
dmc.Text(id="keyboard-output"),
]
)
# ============================================================================
# 4. BACKGROUND CALLBACKS WITH PROGRESS
# ============================================================================
"""
Background callbacks run long operations without blocking the app.
Requires a backend: DiskcacheManager or CeleryManager.
"""
import time
import diskcache
cache = diskcache.Cache("./cache")
background_callback_manager = DiskcacheManager(cache)
@callback(
Output("progress-output", "children"),
Input("start-job-btn", "n_clicks"),
background=True,
manager=background_callback_manager,
running=[
(Output("start-job-btn", "disabled"), True, False),
(Output("progress-bar", "value"), 0, 100),
],
progress=[Output("progress-bar", "value"), Output("progress-text", "children")],
cancel=[Input("cancel-btn", "n_clicks")],
prevent_initial_call=True,
)
def run_background_job(set_progress, n_clicks):
"""Background callback with progress updates and cancellation.
Args:
set_progress: Function to update progress outputs
n_clicks: Trigger input
"""
total_steps = 10
for i in range(total_steps):
# Check if cancelled
time.sleep(0.5)
# Update progress
progress = int((i + 1) / total_steps * 100)
set_progress((progress, f"Step {i + 1}/{total_steps}"))
return "Job completed!"
# Example layout for background callbacks
def background_callback_layout():
return dmc.Stack(
[
dmc.Button(id="start-job-btn", children="Start Long Job"),
dmc.Button(id="cancel-btn", children="Cancel", color="red"),
dmc.Progress(id="progress-bar", value=0),
dmc.Text(id="progress-text"),
dmc.Text(id="progress-output"),
]
)
# ============================================================================
# 5. DEBOUNCED INPUT CALLBACKS
# ============================================================================
"""
Debounced inputs wait for user to stop typing before triggering callback.
DMC components have built-in debounce property.
"""
@callback(
Output("search-results", "children"),
Input("search-input", "value"),
prevent_initial_call=True,
)
def debounced_search(search_value):
"""Callback triggered only after user stops typing (debounce).
The dmc.TextInput component has debounce=500 (milliseconds).
"""
if not search_value:
return "Start typing to search..."
# Simulate search
results = [f"Result for '{search_value}' - {i}" for i in range(5)]
return dmc.Stack([dmc.Text(result) for result in results])
# Example layout with debounce
def debounced_layout():
return dmc.Stack(
[
dmc.TextInput(
id="search-input",
label="Search",
placeholder="Type to search...",
debounce=500, # Wait 500ms after user stops typing
leftSection=DashIconify(icon="radix-icons:magnifying-glass"),
),
html.Div(id="search-results"),
]
)
# ============================================================================
# 6. MODAL AND DRAWER PATTERNS
# ============================================================================
"""
Modal and Drawer components use 'opened' property for visibility.
Common pattern: Button click toggles opened state.
"""
@callback(
Output("example-modal", "opened"),
Input("open-modal-btn", "n_clicks"),
Input("close-modal-btn", "n_clicks"),
State("example-modal", "opened"),
prevent_initial_call=True,
)
def toggle_modal(open_clicks, close_clicks, is_open):
"""Toggle modal open/close.
Use ctx.triggered_id to determine which button was clicked.
"""
if ctx.triggered_id == "open-modal-btn":
return True
if ctx.triggered_id == "close-modal-btn":
return False
return is_open
# Alternative: Simple toggle
@callback(
Output("example-drawer", "opened"),
Input("toggle-drawer-btn", "n_clicks"),
State("example-drawer", "opened"),
prevent_initial_call=True,
)
def toggle_drawer(n_clicks, is_open):
"""Simple toggle: invert current state."""
return not is_open
# Example layout for Modal/Drawer
def modal_drawer_layout():
return dmc.Stack(
[
dmc.Button(id="open-modal-btn", children="Open Modal"),
dmc.Modal(
id="example-modal",
title="Example Modal",
children=[
dmc.Text("Modal content here"),
dmc.Button(id="close-modal-btn", children="Close"),
],
),
dmc.Button(id="toggle-drawer-btn", children="Toggle Drawer"),
dmc.Drawer(
id="example-drawer",
title="Example Drawer",
children=[dmc.Text("Drawer content here")],
),
]
)
# ============================================================================
# 7. PREVENTUPDATE AND NO_UPDATE
# ============================================================================
"""
PreventUpdate: Raise to prevent callback from updating outputs
no_update: Return to skip updating specific outputs
"""
@callback(
Output("output-1", "children"),
Output("output-2", "children"),
Input("trigger-btn", "n_clicks"),
State("condition-input", "value"),
prevent_initial_call=True,
)
def prevent_update_example(n_clicks, condition):
"""Demonstrate PreventUpdate and no_update.
"""
# PreventUpdate: Stop entire callback
if condition == "stop":
raise PreventUpdate
# no_update: Skip updating specific outputs
if condition == "first_only":
return "Updated!", no_update
if condition == "second_only":
return no_update, "Updated!"
# Update both
return "Output 1 updated", "Output 2 updated"
# ============================================================================
# 8. CALLBACK_CONTEXT (ctx) USAGE
# ============================================================================
"""
ctx provides information about what triggered the callback.
Useful for multi-input callbacks.
"""
@callback(
Output("ctx-output", "children"),
Input("btn-1", "n_clicks"),
Input("btn-2", "n_clicks"),
Input("input-1", "value"),
prevent_initial_call=True,
)
def context_example(n1, n2, value):
"""Use ctx to determine which input triggered the callback.
"""
triggered_id = ctx.triggered_id
if triggered_id == "btn-1":
return "Button 1 was clicked"
if triggered_id == "btn-2":
return "Button 2 was clicked"
if triggered_id == "input-1":
return f"Input changed to: {value}"
return "No trigger detected"
# Advanced ctx usage: Get all trigger info
@callback(
Output("ctx-detailed", "children"),
Input("multi-btn", "n_clicks"),
Input({"type": "dynamic-btn", "index": ALL}, "n_clicks"),
)
def detailed_context_example(static_clicks, dynamic_clicks):
"""Access detailed context information.
"""
# triggered_id: ID of component that triggered callback
trigger_id = ctx.triggered_id
# triggered_prop_ids: Dict of all triggered property IDs
trigger_props = ctx.triggered_prop_ids
# outputs_list: List of all outputs
outputs = ctx.outputs_list
# Build response
info = f"""
Triggered ID: {trigger_id}
Triggered Props: {trigger_props}
Number of outputs: {len(outputs)}
"""
return dmc.Code(info, block=True)
# Example layout for ctx patterns
def context_layout():
return dmc.Stack(
[
dmc.Group(
[
dmc.Button(id="btn-1", children="Button 1"),
dmc.Button(id="btn-2", children="Button 2"),
]
),
dmc.TextInput(id="input-1", placeholder="Type here"),
dmc.Text(id="ctx-output"),
dmc.Button(id="multi-btn", children="Static Button"),
dmc.Group(
[
dmc.Button(
id={"type": "dynamic-btn", "index": i},
children=f"Dynamic {i}",
)
for i in range(3)
]
),
html.Div(id="ctx-detailed"),
]
)
# ============================================================================
# MAIN APP EXAMPLE
# ============================================================================
if __name__ == "__main__":
from dash import html
app = Dash(__name__)
app.layout = dmc.MantineProvider(
dmc.Container(
size="lg",
py="xl",
children=[
dmc.Title("DMC Callback Patterns", order=1, mb="xl"),
dmc.Accordion(
multiple=True,
children=[
dmc.AccordionItem(
value="standard",
children=[
dmc.AccordionControl("Standard Callbacks"),
dmc.AccordionPanel(standard_callback_layout()),
],
),
dmc.AccordionItem(
value="pattern",
children=[
dmc.AccordionControl("Pattern-Matching"),
dmc.AccordionPanel(pattern_matching_layout()),
],
),
dmc.AccordionItem(
value="clientside",
children=[
dmc.AccordionControl("Clientside Callbacks"),
dmc.AccordionPanel(clientside_layout()),
],
),
dmc.AccordionItem(
value="debounce",
children=[
dmc.AccordionControl("Debounced Input"),
dmc.AccordionPanel(debounced_layout()),
],
),
dmc.AccordionItem(
value="modal",
children=[
dmc.AccordionControl("Modal & Drawer"),
dmc.AccordionPanel(modal_drawer_layout()),
],
),
dmc.AccordionItem(
value="context",
children=[
dmc.AccordionControl("Callback Context"),
dmc.AccordionPanel(context_layout()),
],
),
],
),
],
),
)
app.run(debug=True, port=8050)
"""Dash Mantine Components - Theme Presets
Pre-built theme configurations and utilities:
1. LIGHT_THEME - Complete light theme configuration
2. DARK_THEME - Complete dark theme configuration
3. BRANDED_THEME - Custom brand color example
4. Component default overrides
5. Helper utilities for theme generation
Usage:
from theme_presets import LIGHT_THEME, DARK_THEME
app.layout = dmc.MantineProvider(
theme=LIGHT_THEME,
children=[...]
)
"""
# ============================================================================
# COMPLETE THEME CONFIGURATIONS
# ============================================================================
LIGHT_THEME = {
"colorScheme": "light",
"primaryColor": "blue",
"fontFamily": "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
"fontFamilyMonospace": "'Fira Code', 'Courier New', monospace",
"defaultRadius": "md",
"colors": {
"brand": [
"#e3f2fd",
"#bbdefb",
"#90caf9",
"#64b5f6",
"#42a5f5",
"#2196f3", # Base color
"#1e88e5",
"#1976d2",
"#1565c0",
"#0d47a1",
],
},
"shadows": {
"xs": "0 1px 2px 0 rgb(0 0 0 / 0.05)",
"sm": "0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)",
"md": "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
"lg": "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
"xl": "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)",
},
"headings": {
"fontFamily": "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
"fontWeight": "600",
"sizes": {
"h1": {"fontSize": "2.5rem", "lineHeight": "1.2"},
"h2": {"fontSize": "2rem", "lineHeight": "1.3"},
"h3": {"fontSize": "1.75rem", "lineHeight": "1.4"},
"h4": {"fontSize": "1.5rem", "lineHeight": "1.5"},
"h5": {"fontSize": "1.25rem", "lineHeight": "1.5"},
"h6": {"fontSize": "1rem", "lineHeight": "1.5"},
},
},
"spacing": {
"xs": "0.625rem", # 10px
"sm": "0.75rem", # 12px
"md": "1rem", # 16px
"lg": "1.25rem", # 20px
"xl": "1.5rem", # 24px
},
}
DARK_THEME = {
"colorScheme": "dark",
"primaryColor": "blue",
"fontFamily": "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
"fontFamilyMonospace": "'Fira Code', 'Courier New', monospace",
"defaultRadius": "md",
"colors": {
"dark": [
"#C1C2C5",
"#A6A7AB",
"#909296",
"#5c5f66",
"#373A40",
"#2C2E33",
"#25262b", # Base background
"#1A1B1E",
"#141517",
"#101113",
],
"brand": [
"#e3f2fd",
"#bbdefb",
"#90caf9",
"#64b5f6",
"#42a5f5",
"#2196f3", # Base color
"#1e88e5",
"#1976d2",
"#1565c0",
"#0d47a1",
],
},
"shadows": {
"xs": "0 1px 2px 0 rgb(0 0 0 / 0.25)",
"sm": "0 1px 3px 0 rgb(0 0 0 / 0.3), 0 1px 2px -1px rgb(0 0 0 / 0.3)",
"md": "0 4px 6px -1px rgb(0 0 0 / 0.35), 0 2px 4px -2px rgb(0 0 0 / 0.35)",
"lg": "0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.4)",
"xl": "0 20px 25px -5px rgb(0 0 0 / 0.45), 0 8px 10px -6px rgb(0 0 0 / 0.45)",
},
"headings": {
"fontFamily": "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
"fontWeight": "600",
"sizes": {
"h1": {"fontSize": "2.5rem", "lineHeight": "1.2"},
"h2": {"fontSize": "2rem", "lineHeight": "1.3"},
"h3": {"fontSize": "1.75rem", "lineHeight": "1.4"},
"h4": {"fontSize": "1.5rem", "lineHeight": "1.5"},
"h5": {"fontSize": "1.25rem", "lineHeight": "1.5"},
"h6": {"fontSize": "1rem", "lineHeight": "1.5"},
},
},
"spacing": {
"xs": "0.625rem", # 10px
"sm": "0.75rem", # 12px
"md": "1rem", # 16px
"lg": "1.25rem", # 20px
"xl": "1.5rem", # 24px
},
}
# Custom branded theme example
BRANDED_THEME = {
**LIGHT_THEME,
"primaryColor": "brand",
"colors": {
**LIGHT_THEME["colors"],
"brand": [
"#f0f4ff",
"#d9e2ff",
"#b3c3ff",
"#8da5ff",
"#6687ff",
"#4169e1", # Royal Blue - base
"#3457c9",
"#2845b1",
"#1c3399",
"#102181",
],
},
}
# ============================================================================
# COMPONENT DEFAULT OVERRIDES
# ============================================================================
COMPONENT_DEFAULTS = {
"Button": {
"radius": "md",
"variant": "filled",
"size": "md",
},
"TextInput": {
"radius": "md",
"size": "md",
},
"Select": {
"radius": "md",
"size": "md",
"searchable": True,
"clearable": True,
},
"Card": {
"withBorder": True,
"shadow": "sm",
"radius": "md",
"padding": "lg",
},
"Modal": {
"centered": True,
"radius": "md",
"overlayProps": {"backgroundOpacity": 0.55, "blur": 3},
},
"Notification": {
"radius": "md",
"autoClose": 5000,
},
}
# ============================================================================
# HELPER UTILITIES
# ============================================================================
def generate_color_palette(hex_color: str) -> list[str]:
"""Generate a 10-shade color palette from a single hex color.
Uses a simple algorithm to create lighter and darker shades.
For production, consider using a proper color library like coloraide.
Args:
hex_color: Base color in hex format (e.g., "#4169e1")
Returns:
List of 10 hex colors from lightest to darkest
Example:
>>> palette = generate_color_palette("#4169e1")
>>> print(palette[5]) # Base color
'#4169e1'
"""
# Remove '#' if present
hex_color = hex_color.lstrip("#")
# Convert to RGB
r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
def adjust_lightness(r: int, g: int, b: int, factor: float) -> str:
"""Adjust color lightness by factor."""
if factor > 1: # Lighter
r = int(r + (255 - r) * (factor - 1))
g = int(g + (255 - g) * (factor - 1))
b = int(b + (255 - b) * (factor - 1))
else: # Darker
r, g, b = int(r * factor), int(g * factor), int(b * factor)
# Clamp values
r, g, b = max(0, min(255, r)), max(0, min(255, g)), max(0, min(255, b))
return f"#{r:02x}{g:02x}{b:02x}"
# Generate palette: lighter shades -> base -> darker shades
palette = [
adjust_lightness(r, g, b, 1.8), # 0 - lightest
adjust_lightness(r, g, b, 1.6), # 1
adjust_lightness(r, g, b, 1.4), # 2
adjust_lightness(r, g, b, 1.2), # 3
adjust_lightness(r, g, b, 1.1), # 4
f"#{hex_color}", # 5 - base color
adjust_lightness(r, g, b, 0.9), # 6
adjust_lightness(r, g, b, 0.8), # 7
adjust_lightness(r, g, b, 0.7), # 8
adjust_lightness(r, g, b, 0.6), # 9 - darkest
]
return palette
def create_theme(
base_color: str,
color_scheme: str = "light",
font_family: str = None,
) -> dict:
"""Create a custom theme from a base color.
Args:
base_color: Primary brand color in hex format
color_scheme: 'light' or 'dark'
font_family: Optional custom font family
Returns:
Complete theme dictionary for MantineProvider
Example:
>>> custom_theme = create_theme("#ff6b6b", "dark", "Roboto")
>>> app.layout = dmc.MantineProvider(theme=custom_theme, children=[...])
"""
base_theme = DARK_THEME if color_scheme == "dark" else LIGHT_THEME
theme = {
**base_theme,
"primaryColor": "brand",
"colors": {
**base_theme["colors"],
"brand": generate_color_palette(base_color),
},
}
if font_family:
theme["fontFamily"] = font_family
theme["headings"]["fontFamily"] = font_family
return theme
# ============================================================================
# EXAMPLE USAGE
# ============================================================================
if __name__ == "__main__":
import dash_mantine_components as dmc
from dash import Dash
from dash_iconify import DashIconify
app = Dash(__name__)
# Use a pre-built theme
app.layout = dmc.MantineProvider(
theme=LIGHT_THEME,
children=[
dmc.Container(
size="lg",
py="xl",
children=[
dmc.Stack(
gap="md",
children=[
dmc.Title("Theme Presets Demo", order=1),
# Show all color shades
dmc.Card(
withBorder=True,
shadow="sm",
radius="md",
p="xl",
children=[
dmc.Text(
"Brand Color Palette",
fw=500,
size="lg",
mb="md",
),
dmc.Group(
gap="xs",
children=[
dmc.Paper(
h=50,
w=50,
style={"backgroundColor": color},
withBorder=True,
)
for color in LIGHT_THEME["colors"]["brand"]
],
),
],
),
# Component examples with default overrides
dmc.Card(
**COMPONENT_DEFAULTS["Card"],
children=[
dmc.Stack(
gap="md",
children=[
dmc.Text(
"Components with Default Overrides",
fw=500,
size="lg",
),
dmc.TextInput(
**COMPONENT_DEFAULTS["TextInput"],
label="Text Input",
placeholder="With default radius & size",
),
dmc.Select(
**COMPONENT_DEFAULTS["Select"],
label="Select",
placeholder="With default props",
data=[
"Option 1",
"Option 2",
"Option 3",
],
),
dmc.Button(
**COMPONENT_DEFAULTS["Button"],
children="Styled Button",
leftSection=DashIconify(
icon="radix-icons:check"
),
),
],
),
],
),
# Custom theme example
dmc.Card(
withBorder=True,
shadow="sm",
radius="md",
p="xl",
children=[
dmc.Text(
"Custom Theme Generation",
fw=500,
size="lg",
mb="md",
),
dmc.Code(
"""
custom_theme = create_theme(
base_color="#ff6b6b",
color_scheme="dark",
font_family="Roboto"
)
""",
block=True,
),
],
),
],
),
],
),
],
)
app.run(debug=True, port=8050)
Dash Advanced Callbacks Reference
Comprehensive guide to advanced callback patterns including standard callbacks, pattern-matching, callback context, clientside callbacks, background callbacks, and DMC-specific features.
Table of Contents
1. Standard Callbacks: Input/Output/State 2. Pattern-Matching Callbacks 3. Callback Context (ctx) 4. Clientside Callbacks 5. Background Callbacks 6. PreventUpdate and no_update 7. Running Parameter 8. Allow Duplicate 9. DMC-Specific Features
---
Standard Callbacks: Input/Output/State
Basic Callback Structure
from dash import Dash, html, dcc, callback, Input, Output, State
@callback(
Output('output-id', 'property-name'),
Input('input-id', 'property-name'),
State('state-id', 'property-name')
)
def callback_function(input_value, state_value):
"""
Inputs: Trigger callback when property changes
State: Provide current value but don't trigger
Output: Update this component property
"""
return f"Input: {input_value}, State: {state_value}"Multiple Inputs and Outputs
@callback(
Output('output-1', 'children'),
Output('output-2', 'children'),
Output('output-3', 'style'),
Input('dropdown-1', 'value'),
Input('dropdown-2', 'value'),
Input('slider', 'value'),
State('text-input', 'value')
)
def multi_callback(dd1_val, dd2_val, slider_val, text_val):
"""Multiple inputs/outputs - order matches function arguments."""
return (
f"Dropdown 1: {dd1_val}",
f"Dropdown 2: {dd2_val}",
{'color': 'red' if slider_val > 50 else 'blue'}
)Flexible Callback Signatures (Dash 2.0+)
Use dictionaries for keyword arguments - order doesn't matter:
@callback(
output=dict(
title=Output('title', 'children'),
figure=Output('graph', 'figure')
),
inputs=dict(
category=Input('category-dropdown', 'value'),
year=Input('year-slider', 'value')
),
state=dict(
data=State('data-store', 'data')
)
)
def update_dashboard(category, year, data):
"""Named arguments - order-independent."""
df = pd.DataFrame(data)
filtered = df[(df['cat'] == category) & (df['year'] == year)]
return dict(
title=f"{category} in {year}",
figure=px.bar(filtered, x='x', y='y')
)Chained Callbacks
Output of one callback becomes input to another:
@callback(
Output('intermediate-data', 'data'),
Input('upload-data', 'contents')
)
def process_upload(contents):
"""First callback: Process uploaded file."""
df = parse_contents(contents)
return df.to_dict('records')
@callback(
Output('graph', 'figure'),
Input('intermediate-data', 'data'),
Input('chart-type', 'value')
)
def update_graph(data, chart_type):
"""Second callback: Create visualization from processed data."""
df = pd.DataFrame(data)
if chart_type == 'bar':
return px.bar(df, x='x', y='y')
return px.line(df, x='x', y='y')---
Pattern-Matching Callbacks
Pattern-matching allows callbacks to respond to dynamic numbers of components using ALL, MATCH, and ALLSMALLER selectors.
Component IDs as Dictionaries
# Static ID (traditional)
html.Button("Click", id="my-button")
# Dynamic ID (pattern-matching)
html.Button("Click", id={"type": "filter-btn", "index": 0})
html.Button("Click", id={"type": "filter-btn", "index": 1})ALL Selector
Matches all components with specified type - passes all values as a list.
from dash import ALL, Patch
app.layout = html.Div([
html.Button("Add Filter", id="add-filter-btn", n_clicks=0),
html.Div(id="dropdown-container", children=[]),
html.Div(id="output")
])
@callback(
Output("dropdown-container", "children"),
Input("add-filter-btn", "n_clicks")
)
def add_dropdown(n_clicks):
"""Dynamically add dropdowns using Patch."""
patched_children = Patch()
new_dropdown = dcc.Dropdown(
options=['NYC', 'MTL', 'LA', 'TOKYO'],
id={"type": "city-dropdown", "index": n_clicks}
)
patched_children.append(new_dropdown)
return patched_children
@callback(
Output("output", "children"),
Input({"type": "city-dropdown", "index": ALL}, "value")
)
def display_output(values):
"""
Triggered when ANY dropdown changes.
Receives ALL dropdown values as a list.
"""
return html.Div([
html.Div(f"Dropdown {i + 1} = {val}")
for i, val in enumerate(values)
if val is not None
])MATCH Selector
Matches individual components - creates separate callback instance per matched component.
app.layout = html.Div([
html.Button("Add Item", id="add-btn", n_clicks=0),
html.Div(id="container", children=[])
])
@callback(
Output("container", "children"),
Input("add-btn", "n_clicks")
)
def add_item(n_clicks):
"""Add dropdown and corresponding output div."""
patched_children = Patch()
new_elements = html.Div([
dcc.Dropdown(
['Option A', 'Option B', 'Option C'],
id={"type": "dynamic-dropdown", "index": n_clicks}
),
html.Div(id={"type": "dynamic-output", "index": n_clicks})
])
patched_children.append(new_elements)
return patched_children
@callback(
Output({"type": "dynamic-output", "index": MATCH}, "children"),
Input({"type": "dynamic-dropdown", "index": MATCH}, "value"),
State({"type": "dynamic-dropdown", "index": MATCH}, "id")
)
def update_output(value, component_id):
"""
Separate callback instance for EACH dropdown.
Only receives the value from the MATCHING dropdown.
"""
return f"Dropdown {component_id['index']} selected: {value}"ALLSMALLER Selector
Passes values from components with smaller indices - useful for cascading filters.
app.layout = html.Div([
html.Button("Add Filter", id="add-filter", n_clicks=0),
html.Div(id="filter-container", children=[])
])
@callback(
Output("filter-container", "children"),
Input("add-filter", "n_clicks")
)
def add_filter_level(n_clicks):
"""Add cascading filter dropdowns."""
patched_children = Patch()
filter_div = html.Div([
html.Label(f"Filter Level {n_clicks}"),
dcc.Dropdown(id={"type": "filter-dd", "index": n_clicks}),
html.Div(id={"type": "filter-output", "index": n_clicks})
])
patched_children.append(filter_div)
return patched_children
@callback(
Output({"type": "filter-output", "index": MATCH}, "children"),
Input({"type": "filter-dd", "index": MATCH}, "value"),
Input({"type": "filter-dd", "index": ALLSMALLER}, "value")
)
def update_cascading_filter(current_value, previous_values):
"""
Receives:
- current_value: Value from MATCHING dropdown
- previous_values: List of values from ALL dropdowns with smaller indices
"""
if current_value is None:
return "Select a value"
# All filter values in order (oldest to newest)
all_filter_values = previous_values[::-1] + [current_value]
# Apply progressive filtering
filtered_count = apply_filters(all_filter_values)
return f"Filters applied: {all_filter_values} → {filtered_count} results"Pattern-Matching with Multiple Types
app.layout = html.Div([
html.Button("Add Chart", id="add-chart", n_clicks=0),
html.Div(id="charts-container", children=[])
])
@callback(
Output("charts-container", "children"),
Input("add-chart", "n_clicks")
)
def add_chart_controls(n_clicks):
"""Add chart with multiple control types."""
patched_children = Patch()
chart_group = html.Div([
dcc.Dropdown(
['bar', 'line', 'scatter'],
'bar',
id={"type": "chart-type", "index": n_clicks}
),
dcc.Slider(
min=0, max=100, value=50,
id={"type": "chart-slider", "index": n_clicks}
),
dcc.Graph(id={"type": "chart-graph", "index": n_clicks})
])
patched_children.append(chart_group)
return patched_children
@callback(
Output({"type": "chart-graph", "index": MATCH}, "figure"),
Input({"type": "chart-type", "index": MATCH}, "value"),
Input({"type": "chart-slider", "index": MATCH}, "value")
)
def update_chart(chart_type, slider_value):
"""Each chart responds to its own controls."""
df = generate_data(slider_value)
if chart_type == 'bar':
return px.bar(df, x='x', y='y')
elif chart_type == 'line':
return px.line(df, x='x', y='y')
return px.scatter(df, x='x', y='y')---
Callback Context (ctx)
Access information about which component triggered the callback using dash.callback_context.
Basic ctx Usage
from dash import ctx
import json
@callback(
Output('output', 'children'),
Input('btn-1', 'n_clicks'),
Input('btn-2', 'n_clicks'),
Input('btn-3', 'n_clicks')
)
def display_clicked_button(btn1, btn2, btn3):
"""Determine which button was clicked."""
if not ctx.triggered:
return "No button clicked yet"
# Get ID of triggering component
button_id = ctx.triggered_id
return f"Button '{button_id}' was clicked"ctx Properties
@callback(
Output('debug-output', 'children'),
Input('input-1', 'value'),
Input('input-2', 'value'),
State('state-1', 'value')
)
def debug_callback(input1, input2, state1):
"""Explore all ctx properties."""
info = {
'triggered_id': ctx.triggered_id, # ID of component that triggered
'triggered': ctx.triggered, # List of triggered inputs
'triggered_prop_ids': ctx.triggered_prop_ids, # Dict mapping IDs to props
'inputs': ctx.inputs, # All input values by ID
'states': ctx.states, # All state values by ID
'outputs_list': ctx.outputs_list # List of outputs
}
return html.Pre(json.dumps(info, indent=2))ctx with Pattern-Matching
@callback(
Output('selected-info', 'children'),
Input({'type': 'item-btn', 'index': ALL}, 'n_clicks')
)
def handle_dynamic_clicks(n_clicks_list):
"""Determine which dynamic button was clicked."""
if not ctx.triggered_id:
return "Click a button"
# ctx.triggered_id is the dictionary ID
clicked_id = ctx.triggered_id
clicked_index = clicked_id['index']
return f"Button at index {clicked_index} was clicked"ctx.args_grouping
Access grouped arguments when using flexible callback signatures:
@callback(
output=dict(
output1=Output('out-1', 'children'),
output2=Output('out-2', 'children')
),
inputs=dict(
input1=Input('in-1', 'value'),
input2=Input('in-2', 'value')
)
)
def grouped_callback(**kwargs):
"""Access inputs through ctx.args_grouping."""
inputs = ctx.args_grouping['inputs']
return dict(
output1=f"Input 1: {inputs['input1']['value']}",
output2=f"Input 2: {inputs['input2']['value']}"
)Conditional Logic Based on Trigger
@callback(
Output('display', 'children'),
Input('submit-btn', 'n_clicks'),
Input('reset-btn', 'n_clicks'),
State('input-field', 'value')
)
def handle_buttons(submit_clicks, reset_clicks, input_value):
"""Different actions based on which button triggered."""
if not ctx.triggered_id:
raise PreventUpdate
if ctx.triggered_id == 'submit-btn':
return f"Submitted: {input_value}"
elif ctx.triggered_id == 'reset-btn':
return "Form reset"
return "Unknown action"---
Clientside Callbacks
Execute callbacks in JavaScript directly in the browser for better performance with frequent updates or large data transfers.
Inline JavaScript
from dash import clientside_callback, Input, Output
clientside_callback(
"""
function(n_clicks) {
return 'Clicked ' + n_clicks + ' times';
}
""",
Output('output', 'children'),
Input('button', 'n_clicks')
)External JavaScript Files
Create assets/custom_clientside.js:
window.dash_clientside = Object.assign({}, window.dash_clientside, {
clientside: {
update_chart: function(slider_value, dropdown_value) {
// Complex JavaScript logic
const data = processData(slider_value, dropdown_value);
return {
data: data,
layout: {title: 'Updated Chart'}
};
},
format_number: function(value) {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(value);
}
}
});Reference in Python:
from dash import ClientsideFunction
clientside_callback(
ClientsideFunction(
namespace='clientside',
function_name='update_chart'
),
Output('graph', 'figure'),
Input('slider', 'value'),
Input('dropdown', 'value')
)Async Clientside Callbacks (Dash 2.4+)
Use promises for asynchronous operations:
clientside_callback(
"""
async function(url) {
try {
const response = await fetch(url);
const data = await response.json();
return data;
} catch (error) {
console.error('Fetch error:', error);
return dash_clientside.no_update;
}
}
""",
Output('data-table', 'data'),
Input('api-endpoint', 'value')
)Clientside Callback Context
clientside_callback(
"""
function(btn1_clicks, btn2_clicks) {
const triggered_id = dash_clientside.callback_context.triggered_id;
if (triggered_id === 'btn-1') {
return 'Button 1 clicked';
} else if (triggered_id === 'btn-2') {
return 'Button 2 clicked';
}
return 'No button clicked';
}
""",
Output('output', 'children'),
Input('btn-1', 'n_clicks'),
Input('btn-2', 'n_clicks')
)Partial Updates with Patch (Dash 3.3+)
clientside_callback(
"""
function(n_clicks) {
var patch = new dash_clientside.Patch();
// Toggle legend visibility
var showLegend = (n_clicks % 2 === 0);
// Only update specific property path
patch.assign(['layout', 'showlegend'], showLegend);
return patch.build();
}
""",
Output('graph', 'figure'),
Input('toggle-legend-btn', 'n_clicks')
)Set Props for Direct Updates (Dash 2.16+)
app.clientside_callback(
"""
function() {
// Listen for keyboard shortcuts
document.addEventListener('keydown', function(e) {
// Ctrl+S to save
if (e.ctrlKey && e.keyCode == 83) {
e.preventDefault();
dash_clientside.set_props('save-indicator', {
children: 'Saving...',
style: {color: 'orange'}
});
// Simulate save
setTimeout(function() {
dash_clientside.set_props('save-indicator', {
children: 'Saved!',
style: {color: 'green'}
});
}, 1000);
}
});
return dash_clientside.no_update;
}
""",
Output('init-trigger', 'data'),
Input('init-trigger', 'data')
)PreventUpdate and no_update in Clientside
clientside_callback(
"""
function(value) {
if (value === null || value === undefined) {
throw new window.dash_clientside.PreventUpdate();
}
if (value < 0) {
return dash_clientside.no_update;
}
return 'Value: ' + value;
}
""",
Output('output', 'children'),
Input('input', 'value')
)---
Background Callbacks
Execute long-running callbacks in separate worker processes to avoid timeouts and blocking.
Setup with DiskcacheManager
For local development:
from dash import Dash, DiskcacheManager, CeleryManager, Input, Output, callback
import diskcache
# Create cache directory
cache = diskcache.Cache("./cache")
background_callback_manager = DiskcacheManager(cache)
app = Dash(__name__, background_callback_manager=background_callback_manager)Setup with CeleryManager
For production with Redis:
import os
from celery import Celery
# Configure Celery
celery_app = Celery(
__name__,
broker=os.environ.get('REDIS_URL', 'redis://localhost:6379/0'),
backend=os.environ.get('REDIS_URL', 'redis://localhost:6379/1')
)
background_callback_manager = CeleryManager(
celery_app,
cache_by=[lambda: session_id] # Isolate by session
)
app = Dash(__name__, background_callback_manager=background_callback_manager)Basic Background Callback
@callback(
Output('result', 'children'),
Input('submit-btn', 'n_clicks'),
State('input-data', 'value'),
background=True,
manager=background_callback_manager
)
def long_running_process(n_clicks, data):
"""Runs in background worker process."""
if not n_clicks:
raise PreventUpdate
# Simulate long operation
time.sleep(10)
result = expensive_computation(data)
return f"Processing complete: {result}"Progress Tracking
app.layout = html.Div([
html.Button('Start', id='start-btn'),
dmc.Progress(id='progress-bar', value=0),
html.Div(id='progress-text'),
html.Div(id='result')
])
@callback(
output=Output('result', 'children'),
inputs=Input('start-btn', 'n_clicks'),
background=True,
manager=background_callback_manager,
progress=[
Output('progress-bar', 'value'),
Output('progress-text', 'children')
],
prevent_initial_call=True
)
def process_with_progress(set_progress, n_clicks):
"""
Background callback with progress updates.
set_progress is automatically injected as first parameter.
"""
total_steps = 10
for i in range(total_steps):
# Simulate work
time.sleep(1)
# Update progress
progress_value = int((i + 1) / total_steps * 100)
set_progress((progress_value, f'Step {i + 1}/{total_steps}'))
return "Processing complete!"Cancellation Support
app.layout = html.Div([
html.Button('Start Processing', id='start-btn'),
html.Button('Cancel', id='cancel-btn'),
html.Div(id='status'),
dmc.Progress(id='progress', value=0)
])
@callback(
output=Output('status', 'children'),
inputs=Input('start-btn', 'n_clicks'),
background=True,
manager=background_callback_manager,
running=[
(Output('start-btn', 'disabled'), True, False),
(Output('cancel-btn', 'disabled'), False, True)
],
cancel=Input('cancel-btn', 'n_clicks'),
progress=Output('progress', 'value')
)
def cancellable_process(set_progress, n_clicks):
"""
Background callback that can be cancelled.
When cancel input triggers, callback is terminated.
"""
for i in range(100):
time.sleep(0.5)
set_progress(i + 1)
return "Processing complete!"set_props for Dynamic Updates (Dash 2.17+)
from dash import set_props
@callback(
Output('final-output', 'children'),
Input('stream-btn', 'n_clicks'),
background=True,
manager=background_callback_manager
)
def streaming_updates(set_progress, n_clicks):
"""
Use set_props to update components not in callback outputs.
"""
if not n_clicks:
raise PreventUpdate
# Update multiple components dynamically
for i in range(10):
time.sleep(1)
# Update any component property on the page
set_props('live-status', {'children': f'Processing step {i + 1}'})
set_props('live-chart', {'figure': generate_chart(i)})
# Also update progress output
set_progress(f'Step {i + 1}/10')
return "Streaming complete!"Running State Management
@callback(
Output('result', 'children'),
Input('submit-btn', 'n_clicks'),
background=True,
manager=background_callback_manager,
running=[
(Output('submit-btn', 'disabled'), True, False),
(Output('submit-btn', 'children'), 'Processing...', 'Submit'),
(Output('loading-spinner', 'style'), {'display': 'block'}, {'display': 'none'})
]
)
def process_with_running_state(n_clicks):
"""
running parameter: (Output, value_while_running, value_after_completion)
"""
time.sleep(5)
return "Processing complete"---
PreventUpdate and no_update
PreventUpdate: Block All Updates
from dash.exceptions import PreventUpdate
@callback(
Output('output-1', 'children'),
Output('output-2', 'children'),
Input('input', 'value')
)
def conditional_update(value):
"""Prevent all outputs from updating."""
if value is None or value == '':
# Stop callback execution entirely
raise PreventUpdate
return f"Value: {value}", f"Length: {len(value)}"no_update: Selective Updates
from dash import no_update
@callback(
Output('valid-output', 'children'),
Output('error-output', 'children'),
Output('warning-output', 'children'),
Input('input', 'value')
)
def selective_update(value):
"""Update only specific outputs."""
if value is None:
raise PreventUpdate
# Validation
if len(value) < 3:
# Only update error, leave others unchanged
return no_update, "Must be at least 3 characters", no_update
if any(char.isdigit() for char in value):
# Only update warning
return no_update, no_update, "Contains numbers"
# Valid input - update valid output, clear error/warning
return f"Valid: {value}", "", ""Combining PreventUpdate and no_update
@callback(
Output('data-display', 'children'),
Output('error-msg', 'children'),
Input('fetch-btn', 'n_clicks'),
State('api-url', 'value')
)
def fetch_data(n_clicks, url):
"""Prevent vs selective update based on conditions."""
# Prevent update on initial load
if not n_clicks:
raise PreventUpdate
# Validate URL
if not url:
# Update only error message
return no_update, "Please enter a URL"
try:
data = requests.get(url).json()
# Clear error, update data
return json.dumps(data, indent=2), ""
except Exception as e:
# Update only error, leave data unchanged
return no_update, f"Error: {str(e)}"---
Running Parameter
Show loading states and disable controls during callback execution.
Basic Usage
@callback(
Output('result', 'children'),
Input('submit-btn', 'n_clicks'),
running=[
(Output('submit-btn', 'disabled'), True, False)
]
)
def process_data(n_clicks):
"""Button disabled while callback runs."""
if not n_clicks:
raise PreventUpdate
time.sleep(3) # Simulate work
return "Complete!"Multiple Running States
@callback(
Output('output', 'children'),
Input('process-btn', 'n_clicks'),
running=[
(Output('process-btn', 'disabled'), True, False),
(Output('process-btn', 'children'), 'Processing...', 'Process'),
(Output('loading-overlay', 'style'), {'display': 'flex'}, {'display': 'none'}),
(Output('cancel-btn', 'disabled'), False, True)
]
)
def long_process(n_clicks):
"""Multiple UI updates during execution."""
if not n_clicks:
raise PreventUpdate
time.sleep(5)
return "Processing complete!"With DMC Components
import dash_mantine_components as dmc
@callback(
Output('result', 'children'),
Input('submit-btn', 'n_clicks'),
running=[
(Output('submit-btn', 'loading'), True, False),
(Output('submit-btn', 'disabled'), True, False),
(Output('progress-bar', 'style'), {'display': 'block'}, {'display': 'none'})
]
)
def process_with_dmc(n_clicks):
"""DMC Button has built-in loading prop."""
if not n_clicks:
raise PreventUpdate
time.sleep(3)
return "Done!"---
Allow Duplicate
Allow multiple callbacks to update the same output property.
Basic allow_duplicate Usage
# First callback updates output
@callback(
Output('display', 'children'),
Input('btn-1', 'n_clicks')
)
def update_from_btn1(n_clicks):
"""Primary callback - no allow_duplicate needed."""
return f"Button 1 clicked {n_clicks} times"
# Second callback also updates same output
@callback(
Output('display', 'children', allow_duplicate=True),
Input('btn-2', 'n_clicks'),
prevent_initial_call=True # Required with allow_duplicate
)
def update_from_btn2(n_clicks):
"""Secondary callback - requires allow_duplicate."""
return f"Button 2 clicked {n_clicks} times"Reset Pattern
@callback(
Output('counter', 'children'),
Input('increment-btn', 'n_clicks'),
State('counter', 'children')
)
def increment(n_clicks, current):
"""Main counter logic."""
if current is None:
current = "0"
return str(int(current) + 1)
@callback(
Output('counter', 'children', allow_duplicate=True),
Input('reset-btn', 'n_clicks'),
prevent_initial_call=True
)
def reset_counter(n_clicks):
"""Reset counter to zero."""
return "0"Validation + Update Pattern
@callback(
Output('validated-input', 'value'),
Output('error-msg', 'children'),
Input('validated-input', 'value')
)
def validate_input(value):
"""Primary validation callback."""
if value and not value.isalnum():
return value, "Only alphanumeric characters allowed"
return value, ""
@callback(
Output('validated-input', 'value', allow_duplicate=True),
Input('clear-btn', 'n_clicks'),
prevent_initial_call=True
)
def clear_input(n_clicks):
"""Clear input on button click."""
return ""---
DMC-Specific Features
Debounce Property
DMC components support debounce prop to limit callback firing frequency:
import dash_mantine_components as dmc
app.layout = dmc.MantineProvider([
dmc.TextInput(
id='search-input',
label='Search',
placeholder='Type to search...',
debounce=500 # Wait 500ms after typing stops
),
html.Div(id='search-results')
])
@callback(
Output('search-results', 'children'),
Input('search-input', 'value')
)
def search(query):
"""Only fires 500ms after user stops typing."""
if not query:
return ""
results = perform_search(query)
return f"Found {len(results)} results for '{query}'"Persistence Properties
DMC components support persistence for storing user input across sessions:
dmc.TextInput(
id='username-input',
persistence=True, # Enable persistence
persistence_type='local' # 'local', 'session', or 'memory'
)
dmc.MultiSelect(
id='tags-select',
data=['Python', 'JavaScript', 'Rust', 'Go'],
persistence=True,
persistence_type='session'
)Loading Overlays
import dash_mantine_components as dmc
app.layout = dmc.MantineProvider([
dmc.Button('Load Data', id='load-btn'),
dmc.LoadingOverlay(
dmc.Table(id='data-table'),
id='table-loader'
)
])
@callback(
Output('data-table', 'children'),
Output('table-loader', 'visible'),
Input('load-btn', 'n_clicks'),
running=[
(Output('table-loader', 'visible'), True, False)
]
)
def load_data(n_clicks):
"""Show loading overlay during data fetch."""
if not n_clicks:
raise PreventUpdate
time.sleep(2) # Simulate data loading
data = fetch_data()
return create_table(data), FalseNotifications
import dash_mantine_components as dmc
@callback(
Output('notification-container', 'children'),
Input('submit-btn', 'n_clicks'),
State('form-data', 'value'),
prevent_initial_call=True
)
def show_notification(n_clicks, data):
"""Display notification based on validation."""
if not data:
return dmc.Notification(
title="Validation Error",
message="Please fill in all required fields",
color="red",
action="show",
id="error-notification"
)
# Process data
success = process_form(data)
if success:
return dmc.Notification(
title="Success",
message="Form submitted successfully!",
color="green",
action="show",
icon=DashIconify(icon="mdi:check-circle"),
id="success-notification"
)
return dmc.Notification(
title="Error",
message="An error occurred during submission",
color="red",
action="show",
id="error-notification"
)Modals with Callbacks
app.layout = dmc.MantineProvider([
dmc.Button('Open Modal', id='open-modal-btn'),
dmc.Modal(
title='Confirm Action',
id='confirm-modal',
children=[
dmc.Text('Are you sure you want to proceed?'),
dmc.Group([
dmc.Button('Cancel', id='cancel-btn', variant='outline'),
dmc.Button('Confirm', id='confirm-btn', color='red')
])
]
),
html.Div(id='action-result')
])
@callback(
Output('confirm-modal', 'opened'),
Input('open-modal-btn', 'n_clicks'),
Input('cancel-btn', 'n_clicks'),
Input('confirm-btn', 'n_clicks'),
State('confirm-modal', 'opened'),
prevent_initial_call=True
)
def toggle_modal(open_clicks, cancel_clicks, confirm_clicks, is_open):
"""Toggle modal visibility based on button clicks."""
if ctx.triggered_id == 'open-modal-btn':
return True
elif ctx.triggered_id in ['cancel-btn', 'confirm-btn']:
return False
return is_open
@callback(
Output('action-result', 'children'),
Input('confirm-btn', 'n_clicks'),
prevent_initial_call=True
)
def perform_action(n_clicks):
"""Execute action when confirmed."""
perform_critical_operation()
return "Action completed successfully"---
References
Based on official Dash documentation:
DMC Charts Data Formats and Configuration
Complete reference for data formats, series configuration, and common properties across all DMC chart components.
Table of Contents
- Data Format
- Series Configuration
- Common Chart Props
- Chart Types
- Axis Configuration
- Styling and Customization
Data Format
General Structure
All DMC charts accept data as a list of dictionaries:
data = [
{"date": "Mar 22", "Apples": 2890, "Oranges": 2338, "Tomatoes": 2452},
{"date": "Mar 23", "Apples": 2756, "Oranges": 2103, "Tomatoes": 2402},
{"date": "Mar 24", "Apples": 3322, "Oranges": 986, "Tomatoes": 1821},
]Key Requirements:
- Each dictionary represents one data point
- Must have a
dataKeyfield (e.g., "date", "month", "category") - Additional fields correspond to series names
Chart-Specific Data
AreaChart, LineChart, BarChart:
data = [
{"month": "January", "Smartphones": 1200, "Laptops": 900, "Tablets": 200},
{"month": "February", "Smartphones": 1900, "Laptops": 1200, "Tablets": 400},
]Waterfall Chart (BarChart type="waterfall"):
data = [
{"item": "TaxRate", "value": 21, "color": "blue"},
{"item": "Foreign inc.", "value": -15.5, "color": "teal"},
{"item": "ETR", "value": 3.5, "color": "blue", "standalone": True},
]Split Area Chart (type="split"):
data = [
{"date": "Mar 22", "value": 110},
{"date": "Mar 23", "value": 60},
{"date": "Mar 24", "value": -80}, # Negative values
]Series Configuration
Basic Series Structure
series = [
{"name": "Apples", "color": "indigo.6"},
{"name": "Oranges", "color": "blue.6"},
{"name": "Tomatoes", "color": "teal.6"}
]Series Properties
Required:
name(string) - Must match a key in the data
Optional:
color(string) - Theme color reference (e.g., "blue.6") or CSS colorlabel(string) - Display label in legend (defaults toname)yAxisId(string) - Bind to right Y axis: "right"strokeDasharray(string) - Line style: "5 5" for dashedfill(string) - Bar fill pattern: "url(#pattern-id)"stackId(string) - Group series in stacked charts
Examples
Custom Labels:
series = [
{"name": "Apples", "label": "Apple Sales", "color": "red.6"},
{"name": "Oranges", "label": "Orange Sales", "color": "orange.6"},
]Right Y Axis:
series = [
{"name": "temperature", "color": "red.6"},
{"name": "humidity", "color": "blue.6", "yAxisId": "right"},
]Dashed Lines:
series = [
{"name": "Actual", "color": "blue.6"},
{"name": "Forecast", "color": "gray.6", "strokeDasharray": "5 5"},
]Common Chart Props
Data Props
data(list[dict], required) - Chart datadataKey(str, required) - Key for X-axis valuesseries(list[dict], required) - Series configuration
Display Props
h(int) - Chart height in pixelswithLegend(bool) - Show legend (default: False)withTooltip(bool) - Show tooltip (default: True)withXAxis(bool) - Show X axis (default: True)withYAxis(bool) - Show Y axis (default: True)withDots(bool) - Show data point dots (default: True for Line/Area)
Axis Props
xAxisLabel(str) - Label below X axisyAxisLabel(str) - Label next to Y axisxAxisProps(dict) - Props for recharts XAxisyAxisProps(dict) - Props for recharts YAxiswithRightYAxis(bool) - Show right Y axisrightYAxisLabel(str) - Label for right Y axisrightYAxisProps(dict) - Props for right YAxis
Grid and Styling
gridAxis(str) - Grid lines: "none", "x", "y", "xy" (default: "x")tickLine(str) - Tick lines: "none", "x", "y", "xy" (default: "y")gridColor(str) - Grid line colortextColor(str) - Text colorstrokeDasharray(str) - Grid dash pattern (default: "5 5")
Tooltip and Animation
tooltipAnimationDuration(int) - Tooltip animation in ms (default: 0)tooltipProps(dict) - Props for recharts TooltipvalueFormatter(dict) - Function to format values:{"function": "funcName"}
Legend
legendProps(dict) - Props for recharts LegendverticalAlign: "top"/"middle"/"bottom"height: Number in pixelsalign: "left"/"center"/"right"
Chart Types
AreaChart
Types:
type="default"- Regular area charttype="stacked"- Stacked areastype="percent"- Percentage stackedtype="split"- Split by positive/negative
Specific Props:
withGradient(bool) - Gradient fill (default: False)splitColors(list) - Colors for split type: ["green.7", "red.7"]curveType(str) - Line curve: "monotone", "linear", "natural", "step"fillOpacity(float) - Area opacity: 0-1 (default: 0.2)strokeWidth(int) - Line width (default: 2)connectNulls(bool) - Connect across null values (default: True)dotProps(dict) - Props for dotsactiveDotProps(dict) - Props for active dot
BarChart
Types:
type="default"- Regular barstype="stacked"- Stacked barstype="percent"- Percentage stackedtype="waterfall"- Waterfall chart
Specific Props:
orientation(str) - "horizontal" or "vertical"withBarValueLabel(bool) - Show values on barsvalueLabelProps(dict) - Props for value labelsbarProps(dict) - Props for recharts BargetBarColor(dict) - Function to color bars:{"function": "funcName"}fillOpacity(float) - Bar opacity (default: 1)maxBarWidth(int) - Max bar width in pixelsminBarSize(int) - Min bar height in pixelscursorFill(str) - Hover fill color
LineChart
Types:
type="default"- Regular line charttype="gradient"- Gradient fill
Specific Props:
curveType(str) - Line curve: "monotone", "linear", "natural", "step"strokeWidth(int) - Line width (default: 2)connectNulls(bool) - Connect across nulls (default: True)dotProps(dict) - Props for dotsactiveDotProps(dict) - Props for active dotgradientStops(list) - Gradient colors for gradient type:
[
{"offset": 0, "color": "red.6"},
{"offset": 50, "color": "yellow.5"},
{"offset": 100, "color": "blue.5"}
]Axis Configuration
X Axis
xAxisProps = {
"angle": -20, # Rotate labels
"tickMargin": 15, # Space between tick and label
"orientation": "top", # "top" or "bottom"
"padding": {"left": 30, "right": 30}, # End padding
}Y Axis
yAxisProps = {
"domain": [0, 100], # Fixed range
"tickMargin": 15, # Space between tick and label
"orientation": "right", # "left" or "right"
}Dual Y Axis
dmc.AreaChart(
data=data,
dataKey="name",
withRightYAxis=True,
yAxisLabel="Primary",
rightYAxisLabel="Secondary",
series=[
{"name": "primary", "color": "blue.6"},
{"name": "secondary", "color": "red.6", "yAxisId": "right"},
],
)Styling and Customization
Colors
Theme Colors:
series = [
{"name": "series1", "color": "blue.6"}, # Theme color
{"name": "series2", "color": "orange.7"},
{"name": "series3", "color": "#FF5733"}, # CSS color
]Color Scheme Dependent:
# In CSS file:
"""
:root {
--chart-color: var(--mantine-color-orange-8)
}
:root[data-mantine-color-scheme="dark"] {
--chart-color: var(--mantine-color-lime-4);
}
"""
# In Python:
series = [{"name": "data", "color": "var(--chart-color)"}]Grid and Text Colors
Using CSS Variables:
# In CSS:
"""
.custom-chart {
--chart-grid-color: var(--mantine-color-blue-5);
--chart-text-color: var(--mantine-color-blue-8);
}
"""
# In Python:
dmc.BarChart(className="custom-chart", ...)Using Props (single color scheme):
dmc.LineChart(
gridColor="gray.5",
textColor="gray.9",
...
)Reference Lines
referenceLines = [
{
"y": 50,
"label": "Target",
"color": "red.6",
"labelPosition": "insideTopRight"
},
{
"x": "Mar 25",
"label": "Event",
"color": "blue.6"
}
]Units
dmc.BarChart(
data=data,
unit="$", # Adds $ to Y-axis ticks and tooltip
...
)Value Formatting
Use custom JavaScript functions for formatting:
# In Python:
dmc.AreaChart(
valueFormatter={"function": "formatCurrency"},
...
)// In assets/custom.js:
var dmcfuncs = window.dashMantineFunctions = window.dashMantineFunctions || {};
dmcfuncs.formatCurrency = (value) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(value);
};Sync Multiple Charts
Sync tooltips across charts with same syncId:
dmc.Stack([
dmc.AreaChart(
areaChartProps={"syncId": "sales"},
...
),
dmc.BarChart(
barChartProps={"syncId": "sales"},
...
),
])Interactive Features
Click and Hover Data
from dash import callback, Input, Output
dmc.BarChart(id="chart", ...)
@callback(
Output("output", "children"),
Input("chart", "clickData"),
Input("chart", "clickSeriesName"),
)
def handle_click(data, series_name):
return f"Clicked: {data}, Series: {series_name}"
@callback(
Output("hover-output", "children"),
Input("chart", "hoverData"),
Input("chart", "hoverSeriesName"),
)
def handle_hover(data, series_name):
return f"Hovering: {data}, Series: {series_name}"Highlight on Hover
dmc.LineChart(
highlightHover=True, # Highlight series on hover
...
)Best Practices
1. Consistent data keys - Use the same field names across all data points 2. Handle nulls - Set connectNulls based on desired behavior 3. Color accessibility - Use high contrast colors for better readability 4. Responsive heights - Use appropriate h values for different screen sizes 5. Format values - Use valueFormatter for consistent number formatting 6. Limit series - Too many series can make charts hard to read 7. Use legends - Enable legends for multi-series charts 8. Test tooltips - Verify tooltip formatting with various data values
Common Patterns
Dynamic Series from Data
# Extract series names from data keys
data = [{"month": "Jan", "A": 100, "B": 200, "C": 150}, ...]
series_names = [k for k in data[0].keys() if k != "month"]
series = [{"name": name, "color": f"blue.{i}"} for i, name in enumerate(series_names)]
dmc.BarChart(data=data, dataKey="month", series=series)Conditional Styling
# In assets/functions.js
dmcfuncs.colorByValue = (value) => {
if (value > 1000) return 'green.7';
if (value > 500) return 'yellow.6';
return 'red.6';
};
# In Python
dmc.BarChart(
getBarColor={"function": "colorByValue"},
...
)Dash Fundamentals Reference
Comprehensive guide to Dash application architecture, state management, data sharing patterns, performance optimization, and best practices.
Table of Contents
1. Application Structure & Lifecycle 2. State Management with dcc.Store 3. Data Sharing Patterns 4. Performance Optimization 5. Callback Gotchas 6. Error Handling Best Practices
---
Application Structure & Lifecycle
Basic Dash App Structure
from dash import Dash, html, dcc, callback, Input, Output
import dash_mantine_components as dmc
# Initialize app
app = Dash(__name__)
# Define layout
app.layout = dmc.MantineProvider([
html.Div([
dcc.Store(id='session-data', storage_type='session'),
dmc.Title("My Dash App", order=1),
dmc.Button("Click Me", id="btn"),
html.Div(id="output")
])
])
# Define callbacks
@callback(
Output("output", "children"),
Input("btn", "n_clicks"),
prevent_initial_call=True
)
def update_output(n_clicks):
return f"Clicked {n_clicks} times"
if __name__ == '__main__':
app.run(debug=True)Callback Execution Lifecycle
Dash uses reactive programming - when inputs change, outputs automatically update:
1. Initial Load: All callbacks with outputs fire once (unless prevent_initial_call=True) 2. User Interaction: Input property changes trigger associated callbacks 3. Callback Execution: Dash collects current state of all Input/State properties 4. Output Update: Results update specified Output properties 5. Cascade: If outputs are inputs to other callbacks, those fire next
Key Principle: "It's sort of like programming with Microsoft Excel: whenever a cell changes (the input), all the cells that depend on that cell (the outputs) will get updated automatically."
Configuration Options
app = Dash(
__name__,
suppress_callback_exceptions=True, # Allow dynamic layouts
update_title=None, # Disable "Updating..." in browser title
assets_folder='assets', # Custom assets directory
background_callback_manager=background_callback_manager # For long callbacks
)
# Additional config
app.config.prevent_initial_callbacks = True # Global prevent_initial_call---
State Management with dcc.Store
The dcc.Store component provides client-side JSON storage for sharing data between callbacks.
Storage Types
from dash import dcc
# Memory storage (default) - cleared on page refresh
dcc.Store(id='memory-store', storage_type='memory')
# Session storage - cleared when browser tab closes
dcc.Store(id='session-store', storage_type='session')
# Local storage - persists across browser sessions
dcc.Store(id='local-store', storage_type='local')Key Properties
| Property | Type | Description |
|---|---|---|
data | dict/list/number/string/bool | Stored JSON data |
modified_timestamp | number | Read-only timestamp of last modification |
clear_data | bool | Set to True to clear stored data |
storage_type | string | 'memory', 'local', or 'session' |
Storage Limitations
- Maximum size: 2MB in most environments, 5-10MB on desktop browsers
- Serialization: Data must be JSON-serializable
- Browser-specific: Limits vary by browser and device type
Basic Usage Pattern
from dash import Dash, html, dcc, callback, Input, Output, State
app.layout = html.Div([
dcc.Store(id='user-data', storage_type='session'),
dcc.Dropdown(['A', 'B', 'C'], id='dropdown'),
html.Div(id='output')
])
# Store data
@callback(
Output('user-data', 'data'),
Input('dropdown', 'value')
)
def store_selection(value):
return {'selected': value, 'timestamp': time.time()}
# Retrieve data
@callback(
Output('output', 'children'),
Input('user-data', 'modified_timestamp'),
State('user-data', 'data')
)
def display_data(ts, data):
if data is None:
return "No data stored"
return f"Selected: {data['selected']}"Initial Data Retrieval Pattern
Important: When using data as an output, you cannot access initial data via the data property. Use modified_timestamp as Input and data as State:
@callback(
Output('display', 'children'),
Input('store', 'modified_timestamp'), # Trigger on changes
State('store', 'data') # Access the actual data
)
def display_data(ts, data):
return f"Data: {data}"Clearing Stored Data
@callback(
Output('store', 'clear_data'),
Input('reset-btn', 'n_clicks')
)
def clear_store(n_clicks):
if n_clicks:
return True
return False---
Data Sharing Patterns
Core Principle: No Global Variables
Critical Rule: "Dash Callbacks must never modify variables outside of their scope."
Why: Dash apps run across multiple workers whose memory is not shared. Modifying globals breaks multi-user deployments.
# ❌ WRONG - Do not modify global variables
df_filtered = df # Global variable
@callback(Output('graph', 'figure'), Input('dropdown', 'value'))
def update_graph(value):
global df_filtered # NEVER DO THIS
df_filtered = df[df['category'] == value]
return px.line(df_filtered)
# ✅ CORRECT - Use local variables
@callback(Output('graph', 'figure'), Input('dropdown', 'value'))
def update_graph(value):
df_filtered = df[df['category'] == value] # Local variable
return px.line(df_filtered)Strategy 1: Browser Storage (dcc.Store)
Best for moderate datasets displayed across multiple components.
@callback(
Output('filtered-data-store', 'data'),
Input('filter-dropdown', 'value')
)
def filter_data(filter_value):
filtered_df = df[df['category'] == filter_value]
return filtered_df.to_dict('records') # Serialize to JSON
@callback(
Output('graph-1', 'figure'),
Input('filtered-data-store', 'data')
)
def update_graph_1(data):
df_filtered = pd.DataFrame(data)
return px.bar(df_filtered, x='x', y='y')
@callback(
Output('table', 'data'),
Input('filtered-data-store', 'data')
)
def update_table(data):
return data # Already in dict formatStrategy 2: Server-Side Caching with Flask-Caching
Best for large datasets and expensive computations.
from flask_caching import Cache
import uuid
cache = Cache(app.server, config={
'CACHE_TYPE': 'redis',
'CACHE_REDIS_URL': os.environ.get('REDIS_URL', 'redis://localhost:6379')
})
# Or for development
cache = Cache(app.server, config={
'CACHE_TYPE': 'filesystem',
'CACHE_DIR': 'cache-directory'
})
app.layout = html.Div([
dcc.Store(id='session-id', storage_type='session', data=str(uuid.uuid4())),
# ... rest of layout
])
# Expensive computation with memoization
@cache.memoize()
def expensive_computation(session_id, filter_value):
"""Cache expensive operations by session ID and parameters."""
# Simulate expensive operation
time.sleep(5)
filtered_df = df[df['category'] == filter_value]
return filtered_df.to_dict('records')
@callback(
Output('output', 'children'),
Input('dropdown', 'value'),
State('session-id', 'data')
)
def update_output(filter_value, session_id):
# Retrieve from cache (fast) or compute (slow)
data = expensive_computation(session_id, filter_value)
return f"Processed {len(data)} records"Strategy 3: Signaling Pattern
Use dcc.Store as a signal to coordinate callbacks, avoiding redundant computation.
app.layout = html.Div([
dcc.Store(id='signal', data=0),
dcc.Store(id='session-id', storage_type='session', data=str(uuid.uuid4())),
dcc.Dropdown(['A', 'B', 'C'], id='filter'),
html.Div(id='output-1'),
html.Div(id='output-2'),
])
# Single callback performs expensive computation
@callback(
Output('signal', 'data'),
Input('filter', 'value'),
State('session-id', 'data')
)
def compute_expensive_data(filter_value, session_id):
# Expensive computation happens once
filtered_data = expensive_computation(session_id, filter_value)
cache.set(f'{session_id}-data', filtered_data)
return filter_value # Signal that computation is done
# Multiple callbacks retrieve cached results
@callback(
Output('output-1', 'children'),
Input('signal', 'data'),
State('session-id', 'data')
)
def update_output_1(signal, session_id):
data = cache.get(f'{session_id}-data')
return f"Output 1: {len(data)} records"
@callback(
Output('output-2', 'children'),
Input('signal', 'data'),
State('session-id', 'data')
)
def update_output_2(signal, session_id):
data = cache.get(f'{session_id}-data')
return f"Output 2: Processing complete"---
Performance Optimization
1. Memoization for Expensive Functions
from functools import lru_cache
@lru_cache(maxsize=128)
def expensive_data_processing(filter_value, category):
"""Cache function results in memory."""
# Expensive operation
result = df[df['cat'] == category].groupby('x').sum()
return result.to_dict()
@callback(Output('graph', 'figure'), Input('dropdown', 'value'))
def update_graph(value):
data = expensive_data_processing(value, 'A')
return px.bar(data)2. Efficient Serialization with orjson
For large data transfers, use orjson for faster JSON serialization:
import orjson
import pandas as pd
@callback(Output('store', 'data'), Input('btn', 'n_clicks'))
def store_large_dataset(n_clicks):
large_df = pd.read_csv('large_file.csv')
# Use orjson for fast serialization
return orjson.loads(
orjson.dumps(large_df.to_dict('records'))
)3. Data Aggregation Upfront
Minimize network traffic by pre-aggregating data:
@callback(
Output('aggregated-store', 'data'),
Input('filter', 'value')
)
def aggregate_data(filter_value):
# ❌ Storing full dataset
# return df.to_dict('records') # Large payload
# ✅ Store aggregated data
aggregated = df.groupby('category').agg({
'value': ['sum', 'mean', 'count']
}).to_dict()
return aggregated # Much smaller payload4. WebGL for Large Scatter Plots
Use WebGL rendering for better performance with large datasets:
import plotly.graph_objects as go
@callback(Output('scatter', 'figure'), Input('data-store', 'data'))
def update_scatter(data):
df = pd.DataFrame(data)
# Use Scattergl instead of Scatter for 100k+ points
fig = go.Figure(
data=go.Scattergl(
x=df['x'],
y=df['y'],
mode='markers',
marker=dict(size=5)
)
)
return fig5. Parallel Worker Processes
Configure multiple workers for concurrent user support:
# Gunicorn with 4 worker processes
gunicorn app:server --workers 4 --threads 2 --bind 0.0.0.0:80006. Clientside Callbacks for Frequent Updates
Move high-frequency callbacks to the browser:
from dash import clientside_callback, Input, Output
# JavaScript runs in browser - no server round-trip
clientside_callback(
"""
function(n_intervals) {
return new Date().toLocaleTimeString();
}
""",
Output('clock', 'children'),
Input('interval', 'n_intervals')
)---
Callback Gotchas
1. Component Presence Requirement
Gotcha: All components referenced in callbacks must exist in the layout when the app starts.
# ❌ Component not in layout - callback will fail
@callback(Output('missing-component', 'children'), Input('btn', 'n_clicks'))
def update(n):
return "text"
# ✅ For dynamic layouts, suppress validation
app.config.suppress_callback_exceptions = True2. All Input/State Components Must Be Rendered
Gotcha: With validation suppressed, callbacks silently fail if components aren't rendered.
app.config.suppress_callback_exceptions = True
@callback(
Output('output', 'children'),
Input('conditional-input', 'value') # May not exist
)
def update(value):
# This callback won't fire if 'conditional-input' isn't rendered
return f"Value: {value}"
# ✅ Solution: Use allow_optional (Dash 3.1+)
@callback(
Output('output', 'children'),
Input('conditional-input', 'value', allow_optional=True)
)
def update(value):
if value is None:
return "Component not present"
return f"Value: {value}"3. Callbacks Must Be Defined Before Server Starts
Gotcha: All callbacks must be defined before app.run().
# ❌ Cannot create callbacks dynamically after server starts
if __name__ == '__main__':
app.run(debug=True)
# This callback definition will be ignored
@callback(Output('x', 'children'), Input('y', 'n_clicks'))
def late_callback(n):
return n
# ✅ Define all callbacks before app.run()
@callback(Output('x', 'children'), Input('y', 'n_clicks'))
def callback_func(n):
return n
if __name__ == '__main__':
app.run(debug=True)4. Circular Callbacks
Gotcha: Callbacks can create infinite loops if outputs feed back as inputs.
# ❌ Circular dependency can cause infinite loop
@callback(Output('input-1', 'value'), Input('input-2', 'value'))
def update_1(val):
return val + 1
@callback(Output('input-2', 'value'), Input('input-1', 'value'))
def update_2(val):
return val + 1
# ✅ Use prevent_initial_call and conditional logic
@callback(
Output('input-1', 'value'),
Input('input-2', 'value'),
prevent_initial_call=True
)
def update_1(val):
if val > 100: # Break the loop
raise PreventUpdate
return val + 1---
Error Handling Best Practices
1. Using PreventUpdate
Prevent callback execution when conditions aren't met:
from dash.exceptions import PreventUpdate
@callback(
Output('output', 'children'),
Input('btn', 'n_clicks'),
State('input', 'value')
)
def update_output(n_clicks, value):
# Don't update on initial load
if n_clicks is None:
raise PreventUpdate
# Don't update if input is empty
if not value:
raise PreventUpdate
return f"You entered: {value}"2. Selective Updates with no_update
Update some outputs while leaving others unchanged:
from dash import no_update
@callback(
Output('output-1', 'children'),
Output('output-2', 'children'),
Output('error-msg', 'children'),
Input('input', 'value')
)
def multi_output(value):
try:
result = int(value)
return f"Result: {result}", f"Squared: {result**2}", ""
except ValueError:
# Update only error message, leave other outputs unchanged
return no_update, no_update, "Please enter a valid number"3. Try-Except with User Feedback
@callback(
Output('result', 'children'),
Output('error-notification', 'children'),
Input('submit-btn', 'n_clicks'),
State('data-input', 'value')
)
def process_data(n_clicks, data):
if not n_clicks:
raise PreventUpdate
try:
# Attempt data processing
result = expensive_computation(data)
return f"Success: {result}", ""
except FileNotFoundError as e:
return no_update, dmc.Notification(
title="File Not Found",
message=str(e),
color="red",
action="show"
)
except Exception as e:
return no_update, dmc.Notification(
title="Processing Error",
message=f"An error occurred: {str(e)}",
color="red",
action="show"
)4. Validation Before Processing
@callback(
Output('output', 'children'),
Input('submit-btn', 'n_clicks'),
State('email-input', 'value'),
State('age-input', 'value')
)
def submit_form(n_clicks, email, age):
if not n_clicks:
raise PreventUpdate
errors = []
# Validate email
if not email or '@' not in email:
errors.append("Valid email required")
# Validate age
try:
age_int = int(age)
if age_int < 0 or age_int > 120:
errors.append("Age must be between 0 and 120")
except (ValueError, TypeError):
errors.append("Age must be a number")
if errors:
return dmc.Alert(
"\n".join(errors),
title="Validation Errors",
color="red"
)
# Process valid data
return dmc.Alert(
f"Form submitted successfully for {email}",
title="Success",
color="green"
)5. Logging for Debugging
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
@callback(
Output('output', 'children'),
Input('input', 'value')
)
def process_input(value):
logger.info(f"Callback triggered with value: {value}")
try:
result = complex_operation(value)
logger.info(f"Operation successful: {result}")
return result
except Exception as e:
logger.error(f"Operation failed: {str(e)}", exc_info=True)
return "An error occurred. Please try again."---
References
Based on official Dash documentation: