✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🐇 Announcing Dash VTK for 3d simulation graphics. Check out the March webinar.

Structuring a multi-tab app

I’ve been following the documentation for dcc.Tabs but I wanted to structure my app across multiple files rather than a single (bloated) file. Following the example listed in the URLs and Multi-Page App Tutorial, I was able to build a simple but successful app where the layouts for each tab are saved in separate files. Note that I had to place all my callbacks in the primary app (main.py), and not in the secondary files.

file structure:

main.py
tabs folder
— tab_1.py
— tab_2.py

main.py

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

app = dash.Dash()

app.config['suppress_callback_exceptions'] = True

app.layout = html.Div([
    html.H1('Dash Tabs component demo'),
    dcc.Tabs(id="tabs-example", value='tab-1-example', children=[
        dcc.Tab(label='Tab One', value='tab-1-example'),
        dcc.Tab(label='Tab Two', value='tab-2-example'),
    ]),
    html.Div(id='tabs-content-example')
])

@app.callback(Output('tabs-content-example', 'children'),
              [Input('tabs-example', 'value')])
def render_content(tab):
    if tab == 'tab-1-example':
        return tab_1.tab_1_layout
    elif tab == 'tab-2-example':
        return tab_2.tab_2_layout

# Tab 1 callback
@app.callback(dash.dependencies.Output('page-1-content', 'children'),
              [dash.dependencies.Input('page-1-dropdown', 'value')])
def page_1_dropdown(value):
    return 'You have selected "{}"'.format(value)

# Tab 2 callback
@app.callback(Output('page-2-content', 'children'),
              [Input('page-2-radios', 'value')])
def page_2_radios(value):
    return 'You have selected "{}"'.format(value)

app.css.append_css({
    'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'
})

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

tab_1.py

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

tab_1_layout = html.Div([
    html.H1('Page 1'),
    dcc.Dropdown(
        id='page-1-dropdown',
        options=[{'label': i, 'value': i} for i in ['LA', 'NYC', 'MTL']],
        value='LA'
    ),
    html.Div(id='page-1-content')
])

tab_2.py

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

tab_2_layout = html.Div([
    html.H1('Page 2'),
    dcc.RadioItems(
        id='page-2-radios',
        options=[{'label': i, 'value': i} for i in ['Orange', 'Blue', 'Red']],
        value='Orange'
    ),
    html.Div(id='page-2-content')
])
``
12 Likes

I have updated your main.py file to work with dash-core-components==0.13.0rc5


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

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

app = dash.Dash()

app.config.suppress_callback_exceptions = True

app.layout = html.Div([
    html.H1('Dash Tabs component demo'),
        dcc.Tabs(
            tabs=[
                {'label': 'Tab One', 'value': 'tab-1-example'},
                {'label': 'Tab Two', 'value': 'tab-2-example'}
            ],
            value='tab-1-example',
            id='tabs-example'
        ),
    html.Div(id='tabs-content-example')
])

@app.callback(Output('tabs-content-example', 'children'),
              [Input('tabs-example', 'value')])
def render_content(tab):
    if tab == 'tab-1-example':
        return tab_1.tab_1_layout
    elif tab == 'tab-2-example':
        return tab_2.tab_2_layout

# Tab 1 callback
@app.callback(dash.dependencies.Output('page-1-content', 'children'),
              [dash.dependencies.Input('page-1-dropdown', 'value')])
def page_1_dropdown(value):
    return 'You have selected "{}"'.format(value)

# Tab 2 callback
@app.callback(Output('page-2-content', 'children'),
              [Input('page-2-radios', 'value')])
def page_2_radios(value):
    return 'You have selected "{}"'.format(value)

app.css.append_css({
    'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'
})

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


3 Likes

Thank you for sharing this example! Very helpful.

As far as I understand, in order to define the callbacks outside of the main app (either in the tab file or a separate file just for callbacks), you need to have a separate app.py file that you import from your main app file so instead of app = dash.Dash() you have from app import app

It is explained in the bottom of the page URLs and Multi-Page App Tutorial that you quoted, but maybe was not when you created this example. (So I thought I just quote it here for reference)

It is worth noting that in both of these project structures, the Dash instance is defined in a separate app.py , while the entry point for running the app is index.py . This separation is required to avoid circular imports: the files containing the callback definitions require access to the Dash app instance however if this were imported from index.py , the initial loading of index.py would ultimately require itself to be already imported, which cannot be satisfied.

And the app.py file is simply just something like this

import dash

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

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
server = app.server
app.config.suppress_callback_exceptions = True
3 Likes

Thanks, @Kaofu. I had not seen this before. If the entry point for running the app is now index.py, how does that affect the naming conventions used in deployment? For example, in the user guide for deploying on Heroku, it specifies that the app must be named app.py and then called as such in Procfile:

web: gunicorn app:server
(Note that app refers to the filename app.py. server refers to the variable server inside that file).

Thanks!

I was wondering in case of a multi-tab app like this one, how is layout managed for a login page ? On the login page, we don’t want Tabs appearing. However, Tabs are part of the app.layout. So as the code as is, will show Tabs on the login page. How can Tabs be removed from the login page ?

One hack I can think of is to hide the Tabs on login page (‘display’:‘none’). However, that will still leave the space occupied by Tabs as empty space. Any better solution ?

May be make Tabs part of Page 1 & Page 2 but not part of the app.layout ?

Hi @austin, I am a newbie to python and dash plotly. I tried using your scripts tp create a simple mutli tab dashboard, but i am getting an error

ImportError: cannot import name ‘tab_1’

I have followed your folder structure. Please do you know that the problem might be?