Updating external_stylesheets via callback?

So usually you would do something like:

external_stylesheets = [‘https://codepen.io/chriddyp/pen/bWLwgP.css’]

app = dash.Dash(name, external_stylesheets=external_stylesheets)

Suppose I’d like to give users the option to pick a theme that updates the external_stylesheets, for example between dash bootstrap components dbc.themes.LUX and dbc.themes.YETI (but not both). So I’d like to be able to replace the external_stylesheets using radio item callbacks, is this possible?

2 Likes

Has anyone found a solution for this? I also want to toggle between dbc themes in my dash app.

Take a look at cool theme explorer by @AnnMarieW,

1 Like

Hey @atharvakatre

Here is a minimal example that shows the clientside callback used to switch the external stylesheets.
And thanks to @Emil who helped improve it - He had the great idea of using two stylsheets with a delay to help reduce the flicker when the stylesheets change.

"""
This is a minimal example of the theme switcher clientside callback.
See the full app at https://hellodash.pythonanywhere.com/

"""


import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output

import dash_bootstrap_components as dbc

app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.themes.BOOTSTRAP])


dbc_themes_url = {
    "BOOTSTRAP": dbc.themes.BOOTSTRAP,
    "CERULEAN": dbc.themes.CERULEAN,
    "COSMO": dbc.themes.COSMO,
    "FLATLY": dbc.themes.FLATLY,
    "JOURNAL": dbc.themes.JOURNAL,
    "LITERA": dbc.themes.LITERA,
    "LUMEN": dbc.themes.LUMEN,
    "LUX": dbc.themes.LUX,
    "MATERIA": dbc.themes.MATERIA,
    "MINTY": dbc.themes.MINTY,
    "PULSE": dbc.themes.PULSE,
    "SANDSTONE": dbc.themes.SANDSTONE,
    "SIMPLEX": dbc.themes.SIMPLEX,
    "SKETCHY": dbc.themes.SKETCHY,
    "SPACELAB": dbc.themes.SPACELAB,
    "UNITED": dbc.themes.UNITED,
    "YETI": dbc.themes.YETI,
    "CYBORG": dbc.themes.CYBORG,
    "DARKLY": dbc.themes.DARKLY,
    "SLATE": dbc.themes.SLATE,
    "SOLAR": dbc.themes.SOLAR,
    "SUPERHERO": dbc.themes.SUPERHERO,
}

dropdown = dcc.Dropdown(
    id="themes",
    options=[{"label": str(i), "value": dbc_themes_url[i]} for i in dbc_themes_url],
    value=dbc_themes_url["BOOTSTRAP"],
    clearable=False,
)

buttons = html.Div(
    [
        dbc.Button("Primary", color="primary", className="mr-1"),
        dbc.Button("Secondary", color="secondary", className="mr-1"),
        dbc.Button("Success", color="success", className="mr-1"),
        dbc.Button("Warning", color="warning", className="mr-1"),
        dbc.Button("Danger", color="danger", className="mr-1"),
        dbc.Button("Info", color="info", className="mr-1"),
        dbc.Button("Light", color="light", className="mr-1"),
        dbc.Button("Dark", color="dark", className="mr-1"),
        dbc.Button("Link", color="link"),
    ],className='m-4'
)

alerts = html.Div(
    [
        dbc.Alert("This is a primary alert", color="primary"),
        dbc.Alert("This is a secondary alert", color="secondary"),
        dbc.Alert("This is a success alert! Well done!", color="success"),
        dbc.Alert("This is a warning alert... be careful...", color="warning"),
    ]
)

"""
===============================================================================
Layout
"""
app.layout = dbc.Container(
    dbc.Row(
        [
            dbc.Col(["Select Theme", dropdown], width=3),
            dbc.Col([buttons, alerts]),
            html.Div(id="blank_output"),
        ],
    ),
    className="m-4",
    fluid=True,
)


# Using 2 stylesheets with the delay reduces the annoying flicker when the theme changes
app.clientside_callback(
    """
    function(url) {
        // Select the FIRST stylesheet only.
        var stylesheets = document.querySelectorAll('link[rel=stylesheet][href^="https://stackpath"]')
        // Update the url of the main stylesheet.
        stylesheets[stylesheets.length - 1].href = url
        // Delay update of the url of the buffer stylesheet.
        setTimeout(function() {stylesheets[0].href = url;}, 100);
    }
    """,
    Output("blank_output", "children"),
    Input("themes", "value"),
)


if __name__ == "__main__":
    app.run_server(debug=True)
1 Like

@AnnMarieW @Emil Thank you guys!! This is exactly what I was looking for :slightly_smiling_face:

2 Likes

