Assign a stylesheet to each page in a Dash multi page app

Viewed 447

I am merging 3 Dash apps that used to run independently into a multipage Dash app. One of the apps was built using Dash Bootstrap Components and uses the stylesheet dbc.themes.BOOTSTRAP and two others don’t use bootstrap but a custom css stylesheet.

I understand I can load the two stylesheets and the last one takes precedence when there is a clash by calling:

app = dash.Dash(name, external_stylesheets=[dbc.themes.BOOTSTRAP, 'assets/custom_styles.css'])

The two stylesheets arent’s very compatible and generate some graphical bugs that are in practice are painful to correct (element not aligned correctly for instance). I would like to minimize refactoring of the code and happy to keep two stylesheets.

Is it possible to assign a stylesheet to an app in the architecture which would greatly simplify my problem? The architecture is:

app.py

index.py
apps
|-- init.py
|-- app1.py
|-- app2.py
|-- app3.py

app1 uses bootstrap whilst app2 and app3 use a custom stylesheet. How could I say app1 uses one stylesheet and app2 another if possible at all?

1 Answers

If found a kind of hacky workaround with Javascript and clientside callbacks.

Let's say for example that the custom_styles.css clashes with the bootstrap styles here:

.btn-primary {
  background-color: red;
}

dbc.Button also has a btn-primary class that sets a background-color, so the styles above would overwrite the background-color from Bootstrap.

What you could do is before you return the layout you dynamically set stylesheets in the document based on the current url pathname.

That could look something like this:

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

app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.layout = html.Div(
    [
        dcc.Location(id="url", refresh=False),
        html.Div(id="page-content"),
        dcc.Store(id="pathname"),
    ]
)

app1_layout = dbc.Button("Button", color="primary")
app2_layout = html.Button("Button", className="primary")

app.clientside_callback(
    """
    function(pathname) {
      document.querySelectorAll('link[rel="stylesheet"]').forEach(stylesheet => {
        stylesheet.remove()
      })
      const link = document.createElement('link')
      link.setAttribute("rel", "stylesheet")
      if (pathname == "/page-2") {
        link.setAttribute("href", "/assets/custom_styles.css")
      } else {
        link.setAttribute("href", "https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css")
      }
      document.head.append(link)

      return pathname
    }
    """,
    Output("pathname", "data"),
    Input("url", "pathname"),
)


@app.callback(
    Output("page-content", "children"),
    Input("pathname", "data"),
)
def display_page(pathname):
    if pathname == "/page-2":
        return app2_layout

    return app1_layout


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

The clientside callback is executed when the pathname changes and outputs to a Store component's data property. A regular callback that returns the layout has this property as an input, so the callback will be fired when it changes.

Related