Combining dcc.Tabs and dcc.Location

I want to be able to implement a multi-page app like in URL Routing and Multiple Apps | Dash for Python Documentation | Plotly with tabs. Below is an example of what I am trying to achieve:

Things that works in the code below:

  • When user clicks on a tab it changes the page content and also updates the URL (Input = Tab.value, Output = page-content and URL)

Things I’d also like to do

  • Be able to load a URL on a specific tab value (for example /page-2 instead of the default). If I set a value in dcc.Tabs the page will always load on that specific tab, implying a callback is needed… (Input = URL, Output = Tab.value) There is some commented-out code that shows my attempt.

However this is now a circular reference and was wondering how to go about this. Also open to not using tabs if it makes the implementation simpler.

Edit: Even though I use dcc.Link in the example below I plan on having hyperlinks (i.e on an annotation on a Chart to redirect to a page/tab with more information). It appears using hyperlinks refreshes the page back to the default tab unlike the dcc.Link in this example.

import dash
import dash_core_components as dcc
import dash_html_components as html

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

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

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div([
        html.Div([
            # Want the value of this dcc.Tabs to be whatever dcc.Location.pathname is
            # dcc.Tabs(id="tabs", value="/page-1", children=[
            dcc.Tabs(id="tabs", children=[
                dcc.Tab(label='Page 1', value='/page-1'),
                dcc.Tab(label='Page 2', value='/page-2'),
                dcc.Tab(label='Page 3', value='/page-3'),
            ]),
        ]),
    ]),
    html.Div(id='page-content'),
])

page_1_layout = html.Div([
    html.H1('Page 1'),
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
])

page_2_layout = html.Div([
    html.H1('Page 2'),
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
])

# This causes a circular reference (although it does work to change tab on clicking link)

# @app.callback([dash.dependencies.Output('page-content', 'children'),
#                dash.dependencies.Output('tabs', 'value')],
#               [dash.dependencies.Input('url', 'pathname')],
#               )
# def display_page(pathname):
#     if pathname == '/page-1':
#         return page_1_layout, pathname
#     elif pathname == '/page-2':
#         return page_2_layout, pathname
#     else:
#         return html.Div([html.H1('Error 404 - Page not found')]), pathname


@app.callback(dash.dependencies.Output('page-content', 'children'),
              [dash.dependencies.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 html.Div([html.H1('Error 404 - Page not found')])

@app.callback(dash.dependencies.Output('url', 'pathname'),
              [dash.dependencies.Input('tabs', 'value')])
def tab_updates_url(value):
    return value


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

You dont need to use Tab.
You can use dcc.Link inside Div, like this:

            html.Div([
                html.Div([
                    dcc.Link([
                        "Page1"
                    ], id="Page1", href="/Page1", className="set of classnames")
                ],id="Page1_link", className="set of classnames"),
                html.Div([
                    dcc.Link([
                        "Page2"
                    ], id="Page2", href="/Page2", className="set of classnames")
                ],id="Page2_link", className="set of classnames"),
            ], style={"display":"flex"})

A click on a dcc.Link doesnt refresh the page.

Then, you can use CSS to make your links looking like tabs.

1 Like

Thank you! I greatly appreciate you took the time to answer my problem and even provide some sample code.

If I may ask, this is the first demo I’ve seen hosted on a .com address without port numbers. I’m very new to web development and have been able to host my dash app on waitress but it still has a ‘:8080’, just curious how you are hosting your app.

I poked around and didn’t see any better solutions than @David22’s excellent suggestion of making a Link look like a Tab. A bit of extra CSS effort, but it should work. But this is making me think it’s really time for us to invest in a solution for circular dependencies. Probably going to take some time as it’d be a substantial architectural effort, but here’s a proposal: https://github.com/plotly/dash/issues/889

re: ports - the default ports are 80 for http and 443 for https - if you serve on those ports you don’t need to include them in your url. As for getting it onto a .com, there are lots of ways - we document how to use Heroku here: https://dash.plot.ly/deployment (note that these dash docs are, themselves, a dash app hosted publicly). For more demanding projects we’d be happy to talk about the dash server that Plotly sells as part of Dash Enterprise :slight_smile:

I am facing the same exact issue. First I thought about doing something like @David22 suggested, using css to design dcc.Link as tabs but when I found the Tab component it was more intuitive. In the Financial Report Dash example they do something like @David22 says but I prefer it looking more like the Tab component. Does someone have a desing example for that? Its important for me that the selected tab will be highlighted.

Here is the modified code of the basic example I posted that achieves my goals:

import dash
import dash_core_components as dcc
import dash_html_components as html

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

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

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(
        [
            dcc.Link(
                "Page 1",
                href="/page-1",
                className="tab first",
            ),
            dcc.Link(
                "Page 2",
                href="/page-2",
                className="tab",
            ),
            dcc.Link(
                "Page 3",
                href="/page-3",
                className="tab",
            ),
        ],
        className="row all-tabs",
    ),
    html.Div(id='page-content'),
])

