Dash renders web applications as a “single-page app”. This means that the application does not completely reload when the user navigates the application, making browsing very fast.
The components dcc.Location and dcc.Link aid page navigation: dcc.Location
represents the web browser address bar. You can access the current pathname in the user’s browser with dcc.Location
‘s pathname
property. dcc.Link
updates the pathname
in the browser.
In the following examples, we demonstrate using these components to build multi-page apps.
from dash import Dash, dcc, html, callback, Input, Output
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
# represents the browser address bar and doesn't render anything
dcc.Location(id='url', refresh=False),
dcc.Link('Navigate to "/"', href='/'),
html.Br(),
dcc.Link('Navigate to "/page-2"', href='/page-2'),
# content will be rendered in this element
html.Div(id='page-content')
])
@callback(Output('page-content', 'children'),
[Input('url', 'pathname')])
def display_page(pathname):
return html.Div([
html.H3(f'You are on page {pathname}')
])
if __name__ == '__main__':
app.run_server(debug=True)
In this example, the callback display_page
receives the current pathname (the last part of the URL) of the page. The callback simply displays the pathname
on page but it could use the pathname
to display different content.
The Link
element updates the pathname
of the browser without refreshing the page. If you used a html.A
element instead, the pathname
would update but the page would refresh.
Here is what this example running looks like. Note how clicking on the Link
doesn’t refresh the page even though it updates the URL!
You can modify the previous example to display different pages depending on the URL:
from dash import Dash, dcc, html, Input, Output, callback
app = Dash(__name__, suppress_callback_exceptions=True)
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-content')
])
index_page = html.Div([
dcc.Link('Go to Page 1', href='/page-1'),
html.Br(),
dcc.Link('Go to Page 2', href='/page-2'),
])
page_1_layout = html.Div([
html.H1('Page 1'),
dcc.Dropdown(['LA', 'NYC', 'MTL'], 'LA', id='page-1-dropdown'),
html.Div(id='page-1-content'),
html.Br(),
dcc.Link('Go to Page 2', href='/page-2'),
html.Br(),
dcc.Link('Go back to home', href='/'),
])
@callback(Output('page-1-content', 'children'),
[Input('page-1-dropdown', 'value')])
def page_1_dropdown(value):
return f'You have selected {value}'
page_2_layout = html.Div([
html.H1('Page 2'),
dcc.RadioItems(['Orange', 'Blue', 'Red'], 'Orange', id='page-2-radios'),
html.Div(id='page-2-content'),
html.Br(),
dcc.Link('Go to Page 1', href='/page-1'),
html.Br(),
dcc.Link('Go back to home', href='/')
])
@callback(Output('page-2-content', 'children'),
[Input('page-2-radios', 'value')])
def page_2_radios(value):
return f'You have selected {value}'
# Update the index
@callback(Output('page-content', 'children'),
[Input('url', 'pathname')])
def display_page(pathname):
if pathname == '/page-1':
return page_1_layout
elif pathname == '/page-2':
return page_2_layout
else:
return index_page
# You could also return a 404 "URL not found" page here
if __name__ == '__main__':
app.run_server(debug=True)
In this example, we’re displaying different layouts through the display_page
function. A few notes:
- Each page can have interactive elements even though those elements may not be in the initial view. Dash handles these “dynamically generated” components gracefully: as they are rendered, they will trigger the callbacks with their initial values.
- Since we’re adding callbacks to elements that don’t exist in the app.layout, Dash will raise an exception to warn us that we might be doing something wrong. In this case, we’re adding the elements through a callback, so we can ignore the exception by setting suppress_callback_exceptions=True
. It is also possible to do this without suppressing callback exceptions. See the example below for details.
- You can modify this example to import the different page’s layout
s in different files.
- This Dash Userguide that you’re looking at is itself a multi-page Dash app, using these same principles.
Dash applies validation to your callbacks, which performs checks such as validating the types of callback arguments and checking to see whether the
specified Input and Output components actually have the specified properties.
For full validation, all components within your callback must therefore appear
in the initial layout of your app, and you will see an error if they do not. However, in the case of more complex Dash apps that involve dynamic modification
of the layout (such as multi-page apps), not every component appearing in your
callbacks will be included in the initial layout.
New in Dash 1.12 You can set app.validation_layout
to a “complete” layout that contains all the components you’ll use in any of the pages / sections. app.validation_layout
must be a Dash component, not a function. Then set app.layout
to just the index layout. In previous Dash versions there was a trick you could use to achieve the same result, checking flask.has_request_context
inside a layout function - that will still work but is no longer recommended.
from dash import Dash, html, dcc, Input, Output, State, callback
app = Dash(__name__)
url_bar_and_content_div = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-content')
])
layout_index = html.Div([
dcc.Link('Navigate to "/page-1"', href='/page-1'),
html.Br(),
dcc.Link('Navigate to "/page-2"', href='/page-2'),
])
layout_page_1 = html.Div([
html.H2('Page 1'),
dcc.Input(id='input-1-state', type='text', value='Montreal'),
dcc.Input(id='input-2-state', type='text', value='Canada'),
html.Button(id='submit-button', n_clicks=0, children='Submit'),
html.Div(id='output-state'),
html.Br(),
dcc.Link('Navigate to "/"', href='/'),
html.Br(),
dcc.Link('Navigate to "/page-2"', href='/page-2'),
])
layout_page_2 = html.Div([
html.H2('Page 2'),
dcc.Dropdown(['LA', 'NYC', 'MTL'], 'LA', id='page-2-dropdown'),
html.Div(id='page-2-display-value'),
html.Br(),
dcc.Link('Navigate to "/"', href='/'),
html.Br(),
dcc.Link('Navigate to "/page-1"', href='/page-1'),
])
# index layout
app.layout = url_bar_and_content_div
# "complete" layout
app.validation_layout = html.Div([
url_bar_and_content_div,
layout_index,
layout_page_1,
layout_page_2,
])
# Index callbacks
@callback(Output('page-content', 'children'),
Input('url', 'pathname'))
def display_page(pathname):
if pathname == "/page-1":
return layout_page_1
elif pathname == "/page-2":
return layout_page_2
else:
return layout_index
# Page 1 callbacks
@callback(Output('output-state', 'children'),
Input('submit-button', 'n_clicks'),
State('input-1-state', 'value'),
State('input-2-state', 'value'))
def update_output(n_clicks, input1, input2):
return f'The Button has been pressed {n_clicks} times. \
Input 1 is {input1} and Input 2 is {input2}'
# Page 2 callbacks
@callback(Output('page-2-display-value', 'children'),
Input('page-2-dropdown', 'value'))
def display_value(value):
return f'You have selected {value}'
if __name__ == '__main__':
app.run_server(debug=True)
Earlier examples show each multi-page app created within a single Python file. For bigger apps, a structure with multiple files may make the app easier to manage.
One way to structure a multi-page app is to have each page as a separate app imported in the main app (app.py
). In the following example, we build our app with two pages pages/page1.py
and pages/page2.py
. More pages (for example, pages/page3.py
) can easily be added to this structure.
File structure:
- app.py
- pages
|-- __init__.py
|-- page1.py
|-- page2.py
pages/page1.py
from dash import dcc, html, Input, Output, callback
layout = html.Div([
html.H3('Page 1'),
dcc.Dropdown(
{f'Page 1 - {i}': f'{i}' for i in ['New York City', 'Montreal', 'Los Angeles']},
id='page-1-dropdown'
),
html.Div(id='page-1-display-value'),
dcc.Link('Go to Page 2', href='/page2')
])
@callback(
Output('page-1-display-value', 'children'),
Input('page-1-dropdown', 'value'))
def display_value(value):
return f'You have selected {value}'
pages/page2.py
from dash import dcc, html, Input, Output, callback
layout = html.Div([
html.H3('Page 2'),
dcc.Dropdown(
{f'Page 2 - {i}': f'{i}' for i in ['London', 'Berlin', 'Paris']},
id='page-2-dropdown'
),
html.Div(id='page-2-display-value'),
dcc.Link('Go to Page 1', href='/page1')
])
@callback(
Output('page-2-display-value', 'children'),
Input('page-2-dropdown', 'value'))
def display_value(value):
return f'You have selected {value}'
app.py
In app.py
we import page1
and page2
. When you run app.py
it loads the layout from page1.py
if you go to the pathname /page1
and the layout from page2.py
if you go to /page2
.
from dash import Dash, dcc, html, Input, Output, callback
from pages import page1, page2
app = Dash(__name__, suppress_callback_exceptions=True)
server = app.server
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-content')
])
@callback(Output('page-content', 'children'),
Input('url', 'pathname'))
def display_page(pathname):
if pathname == '/page1':
return page1.layout
elif pathname == '/page2':
return page2.layout
else:
return '404'
if __name__ == '__main__':
app.run_server(debug=True)
Another option for a multi-page structure is a flat project layout with callbacks and layouts in separate files:
File structure:
- app.py
- callbacks.py
- layouts.py
app.py
from dash import Dash, dcc, html, Input, Output, callback
from layouts import layout1, layout2
import callbacks
app = Dash(__name__, suppress_callback_exceptions=True)
server = app.server
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-content')
])
@callback(Output('page-content', 'children'),
Input('url', 'pathname'))
def display_page(pathname):
if pathname == '/page1':
return layout1
elif pathname == '/page2':
return layout2
else:
return '404'
if __name__ == '__main__':
app.run_server(debug=True)
callbacks.py
from dash import Input, Output, callback
@callback(
Output('page-1-display-value', 'children'),
Input('page-1-dropdown', 'value'))
def display_value(value):
return f'You have selected {value}'
@callback(
Output('page-2-display-value', 'children'),
Input('page-2-dropdown', 'value'))
def display_value(value):
return f'You have selected {value}'
layouts.py
from dash import dcc, html
layout1 = html.Div([
html.H3('Page 1'),
dcc.Dropdown(
{f'Page 1 - {i}': f'{i}' for i in ['New York City', 'Montreal', 'Los Angeles']},
id='page-1-dropdown'
),
html.Div(id='page-1-display-value'),
dcc.Link('Go to Page 2', href='/page2')
])
layout2 = html.Div([
html.H3('Page 2'),
dcc.Dropdown(
{f'Page 2 - {i}': f'{i}' for i in ['London', 'Berlin', 'Paris']},
id='page-2-dropdown'
),
html.Div(id='page-2-display-value'),
dcc.Link('Go to Page 1', href='/page1')
])
The multi-page app examples here use the decorator
@callback
, which is new with Dash 2.0. It simplifies creating multi-page apps. More changes to make it even easier to create multi-page apps are also on the way in future versions of Dash. Big thanks to the Dash Community for contributing to these features with Dash Labs.
Read more about other ways we’re working on simplifying multi-page apps.