šŸ“£ Dash 2.5 Released - Easier Multi-Page Apps, Component Properties

Update : version 2.6.0 has been released since this was posted.

Hey Dash enthusiasts,

:rocket: Weā€™re excited to announce that Dash 2.5.0 & 2.5.1 have been released. 2.5.1 contains a small bug fix for one of the features in 2.5.0. These are a backwards compatible releases and we recommend going straight to 2.5.1 .

pip install dash==2.5.1

Official Changelog :arrow_forward: Dash v2.5.0 and v2.5.1

Highlights :arrow_down_small:

Improved Multipage Experience with Pages

Dash Pages implements features to simplify creating a multi-page app, handling URL routing and offering an easy way to structure and define the pages in your app.

There are many advantages of using Pages to build a multi-page app - it will automatically:

  • import the pages from the pages folder
  • handle nested folders within the pages folder
  • handle the routing
  • update the title in the browser for each page
  • create meta tags for sharing links on social media that include title, description and image
  • pass variables from the url query strings to the page layout function
  • pass variables from the url pathname to the layout function
  • create a default 404 not found page, with the ability to provide a custom 404 page
  • redirect to other pages, when redirect paths are specified.
  • Set validate_layout under-the-hood to avoid callback exceptions

:tv: Tutorial - create multipage app with Pages
multi-page with dash pages

:tv: Check out this beautiful multipage App created by our very own community member, @raptorbrad.

There are three basic steps for creating a multi-page app with Dash Pages:

  1. Create individual .py files for each page in your app, and put them in a /pages directory.
  2. In each of these page files:
    • Add a dash.register_page(__name__), which tells Dash that this is a page in your app.
    • Define the pageā€™s content within a variable or function called layout.
  3. In your main app file, app.py:
    • When declaring your app, set use_pages to True: app = Dash(__name__, use_pages=True)
    • Add dash.page_container in your appā€™s layout where you want the content of the pages to be displayed for when the user visits one of the appā€™s page paths.

Letā€™s see an example of a multipage app with 3 pages and 1 app.py file, which is the entry point to our 3 pages.

Folder structure:

  • app.py
  • assets
  • pages
    |-- analytics.py
    |-- home.py
    |-- archive.py

The Code:

`pages/analytics.py` :
import dash
from dash import html, dcc, callback, Input, Output

dash.register_page(__name__)

layout = html.Div([
    html.H1('This is our Analytics page'),
	html.Div([
        "Select a city: ",
        dcc.RadioItems(['New York City', 'Montreal','San Francisco'],
        'Montreal',
        id='analytics-input')
    ]),
	html.Br(),
    html.Div(id='analytics-output'),
])


@callback(
    Output('analytics-output', 'children'),
    Input('analytics-input', 'value')
)
def update_city_selected(input_value):
    return f'You selected: {input_value}'
`pages/home.py` :
import dash
from dash import html, dcc

dash.register_page(__name__, path='/')

layout = html.Div(children=[
    html.H1('This is our Home page'),
    html.Div('This is our Home page content.'),
])
`pages/archive.py` :
import dash
from dash import html, dcc

dash.register_page(__name__)

layout = html.Div([
    html.H1('This is our Archive page'),
    html.Div('This is our Archive page content.'),
])

And our main app file.

`app.py` :
from dash import Dash, html, dcc
import dash

app = Dash(__name__, use_pages=True)

app.layout = html.Div([
	html.H1('Multi-page app with Dash Pages'),

    html.Div(
        [
            html.Div(
                dcc.Link(
                    f"{page['name']} - {page['path']}", href=page["path"]
                )
            )
            for page in dash.page_registry.values()
        ]
    ),

	dash.page_container  # this is where each of the pages' content will be displayed.
])

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

Simple multi-page app

Important: Use @dash.callback instead of @app.callback so that you donā€™t have circular imports. If you need access to the app object, use app = dash.get_app()


:point_right: Pages has many more features like redirects, meta tags, nested pages, and more. Make sure to read the complete docs to learn more.

:point_right: Additional multipage app examples with Pages. This GitHub repo is community-created & maintained.

:blue_heart: Pages was developed in the open with collaboration from the Dash Community. Many thanks to everyone, and especially to @AnnMarieW for leading this effort! View the original discussion & announcement.