@AnnMarieW sorry to bother you again but is it possible to replace the dropdown for themes with a boolean toggle switch? Like I want to have to a light and a dark theme in my app and instead of having a dropdown or a radio button for both light and dark options I only want to have a single toggle component for the dark theme. But not sure how the values can manipulate the themes as the dbc checklist with a switch returns a True or False value.

Hi @atharvakatre

I’m happy to help :slight_smile:

Actually using a boolean switch is a slightly different process. In the clientside callback you have to use the actual URL for the theme instead of what the dropdown uses - the nice shortcut provided by the dbc library: dbc.themes.MINTY. An easy way to find the URL is with print(dbc.themes.MINTY) (or whatever theme you want to use)

Note that the dbc.Checklist with switch=True is not actually a boolean. It returns either [] or [value], where value is the value as defined in options. In this example, it will return either [] or [1]

options=[{"label": "dark theme", "value": 1}],

If you are using an actual boolean like the dbc.Checkbox, you just need to change the callback slightly. Let me know if you would like to see that example instead.

Here is the example with a single dbc.Checklist and it switches between Minty and Cyborg:


import dash
import dash_html_components as html
from dash.dependencies import Input, Output

import dash_bootstrap_components as dbc

app = dash.Dash(external_stylesheets=[dbc.themes.CYBORG, dbc.themes.MINTY])

# Use this to find the url to use in the clientside callback
print(dbc.themes.CYBORG)
print(dbc.themes.MINTY)


switch = dbc.FormGroup(
    [
        dbc.Label("Select theme"),
        dbc.Checklist(
            options=[{"label": "dark theme", "value": 1},],
            value=[],
            id="switch",
            switch=True,
        ),
    ]
)

buttons = html.Div(
    [
        dbc.Button("Primary", color="primary", className="mr-1"),
        dbc.Button("Secondary", color="secondary", className="mr-1"),
        dbc.Button("Success", color="success", className="mr-1"),
        dbc.Button("Warning", color="warning", className="mr-1"),
        dbc.Button("Danger", color="danger", className="mr-1"),
        dbc.Button("Info", color="info", className="mr-1"),
        dbc.Button("Light", color="light", className="mr-1"),
        dbc.Button("Dark", color="dark", className="mr-1"),
        dbc.Button("Link", color="link"),
    ],
    className="m-4",
)

alerts = html.Div(
    [
        dbc.Alert("This is a primary alert", color="primary"),
        dbc.Alert("This is a secondary alert", color="secondary"),
        dbc.Alert("This is a success alert! Well done!", color="success"),
        dbc.Alert("This is a warning alert... be careful...", color="warning"),
    ]
)

"""
===============================================================================
Layout
"""
app.layout = dbc.Container(
    dbc.Row(
        [
            dbc.Col(switch, width=3),
            dbc.Col([buttons, alerts]),
            html.Div(id="blank_output"),
        ],
    ),
    className="m-4",
    fluid=True,
)


# Using 2 stylesheets with the delay reduces the annoying flicker when the theme changes
app.clientside_callback(
    """
    function(selected) {

        let url = "https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/minty/bootstrap.min.css"
        if (selected.length > 0) {
            url= "https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/cyborg/bootstrap.min.css"
        }

        // Select the theme stylesheets .
        var stylesheets = document.querySelectorAll('link[rel=stylesheet][href^="https://stackpath"]')
        // Update the url of the main stylesheet.
        stylesheets[stylesheets.length - 1].href = url
        // Delay update of the url of the buffer stylesheet.
        setTimeout(function() {stylesheets[0].href = url;}, 100);
    }
    """,
    Output("blank_output", "children"),
    Input("switch", "value"),
)

if __name__ == "__main__":
    app.run_server(debug=True)
3 Likes

@AnnMarieW Thank you, yes I was referring to the dbc.Checlist your example seems perfect for my use case. Looks like it was a pretty easy logic my JS skills are rusty :sweat_smile:

Hi @AnnMarieW , Thnaks for the solutions.
I copied your code and executed it, but the theme is not getting updated ? Could you help me
When ever i toggle the button ,Getting error as : "Cannot set properties of undefined (setting ‘href’)"

Hi @pranithg and welcome to the Dash community :slightly_smiling_face:

The example above worked for Dash v1.21 and Dash Bootstrap Components V0.13

Here is an example using Dash 2.0 and Dash Bootstrap Components V1.0:
Note this also Dash Bootstrap Templates to automatically style figures with your Bootstrap theme. Try it with pip install dash-bootstrap-templates


from dash import Dash, dcc, html, Input, Output
import pandas as pd
import plotly.express as px

import dash_bootstrap_components as dbc
from dash_bootstrap_templates import load_figure_template

# This adds the "bootstrap" and "cyborg" themed templates  to the Plolty figure templates
load_figure_template(["bootstrap", "cyborg"])


