Boostrap Color Modes
Now it’s easier than ever to switch between light and dark modes in your dash app.
Bootstrap now supports color modes, starting with dark mode. With dash-bootstrap-components
v1.5.0 you can set the color mode globally or on specific components and elements, thanks to the data-bs-theme
attribute.
For more information, see the Bootstrap docs: Bootstrap Color Modes.
Here’s a preview - this is the “minty” theme in either light or dark mode. See the code at the end of this post.
Setting Color Modes
Setting Dark mode globally
If you are not using a component to toggle the color mode, you can set the dark mode globally (the default is light).
Simply add the following to a .js
file in the /assets
folder:
document.documentElement.setAttribute('data-bs-theme', 'dark')
This allows you to have even more options for dark themes. It will set a dark mode for any theme – for example, you can have a dark mode for any of the 26 themes available in the dash-bootstrap-components
library.
Color mode switch
You can change the global theme using a clientside callback.
For example, here is the theme switch component shown in the image above:
color_mode_switch = html.Span(
[
dbc.Label(className="fa fa-moon", html_for="switch"),
dbc.Switch( id="switch", value=True, className="d-inline-block ms-1", persistence=True),
dbc.Label(className="fa fa-sun", html_for="switch"),
]
)
And here’s the callback to change the theme:
clientside_callback(
"""
(switchOn) => {
document.documentElement.setAttribute('data-bs-theme', switchOn ? 'light' : 'dark');
return window.dash_clientside.no_update
}
""",
Output("switch", "id"),
Input("switch", "value"),
)
Setting dark or light mode on components
After setting a global theme, it’s possible to use a different color mode on individual components by setting the data-bs-theme
attribute on a html.Div
container.
Here is a dbc.Card
in dark mode:
html.Div(
[
dbc.Card( # card content here)
]
id="card",
**{"data-bs-theme": "dark"}
)
Here is a card with a light mode
html.div(
[
dbc.Card( # card content here)
]
id="card",
**{"data-bs-theme": "light"}
)
You can even change the color mode in a callback:
app.callback(
Output("card", "data-bs-theme").
...
Applying Bootstrap themes to other component libraries
Note that the Bootstrap theme is automatically applied only to dbc
components. If you are using other libraries like dash-core-componets
, DataTable
, dash-ag-grid
you need to style them yourself. To save some time, you can use the stylesheet from the dash-bootstrap-templates
library. For more information, see:
Applying Bootstrap themes to figures
When you change the color mode, the figures do not update automatically. One option is to update the figure template in a callback. The built-in “plotly_white” and “plotly_dark” work well. If you would like your figures to match your bootstrap theme, you can use one of the 52 Bootstrap themed figure templates from the dash-bootstrap-templates library.
The latest release (V1.1.0) has each of the 26 themes in both light and dark mode. For example minty
and minty_dark
For more information, see the Bootstrap Themed Figure Templates
(The update for the dark mode templates comming soon)
Color Mode Switch Example
Here is the app shown in the image above to toggle between light and dark mode for any Bootstrap theme.
"""
Example of light and dark color modes available in Bootstrap >= 5.3
"""
from dash import Dash, html, dcc, Input, Output, Patch, clientside_callback, callback
import plotly.express as px
import plotly.io as pio
import dash_bootstrap_components as dbc
from dash_bootstrap_templates import load_figure_template
# adds templates to plotly.io
load_figure_template(["minty", "minty_dark"])
df = px.data.gapminder()
app = Dash(__name__, external_stylesheets=[dbc.themes.MINTY, dbc.icons.FONT_AWESOME])
color_mode_switch = html.Span(
[
dbc.Label(className="fa fa-moon", html_for="switch"),
dbc.Switch( id="switch", value=False, className="d-inline-block ms-1", persistence=True),
dbc.Label(className="fa fa-sun", html_for="switch"),
]
)
fig = px.scatter(
df.query("year==2007"),
x="gdpPercap",
y="lifeExp",
size="pop",
color="continent",
log_x=True,
size_max=60,
template="minty",
)
app.layout = dbc.Container(
[
html.Div(["Bootstrap Light Dark Color Modes Demo"], className="bg-primary text-white h3 p-2"),
color_mode_switch,
dcc.Graph(id="graph", figure= fig, className="border"),
]
)
@callback(
Output("graph", "figure"),
Input("switch", "value"),
)
def update_figure_template(switch_on):
# When using Patch() to update the figure template, you must use the figure template dict
# from plotly.io and not just the template name
template = pio.templates["minty"] if switch_on else pio.templates["minty_dark"]
patched_figure = Patch()
patched_figure["layout"]["template"] = template
return patched_figure
clientside_callback(
"""
(switchOn) => {
document.documentElement.setAttribute('data-bs-theme', switchOn ? 'light' : 'dark');
return window.dash_clientside.no_update
}
""",
Output("switch", "id"),
Input("switch", "value"),
)
if __name__ == "__main__":
app.run_server(debug=True)
Color Mode Theme Explorer - See All 52 themes!
Here is a minimal app where you can see each of the 26 themes in both light and dark mode. It also applies Bootstrap themes to dash-core-components and dash-ag-grid. (The color mode switch will be coming soon to the Dash Bootstrap Theme Explorer site.
from dash import Dash, dcc, html, Input, Output, callback, Patch, clientside_callback
import plotly.express as px
import plotly.io as pio
import dash_bootstrap_components as dbc
from dash_bootstrap_templates import ThemeChangerAIO, template_from_url
import dash_ag_grid as dag
df = px.data.gapminder()
years = df.year.unique()
continents = df.continent.unique()
# stylesheet with the .dbc class to style dcc, DataTable and AG Grid components with a Bootstrap theme
dbc_css = "https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates/dbc.min.css"
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.FONT_AWESOME, dbc_css])
color_mode_switch = html.Span(
[
dbc.Label(className="fa fa-moon", html_for="switch"),
dbc.Switch( id="switch", value=True, className="d-inline-block ms-1", persistence=True),
dbc.Label(className="fa fa-sun", html_for="switch"),
]
)
# The ThemeChangerAIO loads all 52 Bootstrap themed figure templates to plotly.io
theme_controls = html.Div(
[ThemeChangerAIO(aio_id="theme"), color_mode_switch],
className="hstack gap-3 mt-2"
)
header = html.H4(
"Theme Explorer Sample App", className="bg-primary text-white p-2 mb-2 text-center"
)
grid = dag.AgGrid(
id="grid",
columnDefs=[{"field": i} for i in df.columns],
rowData=df.to_dict("records"),
defaultColDef={"flex": 1, "minWidth": 120, "sortable": True, "resizable": True, "filter": True},
dashGridOptions={"rowSelection":"multiple"},
)
dropdown = html.Div(
[
dbc.Label("Select indicator (y-axis)"),
dcc.Dropdown(
["gdpPercap", "lifeExp", "pop"],
"pop",
id="indicator",
clearable=False,
),
],
className="mb-4",
)
checklist = html.Div(
[
dbc.Label("Select Continents"),
dbc.Checklist(
id="continents",
options=continents,
value=continents,
inline=True,
),
],
className="mb-4",
)
slider = html.Div(
[
dbc.Label("Select Years"),
dcc.RangeSlider(
years[0],
years[-1],
5,
id="years",
marks=None,
tooltip={"placement": "bottom", "always_visible": True},
value=[years[2], years[-2]],
className="p-0",
),
],
className="mb-4",
)
theme_colors = [
"primary",
"secondary",
"success",
"warning",
"danger",
"info",
"light",
"dark",
"link",
]
colors = html.Div(
[dbc.Button(f"{color}", color=f"{color}", size="sm") for color in theme_colors]
)
colors = html.Div(["Theme Colors:", colors], className="mt-2")
controls = dbc.Card(
[dropdown, checklist, slider],
body=True,
)
tab1 = dbc.Tab([dcc.Graph(id="line-chart", figure=px.line(template="bootstrap"))], label="Line Chart")
tab2 = dbc.Tab([dcc.Graph(id="scatter-chart", figure=px.scatter(template="bootstrap"))], label="Scatter Chart")
tab3 = dbc.Tab([grid], label="Grid", className="p-4")
tabs = dbc.Card(dbc.Tabs([tab1, tab2, tab3]))
app.layout = dbc.Container(
[
header,
dbc.Row([
dbc.Col([controls, theme_controls], width=4),
dbc.Col([tabs, colors], width=8),
]),
],
fluid=True,
className="dbc dbc-ag-grid",
)
@callback(
Output("line-chart", "figure" ),
Output("scatter-chart", "figure"),
Output("grid", "rowData"),
Input("indicator", "value"),
Input("continents", "value"),
Input("years", "value"),
Input(ThemeChangerAIO.ids.radio("theme"), "value"),
Input("switch", "value"),
)
def update(indicator, continent, yrs, theme, color_mode_switch_on):
if continent == [] or indicator is None:
return {}, {}, []
theme_name = template_from_url(theme)
template_name = theme_name if color_mode_switch_on else theme_name + "_dark"
dff = df[df.year.between(yrs[0], yrs[1])]
dff = dff[dff.continent.isin(continent)]
fig = px.line(
dff,
x="year",
y=indicator,
color="continent",
line_group="country",
template=template_name
)
fig_scatter = px.scatter(
dff[dff.year == yrs[0]],
x="gdpPercap",
y="lifeExp",
size="pop",
color="continent",
log_x=True,
size_max=60,
template=template_name,
title="Gapminder %s: %s theme" % (yrs[1], template_name),
)
return fig, fig_scatter, dff.to_dict("records")
# updates the Bootstrap global light/dark color mode
clientside_callback(
"""
switchOn => {
document.documentElement.setAttribute('data-bs-theme', switchOn ? 'light' : 'dark');
return window.dash_clientside.no_update
}
""",
Output("switch", "id"),
Input("switch", "value"),
)
# This callback isn't necessary, but it makes updating figures with the new theme much faster
@callback(
Output("line-chart", "figure", allow_duplicate=True ),
Output("scatter-chart", "figure", allow_duplicate=True),
Input(ThemeChangerAIO.ids.radio("theme"), "value"),
Input("switch", "value"),
prevent_initial_call=True
)
def update_template(theme, color_mode_switch_on):
theme_name = template_from_url(theme)
template_name = theme_name if color_mode_switch_on else theme_name + "_dark"
patched_figure = Patch()
# When using Patch() to update the figure template, you must use the figure template dict
# from plotly.io and not just the template name
patched_figure["layout"]["template"] = pio.templates[template_name]
return patched_figure, patched_figure
if __name__ == "__main__":
app.run_server(debug=True)