page_1_layout = html.Div([
    html.H1('Page 1'),
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
])

page_2_layout = html.Div([
    html.H1('Page 2'),
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
])


@app.callback(dash.dependencies.Output('page-content', 'children'),
              [dash.dependencies.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 html.Div([html.H1('Error 404 - Page not found')])


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

CSS:

.tab {
  border-style: solid;
  border-color: rgba(0, 0, 0, 0.2);
  border-bottom-style: none;
  border-top-style: none;
  border-right-style: none;
  color: black;
  padding: 10px 14px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
}

.tab.first {
  border-left-style: none;
}

.tab:focus {
  background-color: yellow;
}

The :focus highlights the selected tab. However I would like tabs to be highlighted when linked (i.e loading /page-1 should have the Page 1 tab highlighted). Anyone have any ideas?

1 Like

Hey @philphi, I was playing around with this and I used a callback to make the link Bold when its linked.
I added an ‘id’ to the link by the name : ‘example_page_tab’ and used it like so:

@app.callback(Output('example_page_tab', 'style'), [Input('url', 'pathname')])
 def update_style(pathname):
         if pathname == "/my_page_url/example":
         return {'font-weight': 'bold'}`Preformatted text`

important to note that the you have to use :

app.layout = html.Div(
    [dcc.Location(id="url", refresh=False), html.Div(id="page-content")]
)

in your app layout so you can use the Location as a trigger for the callback.

Using the same CSS I posted above, I’ve altered the code to include your suggestion:

import dash
import dash_core_components as dcc
import dash_html_components as html

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

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

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(
        [
            dcc.Link(
                "Page 1",
                href="/page-1",
                className="tab first",
                id="tab1",
            ),
            dcc.Link(
                "Page 2",
                href="/page-2",
                className="tab",
                id="tab2",
            ),
            dcc.Link(
                "Page 3",
                href="/page-3",
                className="tab",
                id="tab3",
            ),
        ],
        className="row all-tabs",
    ),
    html.Div(id='page-content'),
])

page_1_layout = html.Div([
    html.H1('Page 1'),
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
])

page_2_layout = html.Div([
    html.H1('Page 2'),
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
])


@app.callback([dash.dependencies.Output('page-content', 'children'),
               dash.dependencies.Output('tab1', 'style'),
               dash.dependencies.Output('tab2', 'style'),
               dash.dependencies.Output('tab3', 'style')],
              [dash.dependencies.Input('url', 'pathname')],
              )
def display_page(pathname):
    if pathname == '/page-1':
        return page_1_layout, {'background-color': 'white', 'color': 'black'}, {}, {}
    elif pathname == '/page-2':
        return page_2_layout, {}, {'background-color': 'white', 'color': 'black'}, {}
    else:
        return html.Div([html.H1('Error 404 - Page not found')]), {}, {}, {'background-color': 'white', 'color': 'black'}


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

However this is quite an inelegant solution since it will need to be updated for every new tab. Does anyone else have any other solution?

I agree, @alexcjohnson.

Because, in my case, if you have a look on ev-trends.com, and more specifically on the way the multiple dcc.dropdown is filled (in the div “setting”) when you click on the Brand name, you might observe that

a) if the dcc.dropdown does not contain any model of brand X, then one click on a brand name (button) updates:

  1. the style of the clicked button in Black,
  2. the content of dcc.dropdown, by adding every model of brand X to the dcc.dropdown list

b) if the dcc.dropdown does contain between 1 and n model of brand X, then one click on a brand name (button) updates:

  1. the style of the clicked button in Transparent,
  2. the content of dcc.dropdown, by removing every model of brand X from the dcc.dropdown list

but, I had circular dependencies to fix for this:

  1. removing one model of brand X from the dcc.dropdown list updates the button’s style of the relevant brand from Black to Grey.
  2. removing every model of brand X from the dcc.dropdown list updates the button’s style of the relevant brand from Black or Grey to Transparent.

I used State and various tricks to get the result I wanted, but it makes my code quite ugly. And note sure that the way we deal with this kind of issues right now is without consequence on the performance. When I load my pages and check the “performance” in the console, I have on average 4000ms for the scripting part.

