Dash Crush, part 1: the layout

Post Contents

Welcome to Dash Crush - your walk-through tutorial to advanced interactive Data Viz dashboards using Dash Plotly.
In the previous chapter, we created our first Dash app. Now, we will learn some practices to make pretty dashboards.


In this chapter:

  1. Simple yet effective recipe for a convenient layout
  2. Using Themes
  3. Using CSS styles

A beautiful out-of-the box dashboard

Luckily, Dash provides tools that make a dashboard beautiful almost effortlessly. The first is the package dash-bootstrap-components (dbc) (https://dash-bootstrap-components.opensource.faculty.ai/) that wraps Bootstrap styles for Dash. DBC provided such components as Navbar, Accordion, or Collapse that make layout designs.
Another nice feature of DBC are themes that enable a quick change of app look. Themes can be explored, viewed here:

The second component library is https://www.dash-mantine-components.com/ (dmc) (https://www.dash-mantine-components.com/) - it wraps React Mantine Library.

It's code time

Let's combine DBC components, themes, and a bit of CSS to implement a layout that splits the screen into a sidebar/work pane/result pane (with navbar and plot pane) to tackle different ways of styling the Dash app.

Install packages

Create and activate your virtual environment and install the following packages:

pip install dash pandas dash-bootstrap-components

Use dash_bootstrap_components' themes

To make the app prettier, we will use the dash_bootstrap_components' MINTY theme (https://bootswatch.com/minty/). To do so, we need to pass it as an external style sheet to the Dash constructor in dashboard.py:

# dashboard.py

import dash
import dash_bootstrap_components as dbc
from src.layout import layout


APP = dash.Dash(__name__, external_stylesheets=EXTERNAL_STYLESHEETS)
APP.layout = layout.create_layout

# Run the Dash app
if __name__ == "__main__":

Now, we will create the layout.

The layout

Our layout will be located in the src/layout folder and split into several files for easier management.

The top-level layout

The definition of a top-level layout is located in the file src/layout/layout.py. We use DBC's components Container, Row, and Col to split the screen into 3 columns of different widths. In DBC, the full width of a screen is 12 columns. Setting the parameter width=X of dbc.Col makes it fill X of 12 columns.

The complete code:

# src/layout/layout.py

import dash_bootstrap_components as dbc
from src.layout.navbar import navbar
from src.layout.sidebar import sidebar_menu
from src.layout.plot_pane import plot_pane
from src.layout.style import COLORS
from src.layout.work_pane import work_pane

def create_layout():
    """Build layout"""
    return dbc.Container(
                        style={"backgroundColor": COLORS["white"]},
                        [work_pane()], width=2, style={"backgroundColor": COLORS["light-gray"]}
                        [navbar(), plot_pane()],

In the snippet above, you can also see two ways of styling the components:

  • style - dict of parameters to modify properties of the component.
  • className - point to CSS style. It can be a predefined DBC style or a custom one in the assets folder.

For better readability, I only defined the top-level structure of the app in src/layout/layout.py. All the components used are defined in separate files and imported here.

The sidebar

The sidebar will occupy a single left-most column of the screen and contain the menu. In our case, it will be a single button: Plot.

# src/layout/sidebar.py

"""Sidebar menu"""

from dash import dcc, html
import dash_bootstrap_components as dbc
from src.layout.grid import spacer

def sidebar_menu():
    """Sidebar menu"""
    return html.Div(

def _buttons():
    return html.Div(
            dbc.Button("Plot", id="plot-button", color="primary"),
        className="d-grid gap-2",

For styling the button, use className="d-grid gap-2". d-grid and gap-2 are built-in DBC classes.

The work pane

A work pane is an area where all the parameter manipulation happens. In our case, the work pane will have a width of 2 columns and be located in src/layout/work_pane.py. It will consist of a label and material selection dropdown.

# src/layout/work_pane.py

from dash import html, dcc
from src.layout.grid import spacer

MATERIALS = ["GaAs", "InAs"]

def work_pane():
    """Work pane"""
    return html.Div(
                options=[{"label": name, "value": name} for name in MATERIALS],

The result area

The right-most part of the screen will be used for the result presentation. The top part will be the navbar (or title bar), and the bottom will be the plot pane.

The navbar

Let's create a simple navbar that contains only the application's title.

# src/layout/navbar.py

import dash_bootstrap_components as dbc
from dash import html
from src.layout.style import NAVBAR_DISPLAY_STYLE

def navbar():

    return html.Div(
                                "Welcome to Band structure visualization dashboard",
                            width={"size": 6, "offset": 3},

For that, use the dbc's Nav component and place a single label in the 6 middle columns of it. To do so, set
width={"size": 6, "offset": 3}

We use styling methods:

  • style - sets text horizontal centering, font size, and margins.
  • className="text-primary" - is a built-in CSS class, that sets text color to the primary color of the BBC Theme.
The plot pane

The last functional area is the plot pane containing dcc.Graph

# src/layout/plot_pane.py

from dash import dcc
from dash import html
import plotly.graph_objs as go

def create_empty_figure():
    """Style empty figure"""
    layout = go.Layout(autosize=True)
    return go.Figure(data=[], layout=layout)

def plot_pane():
    """plot pane"""
    return html.Div(

We style dcc.Graph with a custom CSS dc-graph. create_empty_figure makes an empty graph (before the first plot) look nicer.


All the main components of the layout are now in place. However, some pieces of code are not yet defined - style and grid. They are common and reusable for different code blocks.


To easily add spacing between components, we can use the wrapper spacer, located in src/layout/grid.py

# src/layout/grid.py

import dash_bootstrap_components as dbc

def spacer(height):
    """Add empty row"""
    return dbc.Row(style={"height": f"{height}px"})
The style

Style definitions are located in src/layout/style.py

# src/layout/style.py

    "white": "white",
    "light-gray": "#f4f4f4",
    "dark-gray": "#d6d6d6",

    "display": "inline-block",
    "width": "90%",
    "textAlign": "center",
    "fontSize": "24px",
    "paddingTop": "6px",
    "height": "50px",

Now, we have all the components in place. We have not defined any CSS styles yet, but Dash does not throw an error on missing CSS. Therefore, we can check how our app would look like without styling.
Layout without CSS

As you can see, columns do not use the whole screen height. To overcome this flaw, we need to use some CSS.

Custom CSS

The custom CSS styles need to be located in the assets folder for Dash to automatically pick them.
So, let's add the CSS we have used in the code above.

For the sidebar, we use:

  • dc-graph class to set no side padding. It is defined in assets/dc-graph.css
/* assets/dc-graph.css */

.dc-graph {
    height: calc(100vh - 50px); /* full-visible-height - navbar  */

In this CSS, we set the plot pane's height to be full height minus the height of the navbar (which was defined as 50 pixels in NAVBAR_DISPLAY_STYLE). In that way, the dashboard utilizes all of the space available.

Nota bene: this example shows one of the difficulties of using custom CSS. There is no easy way to share variables between CSS and Python code (well, at least I have not encountered one yet). Consequently, we need to define the navbar's height in two places and if one changes - also modify the other.


To make our dashboard interactive, we need to add a callback, that will plot the graph when the "Plot" button is clicked. We will define it in src/callbacks.py

# src/callbacks.py

from dash import Input, Output, State
from dash.exceptions import PreventUpdate
import pandas as pd
import plotly.graph_objs as go

MATERIAL_DATA = {"GaAs": pd.read_csv("GaAs.dat"), "InAs": pd.read_csv("InAs.dat")}

TRACES = ["E_so", "E_lh", "E_hh", "E_c"]

def create_figure(to_draw):
    """Style graph"""
    layout = go.Layout(
        title="Band structure",
        xaxis={"title": "k (wave vector) [1/nm]"},
        yaxis={"title": "E (Energy) [eV]"},
    return go.Figure(data=to_draw, layout=layout)

def create_callbacks(app):
    """Create callbacks"""

        Output("band_structure_graph", "figure"),
        Input("plot_button", "n_clicks"),
        State("material_dropdown", "value"),
    def plot(n_clicks, material):  # pylint: disable=unused-argument
        if not n_clicks or not material:
            raise PreventUpdate

        data = MATERIAL_DATA[material]

        to_draw = []
        for trace in TRACES:
            to_draw.append(go.Scatter(x=data["k"], y=data[trace], mode="lines", name=trace))

        return create_figure(to_draw)

This callback draws the Band Structure of a selected material (Gallium Arsenide or Indium Arsenide) every time the "Plot" button is clicked. The chosen material is passed to the callback as State. On click, the Scatter trace for every energy profile is added to the plot data. Later, the figure's layout is defined, and the figure is returned.

If you need a more detailed explanation of this callback, please refer to the previous part of Dash Crush, as the only difference in the code is the Input trigger being the "Plot" button click instead of a dropdown selection change.

(REMARK: In the older version of Dash, it was required to pass a list of Outputs, a list of Inputs, and a list of States. For some releases it is no longer required, but still supported.)

The final step is importing create_callbacks in dashboard.py:
from src.callbacks import create_callbacks
and calling it there:

Starting the app:

Start the app with python dashboard.py. You should see an indication of a running app in your console. The dashboard is available under http://localhost:8050.


Quick recap:

  1. Use dbc.Themes for a beautiful, out-of-the-box appearance.
  2. Splitting the screen into a sidebar/work pane/result pane yields good UX.
  3. Use custom CSS to enhance your app's look.
  4. Place custom CSS in assets for automatic pick-up.

The complete code from this chapter can be found on GitHub:

There is also a file requirements.txt which lists all the packages used. Just install it to your virtual env with pip install -r requirements.txt.

We will explore more of Dash magic in the next parts of Dash Crush.

Share this Post:

Related posts