:technologist: Sharing is Caring: If you have already made a multipage app with Pages, weā€™d love to see it. Donā€™t forget to share it on the show-and-tell tag.

:warning: if you had built a multipage app with the dash-labs pages plugin, follow these 3 steps to make your app compatible with Dash 2.5.1 or higher:

  1. Remove import dash_labs as dl or upgrade dash-labs to V1.1.0.

  2. In the main app file, change:

    app = Dash(__name__, plugins=[dl.plugins.pages])
    

    to:

    app = Dash(__name__, use_pages=True)
    
  3. And change:

    dl.plugins.page_container
    

    to:

    dash.page_container
    

Component Properties

Since the beginning of Dash, a fundamental limitation in our component architecture was that the only component property that could accept other components was the children property.

In the React ecosystem, many components allow you to pass other components in as properties. In Dash 2.5, weā€™ve updated the foundational architecture to enable component authors to accept components in arbitrary properties, not just children. This unlocks great potential for richer components, easier customization across the component ecosystem, and greater parity with 3rd party React components.

Any property that might accept a string could be a good candidate for accepting a component as well. Accepting components instead of strings allows end users to easily modify the style of the string with e.g. html.Span('my label', style={'color': 'hotpink'}) . Imagine the ability to pass in html.Img in various labels or dcc.Graph in table cells.

In Dash 2.5, weā€™ve used this feature to expand the API for 3 components, dcc.Dropdown, dcc.Checklist, and dcc.RadioItems. These components can take other Dash components for label within the options property.

If in the past the Dropdown options were written as such:

dcc.Dropdown(options=[
    {'label':'Montreal', 'value':'Montreal'},
    {'label':'New York City', 'value':'NYC'},
    {'label':'San Francisco', 'value':'SF'}
])

Now you can assign components into the label. One advantage of this is enhanced styling of dropdown options. For example, in the code below the html.Div component is assigned to the label of the Dropdown:

    dcc.Dropdown(options=[
        {'label':html.Div('Montreal', style={'color': 'Orange', 'font-size': 20}), 'value':'Montreal'},
        {'label':html.Div('New York City', style={'color': 'Purple', 'font-size': 20}), 'value':'NYC'},
        {'label':html.Div('San Francisco', style={'color': 'Blue', 'font-size': 20}), 'value':'SF'}
    ])

Another advantage of assigning Dash components to label is that you can have more than just a string type variable as part of the options property. Hereā€™s an example of an image combined with a string, assuming your image files are in the assets folder.

dcc.Dropdown(options=[
    {
        'label':html.Div([
            html.Img(src="/assets/python_50px.png", height=30),
            html.Div("Python", style={'font-size': 15, 'padding-left': 10}),
        ], style={'display': 'flex', 'align-items': 'center', 'justify-content': 'center'}), 
        'value':'Py'
    },
    {
        'label':html.Div([
            html.Img(src="/assets/r_50px.png", height=30),
            html.Div("R", style={'font-size': 15, 'padding-left': 10}),
        ], style={'display': 'flex', 'align-items': 'center', 'justify-content': 'center'}), 
        'value':'R'
    },
    {
        'label':html.Div([
            html.Img(src="/assets/julia_50px.png", height=30),
            html.Div("Julia", style={'font-size': 15, 'padding-left': 10}),
        ], style={'display': 'flex', 'align-items': 'center', 'justify-content': 'center'}), 
        'value':'Jul'
    },
])

For more examples, see:

:tv: Component Properties fireside chat
component properties dash


Notable Bug Fixes & Minor Changes

  • #2049 Added wait_for_class_to_equal and wait_for_contains_class methods to dash.testing
  • #2087 - Fix bug #2086 in which using id as a key within a componentā€™s id breaks the new callback contextā€™s args_grouping function.
  • #2084 - In dash 2.5.0, a default viewport meta tag was added as recommended for mobile-responsive sites by mdn. This feature can be disabled by providing an empty viewport meta tag. e.g. app = Dash(meta_tags=[{"name": "viewport"}])
  • #2090, #2092. Fixed bug where the path to the pages_folder was incorrect on Windows.
  • #2043 - Fix bug #2003 in which dangerously_allow_html=True + mathjax=True works in some cases, and in some cases not.
  • #2065 - Fix bug #2064 rendering of dcc.Dropdown when a value is provided but no options.
  • #2047 - Fix bug #1979 in which DASH_DEBUG as environment variable gets ignored. Thank you @PGrawe for the contribution. :heart:
  • #2070 - Fix bug #2066 nested types triggering maximum call stack error when building typescript components.
  • #2050 - Changed find_element and find_elements to accept an attribute argument that aligns with Seleniumā€™s By class, allowing you to search elements by other attributes. Default value is CSS_SELECTOR to maintain backwards compatibility with previous find_elements. Thank you to @MrTeale for this contribution :pray:

Previous Releases

:mega: Dash 2.4 Released - Improved Callback Context, Clientside Callback Promises, Typescript Components, Minor Ticks, and More!
:mega: Dash 2.3.0 Release - MathJax and fillpattern option in scatter trace
:mega: Dash 2.2.0 Release - Adds ticklabelstep to axes, and added dash.get_asset_url
:mega: Dash 2.1.0 Release - Autogenerated IDs and reƤrranged keyword arguments in Dash components
:mega: Dash 2.0 Prerelease Candidate Available!

13 Likes

Excited about this release! And big congrats to community member @AnnMarieW for building and testing Pages :clap:

@adamschroeder and I recorded two conversations about the features above. Do folks appreciate these as part of these announcements? Would love to hear your thoughts, here or in a DM :slightly_smiling_face:

Love these changes, thanks so much to the Dash team and @AnnMarieW :smile:

A quick question re. pages, is it possible to enable hot reloading when the content of pages change? In my trials I had to go and save an imported module to make the app reload.

You can use a callback with dcc.Interval as input to refresh page data. Itā€™s definitely one of the more advanced aspects of Dash as pretty much every downstream callback will stem from that initial load.

I just spent the past 1.5 weeks working in a multi-page app and the functionality is awesome. Much like the rest of Dash, it works exactly like you think it should. Just what I was hoping for!

  • I wish there was a dcc.Page instead of page_container so that I could treat it like the rest of the content e.g. give it a className instead of inspecting the html output to figure out how to target it
  • Another candidate for a component that is exacerbated by the introduction of multi-page apps is navbars. I ended up making my own that detected/matched URL changes to highlight the current page:

  • Thereā€™s a bug where you canā€™t redirect from root (/ or '') to any other page.
  • Minor: it was somehow trickier than I thought it would be to get the dimensions of the page_container to stick to the edges of the window. I was using a horizontal navbar at the top of the page as the app.layout, and had to offset the page_container div with padding equal to the navbar size.

@HashRocketSyntax

You can wrap dash.page_container in another component to give it a className, for example:

html.Div(dash.page_container, className="m-4")

You can find good navbar components in dash-bootstrap-components and dash-mantine-components libraries. Also - I like the navbar you created! If you would like to share, it would be great to see it posted in the tip-and-tricks category :+1:

Iā€™m not sure what you mean about sticking to the edges of the window, but if you start a new topic with a minimal example, I might be able to help.

2 Likes

Hey @RenaudLN Iā€™m glad you like pages :slight_smile:

Can you say more about the hot reloading problem? I canā€™t duplicate the issue. It would also be good to start a new topic so if others are having the same issue it will be easier to follow the thread.

Thanks @chriddyp - Iā€™m so thrilled to see this across the finish line. I appreciate the patient mentoring and thorough code reviews from you, @alexcjohnson and @Philippe . This was an amazing learning experience! Also thanks to the awesome Dash community for all of the feedback during development. And we canā€™t forget the Plotly technical writers Liam and Jessica for the excellent new pages documentation :slight_smile:

6 Likes

Fantastic work, Iā€™m really looking forward to migrating my existing apps from my dodgy home-grown multi-page solution to the official one (loving that query string and pathname variables are supported!).

And the component properties is also fantastic. Is there an ā€˜officialā€™ way of applying this to custom components as well?

2 Likes

If you are a component author, then you can set the property to PropTypes.node. See the dcc.Dropdownā€™s label property as an example: https://github.com/plotly/dash/blob/dev/components/dash-core-components/src/components/Dropdown.react.js. Not all nested forms are supported, see the examples in ā€œFor Component Authorsā€ at the bottom of Component Properties | Dash for Python Documentation | Plotly

Although I canā€™t share the code, due to the sensitive information that it contains, weā€™ve used multi-page apps in our project with the Slovakian government, to better manage the refugee crisis due to Russiaā€™s invasion of Ukraine.
We are using Dash, and the Multi-Page capability, to provide a decision support tool, that is making a difference.
Thank you to everyone involved and to the whole community

6 Likes

Hi @RenaudLN @benn0 @jgomes_eu @HashRocketSyntax, @AnnMarieW and anyone else that might have see the videos attached to the post,
Iā€™d like to bring us back to Chrisā€™ question: if you saw parts or all of the videos we attached to the post, did you find them useful? Yes, No, Why?
Any feedback and suggestions are welcomed, as we try to improve the experience of learning new features and sharing new knowledge.

Thank you all.

Iā€™m planning on watching them tomorrow and will let you know. I always find your videos insightful and Iā€™ve learned a lot with them, but I will make sure to bring any points if I have any. Again, thank you so much for the work you do for this community.

2 Likes

I really enjoy your videos and I think itā€™s really helpful to demonstrate new features this way. I also like how Chris gave some background about how these changes fit into Dashā€™s development plan. Iā€™m excited to see the new things coming, like more components supporting the Components as Properties feature.

You mentioned that one of your favorite new pages feature is how it automatically creates the mega tags so when you share a link to your site it shows as a nicely formatted card with title, description and image. As an example, Here are some links to site that are using pages

The dash-mantine-components documentation is a Dash app made with dmc components and uses pages:

If you provide an image, itā€™s included, and the card is customized for each page

3 Likes

I found the video incredibly useful! Hitting the high level points of the update with code examples really helped me wrap my head around the new functionality. I felt like I got a lot more out of this compared to just going through the release notes with no context

3 Likes

You can find the code for the example from the video here: dash-multi-page-app-demos/multi_page_example1 at main Ā· AnnMarieW/dash-multi-page-app-demos Ā· GitHub

2 Likes

A post was split to a new topic: Error: NoLayoutException in pages multi-page app

Thanks for reporting @HashRocketSyntax. This is now fixed and will be in the next dash release. :confetti_ball:

1 Like

Hi @adamschroeder , thanks for the prompt to check out the videos! Iā€™ll admit that I donā€™t usually watch videos of this length, as finding the time to devote to it can be a challenge, and often I donā€™t feel like Iā€™m the target audience. I really appreciate the thought that goes into putting these together, but Iā€™ll be honest that I donā€™t find them particularly valuable (Iā€™m sure others do, though!). Here are my general notes on the videos:

TL,DR: Maybe Iā€™m not the intended audience. I tend to avoid video tutorials, so that aspect of these was off putting for me, but I would be really keen to see videos on the technical design aspects of these features.

Pages
I tend to steer clear of tutorials in video format, and for me I think that there is too much of a tutorial aspect to these (particularly the pages video). Having the timing of different sections was valuable, but I found myself looking for areas that I think would be particularly interesting, technically. I found that I started seeking around, thinking that the ā€˜deep diveā€™ on the register_pages would be the type of content that Iā€™m really interested in. (Spoiler alert: I was hoping for a deeper dive on what happens under the hood here).

Components as Properties

The first half of this video is (IMO) too slow for me - the first 6 minutes could be much quicker, although I understand why you structured it this way.
The analogy to html and the dom was great, and really felt like a lightbulb to me why dash is the way it is and why this is such a big change. If you assume that people watching this video have enough background (I donā€™t think it needs much, I have no formal background in it) in html representation, then IMO you could jump straight in at this point, which might also make it a much more powerful point. (Without that lightbulb moment, people like me might just assume that itā€™s some kind of weird, dash-specific thing that doesnā€™t really have an explanation)
ā€˜One of our engineers found a clever way to do thisā€™ - I would really, really like a video that goes into the detail on this. This is the kind of content that IMO would suit a video format really well, since it wouldnā€™t be something that ends up in documentation but as someone who has developed custom components, that kind of material would really help me to better understand the dash roadmap, and how these developments might change the way I look at developing dash apps.
Also on the performance degradation - this would be suitable for a deep dive video as well, IMO. I would really like to understand what perf implications there are for these things.

Anyway, these are just my opinions and are often not worth much. And while I donā€™t know that creating more types of videos is necessarily a way of getting more engagement in them, I do know that you would have an audience of at least one for the technical nitty gritty of some of these features.

1 Like

Thanks for the feedback @benn0 ! Appreciate that

1 Like