Im currently rewritting my code to make tabs looking like the Buttons currently displayed for the Country and Period choices. Styles seem to be updated faster after a click when it’s a tab instead of a button.

Use two dcc.Location components. One serves as input, another serves as output. They will sync their value by themselves automatically. Since the input and output components have different names, the callback validation will pass.

dcc.Location(id='url-input'),
dcc.Location(id='url-output'),


@app.callback(Output('url-output', 'pathname'), Input('component-a', 'value'))
def update_url_by_component_a(value):
    return f"/{value}"

@app.callback(Output('component-a', 'value'), Input('url-input', 'pathname'))
def update_url_by_component_a(pathname):
    return f"{pathname}"
1 Like

dcc.Location(id='url-output'),

This one makes the page refresh (default=True), but the challenge is to do it without refreshing.
I have not found any possible way to solve this one with dash. :frowning:

I had some luck using a holder div as the output of display_page() and passing the entire dcc.Tabs() component to it, similar to how this tutorial dealt with having a table update itself.

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

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

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

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),

    html.Div(
        id="tabs_holder",
        children=[dcc.Tabs(id="tabs", value='/page-1')]  # Defaults http://127.0.0.1:8050/ to http://127.0.0.1:8050/page-1. Otherwise, set value=None
    ),

    html.Div(id='page-content'),
])

page_1_layout = html.Div([
    html.H1('Page 1'),
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
])

page_2_layout = html.Div([
    html.H1('Page 2'),
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
])

@app.callback([Output('page-content', 'children'),
               Output('tabs_holder', 'children')],
              [Input('url', 'pathname')])
def display_page(pathname):
    tabs = [
        dcc.Tabs(
            id="tabs",
            value=pathname,
            children=[
                dcc.Tab(label='Page 1', value='/page-1'),
                dcc.Tab(label='Page 2', value='/page-2'),
                dcc.Tab(label='Page 3', value='/page-3'),
            ]
        )
    ]
    if pathname == '/page-1':
        return page_1_layout, tabs
    elif pathname == '/page-2':
        return page_2_layout, tabs
    else:
        return html.Div([html.H1('Error 404 - Page not found')]), tabs


@app.callback(Output('url', 'pathname'),
              [Input('tabs', 'value')])
def tab_updates_url(value):
    return value

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

I came up with a hybrid solution combining a few of the answers here.

The example below takes the pathname from the URL and sets the value in the tabs and the other way round:

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

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

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

app.layout = html.Div([
    dcc.Location(id='url0', refresh=False),
    dcc.Location(id='url1', refresh=False),
    html.Div(
        id="tabs_holder",
    ),

    html.Div(id='page-content'),
])

page_1_layout = html.Div([
    html.H1('Page 1'),
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
])

page_2_layout = html.Div([
    html.H1('Page 2'),
    dcc.Link('Go to Page 1', href='/page-1'),
    html.Br(),
    dcc.Link('Go to Page 2', href='/page-2'),
])

@app.callback([Output('page-content', 'children'),
               Output('tabs_holder', 'children')],
              [Input('url0', 'pathname')])
def display_page(pathname):
    tabs = [
        dcc.Tabs(
            id="tabs",
            value=pathname,
            children=[
                dcc.Tab(label='Page 1', value='/page-1'),
                dcc.Tab(label='Page 2', value='/page-2'),
                dcc.Tab(label='Page 3', value='/page-3'),
            ]
        )
    ]
    if pathname == '/' or pathname == '/page-1':
        return page_1_layout, tabs
    elif pathname == '/page-2':
        return page_2_layout, tabs
    else:
        return html.Div([html.H1('Error 404 - Page not found')]), tabs


@app.callback([Output('tabs', 'value'),
               Output('url0', 'pathname')],
              [Input('url1', 'pathname')])
def url_updates_tabs(pathname):
    if pathname == '/': # the default tab/page
        return ['/page-1', '/page-1']
    return [pathname, pathname]
    
    
@app.callback(Output('url1', 'pathname'),
              [Input('tabs', 'value')])
def tab_updates_url(tab):
    if tab is not None:
        return tab

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

@petya Interesting solution! I get some errors popping up when I do that though, even with suppress_callback_exceptions=True. They look like this:

image

Any thoughts on avoiding those?

Also @wyoming your solution worked for me when just using the tabs and links, but if navigate to <mysite.com>/page-2 it starts me off on <mysite.com>/page-1. It’s kinda looking like I’ll have to ditch dbc.Tabs and just create a look-alike with dcc.Link if I want to be able to have clean behavior when navigating with either the tab or the URL.