app = Dash(
    __name__, external_stylesheets=[dbc.themes.BOOTSTRAP, dbc.icons.FONT_AWESOME]
)


df = pd.DataFrame(
    {
        "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
        "Amount": [4, 1, 2, 2, 4, 5],
        "City": ["SF", "SF", "SF", "Montreal", "Montreal", "Montreal"],
    }
)

toggle = html.Div(
    [
        html.Span(className="fa fa-moon"),
        dbc.Switch(value=True, id="theme", className="d-inline-block ml-2",),
        html.Span(className="fa fa-sun"),
    ],
    className="d-inline-block",
)

buttons = html.Div(
    [
        dbc.Button("Primary", color="primary", className="mr-1"),
        dbc.Button("Secondary", color="secondary", className="mr-1"),
        dbc.Button("Success", color="success", className="mr-1"),
        dbc.Button("Warning", color="warning", className="mr-1"),
        dbc.Button("Danger", color="danger", className="mr-1"),
        dbc.Button("Info", color="info", className="mr-1"),
        dbc.Button("Light", color="light", className="mr-1"),
        dbc.Button("Dark", color="dark", className="mr-1"),
        dbc.Button("Link", color="link"),
    ],
    className="m-4",
)

graph = html.Div(dcc.Graph(id="graph"), className="m-4")

# dummy output needed by the clientside callback
blank = html.Div(id="blank_output")

"""
===============================================================================
Layout
"""
app.layout = dbc.Container(
    dbc.Row([dbc.Col([toggle, buttons, graph, blank])]), className="m-4", fluid=True,
)


@app.callback(
    Output("graph", "figure"), Input("theme", "value"),
)
def update_graph_theme(value):
    template = "bootstrap" if value else "cyborg"
    return px.bar(
        df, x="Fruit", y="Amount", color="City", barmode="group", template=template
    )


# To find the urls for the themes in the clientside callback:
# print(dbc.themes.BOOTSTRAP)
# print(dbc.themes.CYBORG)

app.clientside_callback(
    """
    function(themeToggle) {
        //  To use different themes,  change these links:
        const theme1 = "https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css"
        const theme2 = "https://cdn.jsdelivr.net/npm/bootswatch@5.1.0/dist/cyborg/bootstrap.min.css"
        const stylesheet = document.querySelector('link[rel=stylesheet][href^="https://cdn.jsdelivr"]')        
        var themeLink = themeToggle ? theme1 : theme2;
        stylesheet.href = themeLink
    }
    """,
    Output("blank_output", "children"),
    Input("theme", "value"),
)

if __name__ == "__main__":
    app.run_server(debug=True)

theme_switch_toggle

1 Like

Thank you @AnnMarieW … Its working…like you said,issue is not installing lib dash-bootstrap-template

There are now two theme switch AIO components to make it even easier to switch themes in a Dash app.

See more information here: Dash Bootstrap Templates V1.0.0 New: Theme Switch Components

Hi @AnnMarieW ,

I am trying to load custom dark and light CSS sheets (in a folder named css), but when I execute the following code, nothing happens (i.e. the local sheets are not loaded), do you know why?

function(toggle) {
    const theme1 = "css/dark.css"
    const theme2 = "css/light.css"
    var themeLink = toggle ? theme1 : theme2
    var link = document.createElement('link')
    link.rel = 'stylesheet'
    link.href = themeLink
    document.head.appendChild(link)
}

It does work if I replace the two themes with the ones in your previous example.

Thanks!

Hi @Kefeng

That clientside callback function is only for changing the URL of an external stylesheet. It won’t switch the stylesheets in a local folder.

Note - If you use one of the theme switch components in the dash-bootstsrap-templates library, you won’t need to us e a clientside callback like shown above.

See the latest info here:

Thanks for your answer. It actually does work when I put the stylesheets in assets folder.

Hi @AnnMarieW ,

How can I implement a boolean toggle to switch the theme in a multipage app with the following structure?

  • app.py
  • index.py
  • pages
    |-- init.py
    |-- page1.py
    |-- page2.py

Where the individual pages are called by the index.py landing page.
How can I change the theme for the whole landing page and the pages all together?
I can’t figure out how to use a callback from the index.py landing page to point to the app.py to change the external_stylesheets. Any help or pointers will be much appreciated.

app.py:

import dash
import dash_bootstrap_components as dbc

FA = "https://use.fontawesome.com/releases/v5.8.1/css/all.css"

app = dash.Dash(__name__, suppress_callback_exceptions=True,
                external_stylesheets=[dbc.themes.CERULEAN, FA])

app.title = 'Dashboard'
server = app.server

Thanks

Hi @Xyris and welcome to the Dash community :slight_smile:

You can find more information about changing themes here:

For a multi-page app, just be sure to put the theme switch component in the page where the main app.layout is located.

If you have more question, let me know :four_leaf_clover: