šŸ“£ Introducing Dash `/pages` - A Dash 2.x Feature Preview

Thanks, @chriddyp and @AnnMarieW! The pages feature is an excellent addition and makes Dash even better!

I was wondering if it is possible to set up an app structure that allows multiple sub pages per page. The idea is to navigate the pages using a navigation bar at the top, and to add a second navigation bar whenever a page has sub pages. I came up with the following app structure and would like to ask whether you think this is a reasonable approach. For each of the pages, I have a folder containing page-specific stuff: layout, callbacks etc. In my example, page 2 has sub pages:

-- pages
   |-- home
       |-- home.py
   |-- page_1
       |-- page_1.py
   |-- page_2
       |-- sub_item_1
            |-- sub_item_1.py
       |-- sub_item_2
            |-- sub_item_2.py
       |-- submenu.py
       |-- page_2.py
   |-- other pages...
   |-- app.py

In principle, I got this to work with the following implementation, but I am not sure if this is the best approach and would appreciate any kind of feedback. I only add navigation links to the navigation bar at the top when ā€˜sub’ is not a sub string of the ā€˜module’ value of a page.

app.py:

import dash
import dash_labs as dl
import dash_bootstrap_components as dbc

from dash import html, Input, Output, State

app = dash.Dash( __name__, plugins=[dl.plugins.pages], external_stylesheets=[dbc.themes.BOOTSTRAP])

navbar = dbc.Navbar(
    dbc.Container([
        html.A(dbc.Row(
            dbc.Col(dbc.NavbarBrand("MyApp", className="ms-2")),
                align="center",
                className="g-0",
        ),
        href="/",
        style={"textDecoration": "none"},
        ),
        dbc.Row([
            dbc.NavbarToggler(id="navbar-toggler"),
            dbc.Collapse(
                dbc.Nav([
                    dbc.NavItem(dbc.NavLink(page['name'], href=page['path'], active="exact")) for page in dash.page_registry.values() if page["module"] != "pages.not_found_404" and "sub" not in page["module"]
                ],
                className="w-100",
                ),
            id="navbar-collapse",
            is_open=False,
            navbar=True,
            ),
        ],
        className="flex-grow-1",
        ),
    ],
    fluid=True,
    ),
    dark=True,
    color="dark",
    fixed="top",
    sticky=True
)

app.layout = html.Div([navbar, dl.plugins.page_container]) 

@app.callback(
    Output("navbar-collapse", "is_open"),
    [Input("navbar-toggler", "n_clicks")],
    [State("navbar-collapse", "is_open")],
)
def toggle_navbar_collapse(n, is_open):
    if n:
        return not is_open
    return is_open

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

home.py:

import dash
from dash import html
import dash_bootstrap_components as dbc

dash.register_page(__name__, path="/", order=1)

content = [html.H2("Home")]

layout = html.Div([
   dbc.Row(
       dbc.Col(content, width={"size": 8, "offset": 1}),
       align="center"
   )
])

page_2.py:

import dash
from dash import dcc, html
from .submenu import sidebar

dash.register_page(__name__, path="/page_2", order=2)

content = html.Div([html.H2("Page 2")])

layout = html.Div([sidebar, content])

Creating the submenu automatically from the page registry does not work, so I am creating it manually. I assume, page 2 is not fully registered here, but creating it in app.py and importing it from there is not possible because it would be a circular import.
submenu.py:

import dash_bootstrap_components as dbc
from dash import html

sidebar = html.Div([
    dbc.Nav([  # this works
        dbc.NavLink("Sub Item 1", href="/page_2/item_1", active="partial"),
        dbc.NavLink("Sub Item 2", href="/page_2/item_2", active="partial")
    ], vertical=True, pills=True),

    # dbc.Nav([  # this does not work
    #     dbc.NavLink(page['name'], href=page['path'], active="exact") for page in dash.page_registry.values() if page["module"] != "pages.not_found_404" and "page_2.sub" in page["module"]
    # ], vertical=True, pills=True)


], style={"position": "fixed", "padding": "1rem 1rem", "background-color": "#f8f9fa", "height": "100vh"})

sub_item_1.py:

import dash
from dash import html
from ..submenu import sidebar

dash.register_page(__name__, path="/page_2/item_1", order=1)

content = html.Div([html.H2("Page 2 - Item 1")])

layout = html.Div([sidebar, content])

sub_item_2.py:

import dash
from dash import html
from ..submenu import sidebar

dash.register_page(__name__, path="/page_2/item_2", order=1)

content = html.Div([html.H2("Page 2 - Item 2")])

layout = html.Div([sidebar, content])

With this setup, the ā€˜active’ style of the dbc.NavLinks in the navigation bar at the top does not work any more. Any ideas on how to use this feature? Any feedback would be highly appreciated!