<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>dash-resources.com</title>
	<atom:link href="https://dash-resources.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://dash-resources.com/</link>
	<description>Learn to build interactive web applications with Python and Dash plotly</description>
	<lastBuildDate>Tue, 17 Feb 2026 23:09:24 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>

<image>
	<url>https://dash-resources.com/wp-content/uploads/2024/12/cropped-dash-logo-favicon-512-32x32.png</url>
	<title>dash-resources.com</title>
	<link>https://dash-resources.com/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>How to secure a multi-page Dash app with dash-auth</title>
		<link>https://dash-resources.com/how-to-secure-a-multi-page-dash-app-with-dash-auth/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Thu, 28 Aug 2025 08:44:53 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[dash-auth]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=926</guid>

					<description><![CDATA[<p>In this tutorial, we will see how to secure your Dash multi-page app using the basic authentication mechanism in dash-auth. It’s the natural continuation of [...]</p>
<p>L’article <a href="https://dash-resources.com/how-to-secure-a-multi-page-dash-app-with-dash-auth/">How to secure a multi-page Dash app with dash-auth</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this tutorial, we will see how to secure your <a href="https://dash.plotly.com/urls">Dash multi-page</a> app using the basic authentication mechanism in <code>dash-auth</code>.</p>



<p>It’s the natural continuation of the previous tutorial: <a href="https://dash-resources.com/how-to-secure-your-dash-app-with-dash-auth/">How to secure a Dash app with dash-auth</a>, where you can learn how to use <code>dash-auth</code>, connect to a database, and display the username.</p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#dash-auth">Dash-auth</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#app-py">app.py</a>
          </li>
          <li>
            <a href="#pages-public-py">pages/public.py</a>
          </li>
          <li class="last">
            <a href="#pages-private-py">pages/private.py</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#result">Result</a>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>Let’s go! </p>



<h2 id='dash-auth'  id="boomdevs_1" class="wp-block-heading" >Dash-auth</h2>



<p>If you haven’t yet, install <code>dash</code> and <code>dash-auth</code>:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">pip install dash dash-auth
</code></pre>



<p>Then, create three files and one folder:</p>



<ul class="wp-block-list">
<li><code>app.py</code>: the main app file</li>



<li><code>pages/public.py</code>: the publicly accessible page at URL &#8220;/&#8221;</li>



<li><code>pages/private.py</code>: the private page at URL &#8220;/private&#8221;</li>
</ul>



<h3 id='app-py'  id="boomdevs_2" class="wp-block-heading" >app.py</h3>



<p>This file will be very similar to what we had in the previous tutorial. The list of users is hardcoded at the beginning of the file:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import Dash, html, dcc, page_container
import dash_auth

# Note: better store the list in a secrets file or in a database.
USERS = {
    "alice": "secret123",
    "bob": "pa$$w0rd"
}

# Create the Dash app
app = Dash(
    __name__,
    use_pages=True,
    suppress_callback_exceptions=True
)

# Add authentication
auth = dash_auth.BasicAuth(
    app,
    username_password_list=USERS,
    secret_key="something_like_nUGz8DZvb...",
    # Public routes are not protected by authentication.
    # All others are protected by default
    public_routes=["/"]
)

# Define the layout
app.layout = html.Div([
    html.H1("My app"),
    html.P(dcc.Link("Public page", href="/")),
    html.P(dcc.Link("Private page", href="/private")),
    html.Hr(),
    page_container
])

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

</code></pre>



<p>There are two key differences:</p>



<ul class="wp-block-list">
<li>We use pages for the Dash app (<code>use_pages=True</code> and <code>suppress_callback_exceptions=True</code>)</li>



<li>We define the public pages in Dash-auth using <code>public_routes</code>.</li>
</ul>



<p>Then, the layout shows two links to the two pages and the page content (using <code>page_container</code>).</p>



<h3 id='pages-public-py'  id="boomdevs_3" class="wp-block-heading" >pages/public.py</h3>



<p>The public page will contain a button that increments itself when clicked:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import html, dcc, register_page, callback, Output, Input
from dash_auth import public_callback

# The public page is the home
register_page(
    __name__,
    "/",
)

# The page layout
def layout():
    return html.Div([
        html.P("This is a public page."),
        html.Button("Click me (0)", id="button", n_clicks=0)
    ])

# A simple public callback
@public_callback(
    Output("button", "children"),
    Input("button", "n_clicks"),
)
def update_button(n_clicks):
    return f"Click me ({n_clicks})"

</code></pre>



<p>The only key change here is the use of <code>public_callback</code> (from <code>dash-auth</code>) instead of <code>callback</code>.</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img fetchpriority="high" decoding="async" width="1024" height="508" src="https://dash-resources.com/wp-content/uploads/2025/08/image-3-1024x508.png" alt="" class="wp-image-929" style="width:553px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2025/08/image-3-1024x508.png 1024w, https://dash-resources.com/wp-content/uploads/2025/08/image-3-300x149.png 300w, https://dash-resources.com/wp-content/uploads/2025/08/image-3-768x381.png 768w, https://dash-resources.com/wp-content/uploads/2025/08/image-3.png 1168w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">The public, unprotected page</figcaption></figure>
</div>


<p class="callout">By default, all callbacks use the same URL endpoint: <code>/dash-update-component</code>. This is why we need to specify that this callback is public, while others are protected by default as soon as we add <code>dash-auth</code> to the app.<br><br>Under the hood, the callback ID is simply added to a list of whitelisted callbacks in the Flask server’s config.</p>



<h3 id='pages-private-py'  id="boomdevs_4" class="wp-block-heading" >pages/private.py</h3>



<p>The private page will only show the username using a layout function:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import html, register_page
import flask

register_page(
    __name__,
    "/private",
)

def layout():
    username = flask.session.get("user").get("email")

    return html.Div([
        html.P("If you see this, you are authenticated <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />"),
        html.P(f"You are logged in as {username}.")
    ])
</code></pre>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" width="1024" height="530" src="https://dash-resources.com/wp-content/uploads/2025/08/image-4-1024x530.png" alt="" class="wp-image-930" style="width:565px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2025/08/image-4-1024x530.png 1024w, https://dash-resources.com/wp-content/uploads/2025/08/image-4-300x155.png 300w, https://dash-resources.com/wp-content/uploads/2025/08/image-4-768x398.png 768w, https://dash-resources.com/wp-content/uploads/2025/08/image-4.png 1136w" sizes="(max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">The private page, protected by HTTP basic authentification.</figcaption></figure>
</div>


<p><code>dash-auth</code> saves the current username in a flask cookie, so we can get easily get it in the layout function, or within a callback.</p>



<h2 id='result'  id="boomdevs_5" class="wp-block-heading" >Result</h2>



<p>Now let’s run this app!<br>Here is a little video of what it looks like:</p>



<figure class="wp-block-video"><video height="852" style="aspect-ratio: 1386 / 852;" width="1386" controls src="https://dash-resources.com/wp-content/uploads/2025/08/dash-auth-pages.mp4"></video></figure>



<p><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" />&nbsp;Note that once we’re logged in, we can’t log out manually. The browser must be closed instead.</p>



<h2 id='conclusion'  id="boomdevs_6" class="wp-block-heading" >Conclusion</h2>



<p>I hope this tutorial was instructive! I found it interesting that <code>dash-auth</code> evolved to secure multi-page apps as well, which I didn’t know until writing these two tutorials <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>As with the <a href="https://dash-resources.com/how-to-secure-your-dash-app-with-dash-auth/">previous tutorial</a>, the conclusion is similar: <code>dash-auth</code> has its drawbacks, but it’s a <strong>fairly easy solution</strong> to add an authentication layer to a dashboard or a data app. However, I would <strong>not recommend</strong> using it for production apps that have very sensitive data.</p>



<p>If you need proper login/logout behavior (e.g., session expiration, multiple users switching), you’ll need a more advanced system such as:</p>



<ul class="wp-block-list">
<li><code>flask_login</code></li>



<li>OAuth (Google, GitHub…)</li>



<li>Enterprise identity providers (Okta, Auth0, Azure AD…)</li>



<li><a href="https://plotly.com/dash/authentication/">Dash-Enterprise</a> (handles LDAP, SAML, OIDC)</li>
</ul>



<p>Happy Dash coding!</p>



<p></p>
<p>L’article <a href="https://dash-resources.com/how-to-secure-a-multi-page-dash-app-with-dash-auth/">How to secure a multi-page Dash app with dash-auth</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		<enclosure url="https://dash-resources.com/wp-content/uploads/2025/08/dash-auth-pages.mp4" length="363032" type="video/mp4" />

			</item>
		<item>
		<title>How to secure a Dash app with dash-auth</title>
		<link>https://dash-resources.com/how-to-secure-your-dash-app-with-dash-auth/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Thu, 28 Aug 2025 08:37:01 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[dash-auth]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=918</guid>

					<description><![CDATA[<p>In this tutorial, we’ll see how you can protect your Dash app with the package dash-auth. We’ll cover how to set up basic authentication, how [...]</p>
<p>L’article <a href="https://dash-resources.com/how-to-secure-your-dash-app-with-dash-auth/">How to secure a Dash app with dash-auth</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In this tutorial, we’ll see how you can protect your Dash app with the package <code>dash-auth</code>.</p>



<p>We’ll cover how to set up basic authentication, how to define a custom authentication function, and even how to connect it to a database.</p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#basic-dash-auth">Basic Dash auth</a>
      </li>
      <li>
        <a href="#retrieve-the-user">Retrieve the user</a>
      </li>
      <li>
        <a href="#authentication-function">Authentication function</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#comparing-hashed-passwords">Comparing hashed passwords</a>
          </li>
          <li class="last">
            <a href="#use-a-database">Use a database</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#why-you-can-t-logout">Why you can’t logout</a>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>Let’s go! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b07.png" alt="⬇" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 id='basic-dash-auth'  id="boomdevs_1" class="wp-block-heading" >Basic Dash auth</h2>



<p>First, install Dash and Dash Auth:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">pip install dash dash-auth</code></pre>



<p>Next, define a list of users and their associated passwords:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import Dash, html
import dash_auth

# <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a0.png" alt="⚠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Defining passwords directly in code is a bad practice.
# Better: store them in a secrets file or a database.
USERS = {
    "alice": "secret123",
    "bob": "pa$$w0rd"
}

# Create the Dash app
app = Dash(__name__)

# Add authentication
auth = dash_auth.BasicAuth(
    app,
    username_password_list=USERS,
    # Set the secret key to something random.
    secret_key="something_like_nUGz8DZvb..."
)

# Define the layout
app.layout = html.Div([
    html.H1("Protected app"),
    html.P("If you see this, you are authenticated <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />")
])

if __name__ == '__main__':
    app.run(debug=True)
</code></pre>



<p>The secret key must be unique and… secret <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" />.<br>To generate one, run this in your terminal:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">python -c "import secrets; print(secrets.token_urlsafe(32))"
</code></pre>



<p class="callout"><strong>Note:</strong> the <code>secret_key</code> isn’t mandatory for <code>dash-auth</code> since it doesn’t use cookies. However, adding it removes the warning: “<em>WARNING:root:Session is not available. Have you set a secret key?</em>”.</p>



<p>Then, just run your app with <code>python app.py</code>. You should get this result:</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1824" height="1144" src="https://dash-resources.com/wp-content/uploads/2025/08/image.png" alt="" class="wp-image-919" srcset="https://dash-resources.com/wp-content/uploads/2025/08/image.png 1824w, https://dash-resources.com/wp-content/uploads/2025/08/image-300x188.png 300w, https://dash-resources.com/wp-content/uploads/2025/08/image-1024x642.png 1024w, https://dash-resources.com/wp-content/uploads/2025/08/image-768x482.png 768w, https://dash-resources.com/wp-content/uploads/2025/08/image-1536x963.png 1536w" sizes="(max-width: 1824px) 100vw, 1824px" /></figure>



<p>And after log-in:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1630" height="1058" src="https://dash-resources.com/wp-content/uploads/2025/08/image-1.png" alt="" class="wp-image-920" srcset="https://dash-resources.com/wp-content/uploads/2025/08/image-1.png 1630w, https://dash-resources.com/wp-content/uploads/2025/08/image-1-300x195.png 300w, https://dash-resources.com/wp-content/uploads/2025/08/image-1-1024x665.png 1024w, https://dash-resources.com/wp-content/uploads/2025/08/image-1-768x498.png 768w, https://dash-resources.com/wp-content/uploads/2025/08/image-1-1536x997.png 1536w" sizes="auto, (max-width: 1630px) 100vw, 1630px" /></figure>



<p>That’s it! You get a protected page. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f929.png" alt="🤩" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>There are some <strong>limitations</strong>:</p>



<ul class="wp-block-list">
<li>You cannot customize the login/password popup window, it’s natively handled by the browser.</li>



<li>Users cannot logout: the session is active until they close their browser.</li>



<li>The list of users is fixed: you will need to reload the app to add or remove a user or update a password.</li>
</ul>



<h2 id='retrieve-the-user'  id="boomdevs_2" class="wp-block-heading" >Retrieve the user</h2>



<p>The user information is stored in the <code>flask.session</code> object. It’s therefore easy to retrieve it in a callback:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># ...

# Define the layout
app.layout = html.Div([
    html.H1("Protected app"),
    html.P("If you see this, you are authenticated <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />"),
    html.P(id="user_info")  # added this
])

# Define the callback
@app.callback(
    Output("user_info", "children"),
    Input("user_info", "id"),  # dummy trigger
)
def update_user_info(_):
    """ Display the username of the logged in user. """
    username = flask.session.get("user").get("email")
    return f"You are logged in as {username}."

if __name__ == '__main__':
    app.run(debug=True)
</code></pre>



<p>Let’s explain this code:</p>



<ul class="wp-block-list">
<li>I added a new paragraph #user_info</li>



<li>A new callback is triggered at initialization time. It displays the username.</li>
</ul>



<p>Now let’s see the result:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1630" height="1058" src="https://dash-resources.com/wp-content/uploads/2025/08/image-2.png" alt="" class="wp-image-923" srcset="https://dash-resources.com/wp-content/uploads/2025/08/image-2.png 1630w, https://dash-resources.com/wp-content/uploads/2025/08/image-2-300x195.png 300w, https://dash-resources.com/wp-content/uploads/2025/08/image-2-1024x665.png 1024w, https://dash-resources.com/wp-content/uploads/2025/08/image-2-768x498.png 768w, https://dash-resources.com/wp-content/uploads/2025/08/image-2-1536x997.png 1536w" sizes="auto, (max-width: 1630px) 100vw, 1630px" /></figure>



<h2 id='authentication-function'  id="boomdevs_3" class="wp-block-heading" >Authentication function</h2>



<p>It’s possible to use an arbitrary python function to handle authentication. This is useful to:</p>



<ul class="wp-block-list">
<li>store and compare hashed passwords instead of plain passwords;</li>



<li>retrieve user information and passwords from a database.</li>
</ul>



<h3 id='comparing-hashed-passwords'  id="boomdevs_4" class="wp-block-heading" >Comparing hashed passwords</h3>



<p>Storing passwords in clear in the code is a pretty bad practice for security and confidentiality reasons. Instead, a better practice is to store hashed passwords instead of the clear version. If the password leaks, the original password can&#8217;t be found!</p>



<p>You can hash a password using the <code>generate_password_hash</code> from <code>werkzeug.security</code> package. For instance:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from werkzeug.security import generate_password_hash
print(generate_password_hash("secret123"))
# scrypt:32768:8:1$PICpPDH9JdIz75DT$d5f81a560015a407e51989f03d046816954ba2b8d5ee520b999f492352b9e8a39084210ea2fe3bfdcdc2a94047a5397e04bbf3663b505967d3d1ea91a95101d7'
</code></pre>



<p>Then, we can compare the two password hashes with the <code>check_password_hash</code> function:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">
from werkzeug.security import check_password_hash

def auth_func(username, password):
    """ Authenticate the user using hashed passwords. """

    # Check the user exists and the password is correct
    if username in USERS and check_password_hash(USERS[username], password):
        return True
    return False
</code></pre>



<p>That&#8217;s it. We just check the user exists, and compare its hash.</p>



<p>Let&#8217;s put this all together and replace the <code>USERS</code> list with the <code>auth_func</code> parameter:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import Dash, html
from dash_auth import BasicAuth
from werkzeug.security import generate_password_hash, check_password_hash

# This time, we store hashed passwords. 
# Meaning that they can't be compromised if our code leaks.
USERS = {
    "alice": "scrypt:32768:8:1$HtB7iBw3ZGspBOHX$36879fd912db96322af2c33c3eb3fd0142ca2ec51988f332cc477b89c012449e5562487b410264b02d495d785780d0099e9130b42c4f61d7bc9166b93f7d1626",
    "bob": "scrypt:32768:8:1$j2rFpuXSr2D5t4Ij$9436a942b25f93054d3c96e54dcdd342c97f86515e09aafecd564984c22beb991029b70a89164215590ec131be4e13a12e043f4572ceb06576f86f925b1b9d65"
}

def auth_func(username, password):
    """ Authenticate the user using hashed passwords. """
    if username in USERS and check_password_hash(USERS[username], password):
        return True
    return False

app = Dash(__name__)
auth = BasicAuth(
    app, 
    auth_func=auth_func, # We now use auth_func in place of the user list.
    secret_key="something_like_nUGz8DZvb..."
)

app.layout = html.Div([
    html.H1("Protected app"),
    html.P("If you see this, you are authenticated.")
])

if __name__ == "__main__":
    app.run(debug=True)
</code></pre>



<p>The result is visually the same for the user, but now passwords are not stored in clear. It also means that the original passwords are not shared with the developers having access to the Dash app code.</p>



<p>Much better! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h3 id='use-a-database'  id="boomdevs_5" class="wp-block-heading" >Use a database</h3>



<p>We can also use the authentication function to request an external database. The good thing with this solution is that the list of users can be dynamically managed.</p>



<p>Let&#8217;s create a simple script to initialize a <code>sqlite</code> database:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># init_db.py
import sqlite3
from werkzeug.security import generate_password_hash

DB_PATH = "users.db"

def init_db():
    """Create a tiny users table and seed two demo users if empty."""
    with sqlite3.connect(DB_PATH) as con:
        cur = con.cursor()
        cur.execute("""
            DROP TABLE IF EXISTS users;
            CREATE TABLE users (
                username TEXT PRIMARY KEY,
                password_hash TEXT NOT NULL,
                language TEXT NOT NULL
            )
        """)

        # Insert values
        cur.executemany(
            "INSERT INTO users(username, password_hash, language) VALUES (?, ?, ?)",
            [
                ("alice", generate_password_hash("secret123"), "en"),
                ("bob",   generate_password_hash("pa$$w0rd"), "fr"),
            ],
        )

if __name__ == "__main__":
    init_db()
    print("Database initialized")
</code></pre>



<p>Note that we directly save the hashed password in the <code>users</code> table.</p>



<p>Then, we simply query this database in our <code>auth_func</code> function to get the hashed password of a user, then compare it:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import Dash, html
from dash_auth import BasicAuth
from werkzeug.security import generate_password_hash, check_password_hash
import sqlite3

DB_PATH = "users.db"

def get_hash(username):
    """Fetch the stored hash for a username, or None."""
    with sqlite3.connect(DB_PATH) as con:
        cur = con.cursor()
        cur.execute("SELECT password_hash FROM users WHERE username = ?", (username,))
        row = cur.fetchone()
        return row[0] if row else None

def auth_func(username, password):
    """ Authenticate the user using hashed passwords. """
    hash_from_db = get_hash(username)

    if hash_from_db and check_password_hash(hash_from_db, password):
        return True
    return False

app = Dash(__name__)
auth = BasicAuth(
    app, 
    auth_func=auth_func,
    secret_key="something_like_nUGz8DZvb..."
)

app.layout = html.Div([
    html.H1("Protected app"),
    html.P("If you see this, you are authenticated <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />"),
])

if __name__ == "__main__":
    app.run(debug=True)
</code></pre>



<p>That&#8217;s it!</p>



<p>Now you can modify the list of users, modify passwords, etc. in the database without having to reload the application. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>It doesn’t need to be a <code>sqlite</code> database. You can also make HTTP requests to a database provider, or query any other type of database (Postgresql, MongoDB, &#8230;).</p>



<p class="callout">Keep in mind that the <code>auth_func</code> function is executed for every callback and every dash request. If the check takes too much time, think about caching solutions like <code>memoize</code>.</p>



<h2 id='why-you-can-t-logout'  id="boomdevs_6" class="wp-block-heading" >Why you can’t logout</h2>



<p><code>dash-auth</code> uses <strong>HTTP Basic Authentication</strong>, which is built into your browser.</p>



<ul class="wp-block-list">
<li>Your credentials (username + password) are cached.</li>



<li>The browser automatically re-sends them with every request.</li>



<li>There’s no “logout” button because the browser doesn’t expose a way to clear those credentials.</li>
</ul>



<p>The only way to log out? Close the browser tab or window.</p>



<h2 id='conclusion'  id="boomdevs_7" class="wp-block-heading" >Conclusion</h2>



<p>This tutorial helped you to secure a Dash app using the <code>dash_auth</code> package. </p>



<p><code>dash-auth</code> has its drawbacks, but it’s a fairly easy solution to add an authentication layer to a dashboard or a data app. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> However, I would not recommend using it for production apps that have very sensitive data. </p>



<p>If you need proper login/logout behavior (e.g., session expiration, multiple users switching), you’ll need a more advanced system such as:</p>



<ul class="wp-block-list">
<li><code>flask_login</code></li>



<li>OAuth (Google, GitHub…)</li>



<li>Enterprise identity providers (Okta, Auth0, Azure AD, …)</li>



<li><a href="https://plotly.com/dash/authentication/">Dash-Enterprise</a> (handles LDAP, SAML, OIDC)</li>
</ul>



<p>In the next tutorial, we will see <a href="https://dash-resources.com/how-to-secure-a-multi-page-dash-app-with-dash-auth/">how to use <code>dash-auth</code> for multi-pages Dash apps.</a></p>



<p>I hope to see you there!</p>



<p></p>
<p>L’article <a href="https://dash-resources.com/how-to-secure-your-dash-app-with-dash-auth/">How to secure a Dash app with dash-auth</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to use allow_duplicate (good and bad practices)</title>
		<link>https://dash-resources.com/how-to-use-allow_duplicate-good-and-bad-practices/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Wed, 16 Apr 2025 16:26:07 +0000</pubDate>
				<category><![CDATA[Intermediate level]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=852</guid>

					<description><![CDATA[<p>A few weeks ago, I was inspired to write an article about the allow_duplicate options and the possible drawbacks of using it. In this new [...]</p>
<p>L’article <a href="https://dash-resources.com/how-to-use-allow_duplicate-good-and-bad-practices/">How to use allow_duplicate (good and bad practices)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>A few weeks ago, I was inspired to write <a href="https://dash-resources.com/avoiding-desynchronized-state-in-dash-sequential-callbacks-patch-and-better-design-patterns/">an article</a> about the <code>allow_duplicate</code> options and the possible drawbacks of using it.</p>



<p>In this new article, I want to soften my original take and explain when to use <code>allow_duplicate=True</code> and when not to use it with clear examples. Of course, everything you&#8217;ll read here is based on my personal experience and might not cover all use cases! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /> </p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#first-what-is-allow-duplicate=true">First, what is allow_duplicate=True?</a>
      </li>
      <li>
        <a href="#pros-and-cons-of-allow-duplicate=true">Pros and cons of allow_duplicate=True</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#harder-to-track-logic-maintainability-concerns">Harder to track logic &amp; Maintainability Concerns</a>
          </li>
          <li class="last">
            <a href="#potential-race-conditions">Potential race conditions</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#good-use-example-1-separate-concern">Good use example 1: separate concern</a>
      </li>
      <li>
        <a href="#good-use-example-2-partial-update">Good use example 2: partial update</a>
      </li>
      <li>
        <a href="#bad-use-example-1-harder-to-track">Bad use example 1: harder to track</a>
      </li>
      <li>
        <a href="#bad-example-2-race-condition">Bad example 2: race condition</a>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>Let&#8217;s dive in!</p>



<h2 id='first-what-is-allow-duplicate=true'  id="boomdevs_1" class="wp-block-heading" >First, what is <code>allow_duplicate=True</code>?</h2>



<p>In Dash, <strong>each Output</strong> is typically tied to exactly <strong>one</strong> callback. Attempting to attach an additional callback to the same Output will normally raise an error:</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="407" src="https://dash-resources.com/wp-content/uploads/2025/04/image-1024x407.png" alt="" class="wp-image-853" srcset="https://dash-resources.com/wp-content/uploads/2025/04/image-1024x407.png 1024w, https://dash-resources.com/wp-content/uploads/2025/04/image-300x119.png 300w, https://dash-resources.com/wp-content/uploads/2025/04/image-768x305.png 768w, https://dash-resources.com/wp-content/uploads/2025/04/image.png 1242w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Illustration: every Dash developer has experienced this problem of “duplicate callback output” at least once.</figcaption></figure>



<p>However, since version 2.9 Dash provides an <strong>advanced</strong> setting—<code>allow_duplicate=True</code>—that lets you break that convention. This flag, placed in the <code>Output</code> declaration, tells Dash that you <em>intend</em> to create another callback targeting the <em>same</em> Output that was already used in a different callback.</p>



<p>Here is an example:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># First callback
@app.callback(
    Output('output', 'children', allow_duplicate=True),
    Input('input-1', 'value'),
    prevent_initial_call=True,
)
def update_output_1(value):
    # ... some processing 
    return value

# Second callback (duplicate output)
@app.callback(
    Output('output', 'children'),
    Input('input-2', 'value'),
    prevent_initial_call=True,
)
def update_output_2(value):
    # ... some other processing
    return value
</code></pre>



<p>Here, both callbacks target the same <code>Output</code> component property. Because the first callback has <code>allow_duplicate=True</code>, the second is allowed to share that same Output. We could just as easily add a third one, or even more.</p>



<p class="callout">Note that the <code>allow_duplicate=True</code> is required only for the first callback. But if you change the order callbacks are written in the code, you’ll need to update it. Setting it everywhere makes it less error-prone.</p>



<p>Using this option will also requires setting <code>prevent_initial_call=True</code> to ensures the callbacks don’t get triggered simultaneously (which is what we want to avoid, as you&#8217;ll read later).</p>



<h2 id='pros-and-cons-of-allow-duplicate=true'  id="boomdevs_2" class="wp-block-heading" >Pros and cons of <code>allow_duplicate=True</code></h2>



<p>Don’t get me wrong, <strong><code>allow_duplicate=True</code> is useful.</strong></p>



<p>Sometimes we have two distinct user interactions that need to update the same component. As the number of inputs grows, we quickly end up modifying a complex login into a &#8220;mega-callback&#8221;. It therefore become a nightmare to maintain and little changes can break many behaviors.</p>



<p>Instead, <code>allow_duplicate=True</code> enables splitting the responsibilities into smaller, clearer, more isolated callbacks. </p>



<p>But this goes against the first principle: having only one output per callback, which is generally a good design principle. Not following it has potential drawbacks and trade-offs to consider <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b07.png" alt="⬇" class="wp-smiley" style="height: 1em; max-height: 1em;" />.</p>



<h3 id='harder-to-track-logic-maintainability-concerns'  id="boomdevs_3" class="wp-block-heading" >Harder to track logic &amp; <strong>Maintainability Concerns</strong></h3>



<p>Normally in Dash, each property is updated by exactly one callback, so you can always look at that callback to know “what logic is behind this value.” Breaking that rule can lead to confusion: “Wait, which callback is controlling the output now? Or is it both?”</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="1920" height="1116" src="https://dash-resources.com/wp-content/uploads/2025/04/image-1.png" alt="" class="wp-image-854" style="width:627px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2025/04/image-1.png 1920w, https://dash-resources.com/wp-content/uploads/2025/04/image-1-300x174.png 300w, https://dash-resources.com/wp-content/uploads/2025/04/image-1-1024x595.png 1024w, https://dash-resources.com/wp-content/uploads/2025/04/image-1-768x446.png 768w, https://dash-resources.com/wp-content/uploads/2025/04/image-1-1536x893.png 1536w" sizes="auto, (max-width: 1920px) 100vw, 1920px" /><figcaption class="wp-element-caption">Illustration: a small callback mess.</figcaption></figure>
</div>


<p>Multiple callbacks returning to the same output can significantly complicate debugging and future maintenance. Other developers (or a future you) might have difficulty quickly discerning how that one visible component is being updated in different contexts.</p>



<h3 id='potential-race-conditions'  id="boomdevs_4" class="wp-block-heading" ><strong>Potential race conditions</strong></h3>



<p>If you have multiple callbacks writing to the same component property at roughly the same time, you may get <a href="https://en.wikipedia.org/wiki/Race_condition">race conditions</a>—where whichever finishes last overwrites the other. This can cause flickering, inconsistent states, or unexpected behavior if not carefully managed.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="1440" height="654" src="https://dash-resources.com/wp-content/uploads/2025/04/image-2.png" alt="" class="wp-image-855" style="width:653px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2025/04/image-2.png 1440w, https://dash-resources.com/wp-content/uploads/2025/04/image-2-300x136.png 300w, https://dash-resources.com/wp-content/uploads/2025/04/image-2-1024x465.png 1024w, https://dash-resources.com/wp-content/uploads/2025/04/image-2-768x349.png 768w" sizes="auto, (max-width: 1440px) 100vw, 1440px" /><figcaption class="wp-element-caption">Illustration: the &#8220;race condition&#8221; means the result will depend on which callback finishes first, which can lead to unexepcted results (non-deterministic).</figcaption></figure>
</div>


<p>This is something that I saw happening and I wrote an article about it in detail here: <a href="https://dash-resources.com/avoiding-desynchronized-state-in-dash-sequential-callbacks-patch-and-better-design-patterns/">The dark side of allow_duplicates &amp; making calbacks sequential</a>.</p>



<h2 id='good-use-example-1-separate-concern'  id="boomdevs_5" class="wp-block-heading" >Good use example 1: separate concern</h2>



<p>Let’s take an example of fully separated logic.</p>



<p>The following is a true example of a login/register process that I experienced in my app:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("session_id", "data", allow_duplicate=True),
    [
        Input("login_button", "n_clicks"),
        State("login_email", "value"),
        State("login_password", "value"),
    ],
    prevent_initial_call=True,
)
def user_identification(button_n_clicks, username, password):
    """This callback handles the login of a user given a username and password """
    # ... handle login
    session_id = uuid.uuid4()
    return session_id

@callback(
    Output("session_id", "data", allow_duplicate=True),
    [
        Input("register_button", "n_clicks"),
        State("register_email", "value"),
        State("register_password", "value"),
        State("register_lastname", "value"),
        State("register_firstname", "value"),
        State("register_company", "value"),
        State("register_tel", "value"),
    ],
    prevent_initial_call=True,
)
def user_registration(button_n_clicks, email, password, lastname, firstname, company, telephone):
    """This callback handles the registration of a new user"""
    # ... handle registration
    session_id = uuid.uuid4()
    return session_id
</code></pre>



<p>Both callbacks handle multiple inputs (<em>email, password, firstname, lastname</em>, etc.) and they return a <code>session_id</code>, that is stored in a session <code>dcc.Store</code>.</p>



<p>This <code>session_id</code> will then trigger another set of callbacks that are related to the loading of an user: displaying its name, loading its preferences, etc.</p>



<p>What’s interesting about this example is that it actually benefited from using <code>allow_duplicate</code>. The two callbacks are different and process a different set of inputs, thus, it makes sense to have two separate callbacks.</p>



<p>This is what it looked like if merged into one large callback:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("session_id", "data"),
    [
        Input("login_button", "n_clicks"),
        Input("register_button", "n_clicks"),
    ],
    [
        State("login_email", "value"),
        State("login_password", "value"),
        State("register_email", "value"),
        State("register_password", "value"),
        State("register_lastname", "value"),
        State("register_firstname", "value"),
        State("register_company", "value"),
        State("register_tel", "value"),
    ],
    prevent_initial_call=True,
)
def user_identification_or_registration(login_n_clicks, register_n_clicks, username, password1, email, password2, lastname, firstname, company, telephone):
    """This callback handle the login of a user given a username and password """
    if ctx.triggered_id == "login_button":
        # ... handle login
        session_id = uuid.uuid4()
        return session_id
    elif ctx.triggered_id == "register_button":
        # ... handle registration
        session_id = uuid.uuid4()
        return session_id
</code></pre>



<p>But large callbacks also have their own drawbacks: harder to maintain, multiple inputs to handle, etc… Plus, we would be sending unnecessary information with every callback trigger—making it less efficient</p>



<p>In this case, <code>allow_duplicate</code> proves really helpful in reducing the complexity and making a more readable and maintainable code.</p>



<p class="callout">Prior the introduction of <code>allow_duplicate</code>, I actually handled this with two <a href="http://dcc.Store">dcc.Store</a> : <code>session_id_login</code> and <code>session_id_register</code>, which were both triggering a callback updating a single output: <code>session_id</code>. They were used as temporary storage —but that was less efficient and required one round trip to the server as well as one unecessary callback execution.</p>



<h2 id='good-use-example-2-partial-update'  id="boomdevs_6" class="wp-block-heading" >Good use example 2: partial update</h2>



<p>The previous example illustrated how <code>allow_duplicate</code> can help separate callbacks when logic is different. It’s more about callback designing, but there are solutions to do it without <code>allow_duplicate</code>.</p>



<p>The following example <strong>could not be possible</strong> without the help of <code>allow_duplicate</code> .</p>



<p>Imagine you have a figure. And you want to update only the title of a figure without redrawing the whole figure. The code would look like:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output('graph', 'figure', allow_duplicate=True),
    Input('dropdown', 'value'),
    prevent_initial_call=True,
)
def update_graph(value):
    # Download very large data from a server
    df = pd.read_csv('https://.../data.csv')
    fig = px.line(df, x='date', y='value', color='category')
    return fig

@callback(
    Output('graph', 'figure'),
    Input('title_input', 'value'),
    prevent_initial_call=True,
)
def update_title(title):
    # Just modify the title with Patch()
    fig = Patch()
    fig['layout']['title'] = title
    return fig

</code></pre>



<p>In <code>update_graph</code>, we load a large dataset and return a figure. In the other callback <code>update_title</code>, we only update the title of the figure and return it, using <code>Patch()</code>.</p>



<p>What’s wonderful here is that the use of <code>allow_duplicate</code> and <code>Patch()</code> conjointly helped updating the figure without the need to reload the whole data and create the entire figure again.</p>



<p>It’s a more efficient solution, that would translate in a more responsive application and a better user experience.</p>



<p class="callout">Lean more about partial updates here: <a href="https://dash.plotly.com/partial-properties">https://dash.plotly.com/partial-properties</a></p>



<p>In this case, <code>allow_duplicate</code> enabled something that wasn’t possible before.</p>



<h2 id='bad-use-example-1-harder-to-track'  id="boomdevs_7" class="wp-block-heading" >Bad use example 1: harder to track</h2>



<p>The following is a fictitious bad example, and/or bad callback design, where two callbacks update the same container:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("results_container", "children", allow_duplicate=True),
    Input("search_button", "n_clicks"),
    prevent_initial_call=True,
)
def search_results(n_clicks):
    """This callback searches for results when the search button is clicked"""
    if n_clicks is None:
        raise PreventUpdate
    
    # some processing
    
    return [
        html.Div("Search results from button click"),
        html.Div([html.Span(f"Result {i}") for i in range(5)])
    ]

@callback(
    Output("results_container", "children", allow_duplicate=True),
    Input("filter_dropdown", "value"),
    prevent_initial_call=True,
)
def filter_results(filter_value):
    """This callback filters results when dropdown value changes"""
    if filter_value is None:
        raise PreventUpdate
    
    # some processing
    
    return [
        html.Div(f"Filtered results for: {filter_value}"),
        html.Div([html.Span(f"Filtered item {i}") for i in range(3)])
    ]
</code></pre>



<p>Both <code>search_results</code> and <code>filter_results</code> can be triggered . If both are triggered at the same time, we will have either one result or the other (the last callback being executed), which is misleading.</p>



<p>The issue here is that we’re using one single container, <code>result_container</code>, to display two different solution. And <code>allow_duplicate=True</code> made it possible.</p>



<p class="callout">To be clear, the above code will work. It’s just that in my opinion, this is not a good use of <code>allow_duplicates</code> for the sake of maintainability and callback logic.</p>



<p>Instead, it would have been better to just have two separate containers (e.g. a new <code>search_result_container</code> and <code>filter_result_container</code>) or to merge the logic into one callback. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 id='bad-example-2-race-condition'  id="boomdevs_8" class="wp-block-heading" >Bad example 2: race condition</h2>



<p>This example will illustrate the <a href="https://en.wikipedia.org/wiki/Race_condition">race condition</a> problem. </p>



<p>Imagine you need to store the number of clicks on two buttons in a list. The first button increments the first count, and the second button updates the second count:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Our dcc store is like this:
results_store = [0, 0]

@callback(
    Output("results_store", "data", allow_duplicate=True),
    Input("button1", "n_clicks"),
    State("results_store", "data"),
    prevent_initial_call=True,
)
def update_store_from_button1(n_clicks, current_data):
    """ Store the number of clicks on button1"""
    # Sleep randomly to simulate network latency
    time.sleep(random.uniform(0.5, 2))

		# Update button 1 count
    current_data[0] = n_clicks
    return current_data

@callback(
    Output("results_store", "data", allow_duplicate=True),
    Input("button2", "n_clicks"),
    State("results_store", "data"),
    prevent_initial_call=True,
)
def update_store_from_button2(n_clicks, current_data):
    """ Store the number of clicks on button2"""
    # Sleep randomly to simulate network latency
    time.sleep(random.uniform(0.5, 2))
    
    # Update button 2 count
    current_data[1] = n_clicks
    return current_data
</code></pre>



<p>Each callback is triggered by its button, uses the state of <code>current_data</code> and returns the updated value.</p>



<p>Let’s give an example:</p>



<ul class="wp-block-list">
<li>click on button1 → … add one to the number of clicks (current data state is <code>[0, 0]</code>) … → the callback returns <code>[1, 0]</code></li>



<li>then click on button2 → … add one to the number of clicks (current data state is <code>[1, 0]</code>) … → the callback returns <code>[1, 1]</code></li>
</ul>



<p>This is all good because we click and wait for the callback to be fully executed. Now, what if we click to both buttons at the same time ?</p>



<ul class="wp-block-list">
<li>click on button1 → … add one to the number of clicks (current data state is <code>[1, 1]</code>) … → the callback returns <code>[2, 1]</code></li>



<li>click on button2 → … add one to the number of clicks (current data state is <code>[1, 1]</code>) … → the callback returns <code>[1, 2]</code></li>
</ul>



<p>Depending on which callback finishes first, we’ll get different results: [2, 1] or [1, 2].</p>



<p>You can try a live demo below (or click <a href="https://scripts.dash-resources.com/sequential/app_btns.py/">here</a>):</p>



<iframe loading="lazy" src="https://scripts.dash-resources.com/sequential/app_btns.py/" width="100%" height="400" frameBorder="0"></iframe>



<p class="callout">In this example, I chose to simulate network latency with <code>time.sleep()</code> so that the problem is emphasized. When testing locally, you might not notice the problem—but it can become obvious once deployed to production.</p>



<p>In this case, a better solution would have been to</p>



<ul class="wp-block-list">
<li>use <code>Patch()</code> to be sure we do not rely on states</li>



<li>use different <code>dcc.Store</code> to store the two information.</li>
</ul>



<p>I hope this example helped to understand race conditions. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Again, <a href="https://dash-resources.com/avoiding-desynchronized-state-in-dash-sequential-callbacks-patch-and-better-design-patterns/">this other article</a> also illustrates this problem.</p>



<h2 id='conclusion'  id="boomdevs_9" class="wp-block-heading" >Conclusion</h2>



<p>I hope this article helped you to understand what are the good and bad practices with this powerful option, <code>allow_duplicate</code>.</p>



<p>If you’re unsure when to use it, here are a few rules I’ve come up with:</p>



<div class="wp-block-group is-nowrap is-layout-flex wp-container-core-group-is-layout-ad2f72ca wp-block-group-is-layout-flex">
<ul class="wp-block-list">
<li>If you have two separate callback logic that depend on which inputs are triggering the callback (i.e. with <code>ctx.triggered</code>), and have a different set of inputs, then creating two callbacks with <code>allow_duplicate</code> may be a good solution <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>If the callbacks (or more) are never supposed to be triggered at the same time, you are also safe to use <code>allow_duplicate</code> but consider using different outputs if possible <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>



<li>If you need to update partially a component (e.g. a figure) using Patch, then <code>allow_duplicate</code> is your only option. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></li>
</ul>
</div>



<p>I you have questions or want to join the discussion, I created at topic here: <a href="https://community.plotly.com/t/the-dark-side-of-allow-duplicates-making-callbacks-sequential/91459">plotly forum</a>.</p>



<p>— Happy coding! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p></p>
<p>L’article <a href="https://dash-resources.com/how-to-use-allow_duplicate-good-and-bad-practices/">How to use allow_duplicate (good and bad practices)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>The dark side of allow_duplicate &#038; making callbacks sequential</title>
		<link>https://dash-resources.com/avoiding-desynchronized-state-in-dash-sequential-callbacks-patch-and-better-design-patterns/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Tue, 25 Mar 2025 16:29:47 +0000</pubDate>
				<category><![CDATA[Intermediate level]]></category>
		<category><![CDATA[callbacks]]></category>
		<category><![CDATA[storage]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=828</guid>

					<description><![CDATA[<p>I was inspired by this question on the Plotly community forum to talk about sequential callbacks and, more broadly, callback design with or without allow_duplicate. [...]</p>
<p>L’article <a href="https://dash-resources.com/avoiding-desynchronized-state-in-dash-sequential-callbacks-patch-and-better-design-patterns/">The dark side of allow_duplicate &amp; making callbacks sequential</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>I was inspired by <a href="https://community.plotly.com/t/execution-of-callbacks-writting-to-a-single-dcc-store/91448/3">this question</a> on the Plotly community forum to talk about sequential callbacks and, more broadly, callback design with or without <code>allow_duplicate</code>.</p>



<p>Ensuring that some information is stored without desynchronization issues with callbacks is also a problem that I encountered a few times when developing Dash applications. As a result, I want to discuss here a few solutions and when to approach them.</p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#the-problem">The problem</a>
      </li>
      <li>
        <a href="#solution-1-chaining-callbacks">Solution 1: chaining callbacks</a>
      </li>
      <li>
        <a href="#solution-2-rewriting-callbacks">Solution 2: rewriting callbacks</a>
      </li>
      <li>
        <a href="#solution-3-using-intermediary-stores">Solution 3: using intermediary stores</a>
      </li>
      <li>
        <a href="#solution-4-using-partial-update">Solution 4: using Partial Update</a>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>Let&#8217;s dive in <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 id='the-problem'  id="boomdevs_1" class="wp-block-heading" >The problem</h2>



<p>Imagine you have a <code>dcc.Store("user_data")</code> that stores something like <code>{"firstname": "John", "lastname": "Doe", "age": 50, ...}</code>. This Store is set via multiple inputs: first_name, last_name, age, …It is a large dictionary and you might have multiple callbacks updating it.</p>



<p>To update this <code>user_data</code> from many inputs, you can set <code>allow_duplicate=True</code>. That way, you are not restricted to only one callback for this output. You just need to get the full <code>user_data</code> back each time (as a State), modify it, and return it.</p>



<p>The code would be something like:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("user_data", "data", allow_duplicate=True),
    Input("first_name", "value"),
    State("user_data", "data"),
    prevent_initial_call=True,
)
def update_first_name(first_name, user_data):

    # Just update user_data with the first name
    if first_name: 
        user_data["first_name"] = first_name
        return user_data

    raise PreventUpdate

@callback(
    Output("user_data", "data", allow_duplicate=True),
    Input("last_name", "value"),
    State("user_data", "data"),
    prevent_initial_call=True,
)
def update_last_name(last_name, user_data):

    # Just update user_data with the last name
    if last_name: 
        user_data["last_name"] = last_name
        return user_data

    raise PreventUpdate

# ... other callbacks</code></pre>



<p>For the moment, everything is fine. But a problem starts appearing if you trigger all inputs at the same time, e.g. to set initial values:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># A callback to simulate the desynchro problem.
# As it triggers the first name and last name input as the same time
@app.callback(
    Output("first_name", "value"),
    Output("last_name", "value"),
    Input("load_data", "n_clicks"),
)
def load_data(n_clicks):
    if n_clicks:
        return "Mickael", "Jackson"
    raise PreventUpdate</code></pre>



<p>A de-synchronization problem appears, between the inputs and the result in the <code>user_dict</code> memory store. The inputs have the updated values, not the stored memory <code>user_dict</code>.</p>



<p>You can try it below (or <a href="https://scripts.dash-resources.com/sequential/app.py/">click here</a>):  </p>



<ul class="wp-block-list">
<li>if you enter &#8220;john&#8221; and &#8220;doe&#8221;, then everything gets updated correctly.</li>



<li>then click on &#8220;load&#8221; data. the <code>user_data</code> should contains firstname = &#8220;mickael&#8221; and lastname = &#8220;jackson&#8221;, but instead some discrepency appears.</li>
</ul>



<iframe loading="lazy" src="https://scripts.dash-resources.com/sequential/app.py/" width="100%" height="600" frameBorder="0"></iframe>



<p><strong>Why this problem appears.</strong></p>



<p>Both callbacks are run at the same time. If <code>update_first_name</code> is triggered at the same time <code>update_last_name</code> is triggered, the state value of <code>user_data</code> do not contains the updated first name. It also works the way around, which explains that sometimes we don&#8217;t have the first name, and sometimes the last name. it depends which callback is fired first.</p>



<p>And having <code>Input("user_data", "value")</code> instead of <code>State("user_data", "value")</code> might be possible in some cases, but you would end up with some weird behavior due to the circular pattern. If you have 5 or 10 callbacks following the same scheme, your app will basically blow up because of all the callbacks triggered in all directions. </p>



<p>Here is a video example with 4 inputs:</p>



<figure class="wp-block-video"><video height="1266" style="aspect-ratio: 1964 / 1266;" width="1964" controls src="https://dash-resources.com/wp-content/uploads/2025/03/dash-too-many-circular-callbacks.mp4"></video><figcaption class="wp-element-caption">Illustration: 4 inputs with circular callbacks. Each input modification generates a lot of callback requests.</figcaption></figure>



<p><strong>Notice how many callbacks </strong>are triggered with a single change in one input (1 HTTP request = 1 callback)! Because of the circular scheme, callbacks get triggered unecessarily.</p>



<p>The solution can be to ensure that callbacks are executed sequentially: first <code>update_first_name</code>, and then <code>update_last_name</code>, with the updated value of <code>user_data</code>. That&#8217;s actually what it &#8220;seems to be&#8221;, but the real solution is to think about the problem differently.</p>



<p>But let&#8217;s see why.</p>



<h2 id='solution-1-chaining-callbacks'  id="boomdevs_2" class="wp-block-heading" >Solution 1: chaining callbacks</h2>



<p>How do we make callbacks sequential?</p>



<p>Chaining is the simplest form of sequential callbacks. In that case, you just need to connect one of the outputs of the first callback as an input for the second callback (A -&gt; o -&gt; B -&gt; o).</p>



<p>But as I said previously, we cannot set <code>user_data</code> as an input for the second callback (because of circular dependencies).</p>



<p>Instead, we can use an intermediary <code>dcc.Store</code>, that will just be used as a trigger. I think a timestamp is a good fit for this purpose:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("user_data", "data", allow_duplicate=True),
    Output("intermediary_timestamp", "data"),
    Input("first_name", "value"),
    State("user_data", "data"),
    prevent_initial_call=True,
)
def update_first_name(first_name, user_data):

    # Just update user_data with the first name
    if first_name: 
        user_data["first_name"] = first_name
        return user_data, time.time()

    raise PreventUpdate

@callback(
    Output("user_data", "data", allow_duplicate=True),
    Input("last_name", "value"),
    Input("intermediary_timestamp", "data"),
    State("user_data", "data"),
    prevent_initial_call=True,
)
def update_last_name(last_name, timestamp, user_data):

    # Just update user_data with the last name
    if last_name: 
        user_data["last_name"] = last_name
        return user_data

    raise PreventUpdate</code></pre>



<p>In this example, the <code>update_first_name</code> callback will return the <code>user_data</code> and a timestamp. The dash-renderer will know that it has to wait before triggering <code>update_last_name</code> because <code>intermediary_timestamp</code> must be ready before executing it.</p>



<p class="callout"><strong>Pro-tip:</strong> we could have used the property <code>modified_timestamp</code> of our <code>user_data</code> dcc.Store component as an input. i.e., replacing <code>Input("intermediary_timestamp", "data")</code> by <code>Input("user_data", "modified_timestamp")</code>. That works too and we don&#8217;t even need the intermediary dcc.Store <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 id='solution-2-rewriting-callbacks'  id="boomdevs_3" class="wp-block-heading" >Solution 2: rewriting callbacks</h2>



<p>But wait wait wait… Should we really need to chain callbacks?<br>Often the best option is to handle the processing of the two callbacks inside the same callback.</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("user_data", "data"),
    Input("first_name", "value"),
    Input("last_name", "value"),
    # .. other possible inputs
    State("user_data", "data"),
)
def update_user_data(first_name, user_data):

    # Just update user_data with the first name
    if first_name: 
        user_data["first_name"] = first_name
    if last_name:
        user_data["last_name"] = last_name
    # .. other possible values

    return user_data</code></pre>



<p>And that solves the synchronization problem.</p>



<p>However, it can happen that you don&#8217;t want to get the value from the input if it wasn&#8217;t really triggered.</p>



<p>Hopefully, there is a way to filter which input was really triggered, using <code>ctx.triggered_prop_ids</code> (<a href="https://dash.plotly.com/advanced-callbacks#determining-which-input-has-fired-with-dash.callback_context">documentation link</a>). Let&#8217;s see an example:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def slow_processing(user_data):
    # Simulate some long processing, e.g. requesting a database
    time.sleep(4)
    return "Some information"


@callback(
    Output("user_data", "data"),
    Input("first_name", "value"),
    Input("last_name", "value"),
    Input("info_button", "n_clicks"),
    # .. other possible inputs
    State("user_data", "data"),
)
def update_user_data(first_name, last_name, n_clicks, user_data):

    # Identify which inputs were triggered
    first_name_triggered = "first_name" in ctx.triggered_prop_ids.values()
    last_name_triggered = "last_name" in ctx.triggered_prop_ids.values()
    button_triggered = "info_button" in ctx.triggered_prop_ids.values()

    # Update user_data accordingly
    if first_name_triggered and first_name: 
        user_data["first_name"] = first_name
    if last_name_triggered and last_name:
        user_data["last_name"] = last_name

    if button_triggered and n_clicks:
        # An information that takes time to retrieve
        # You want to compute it only if button was *really* triggered
        info = slow_processing(user_data)  
        user_data["info"] = info

    # .. other possible inputs being hanled

    return user_data</code></pre>



<p>The good thing is that this solution is scalable: we can continue adding inputs and keys to our <code>user_data</code> easily. And we get rid of <code>allow_duplicate=True</code> which is useful but also leads to &#8220;callback bad design&#8221;. I&#8217;ll come back to this later.</p>



<h2 id='solution-3-using-intermediary-stores'  id="boomdevs_4" class="wp-block-heading" >Solution 3: using intermediary stores</h2>



<p>Part of the problem that we have here is the use of one memory store for multiple information. </p>



<p>So instead of trying to store everything inside the same callback, we could actually split the <code>user_data</code> dict into as many stores as required: <code>user_first_name</code>, <code>user_last_name</code>, <code>user_age</code>, <code>user_info</code>, etc.</p>



<p>Then, we would have to merge all this information into one callback:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">
app.layout = [
    # ...
    dcc.Store(id="user_first_name"),
    dcc.Store(id="user_last_name"),
    dcc.Store(id="user_age"),
    dcc.Store(id="user_info"),
    # ... other values
]

@callback(
    Output("user_first_name", "data"),
    Input("first_name", "value")
)
def update_first_name(first_name):
    if first_name: 
        return first_name
    raise PreventUpdate

@callback(
    Output("user_last_name", "data"),
    Input("last_name", "value")
)
def update_last_name(last_name):
    if last_name: 
        return last_name
    raise PreventUpdate

@callback(
    Output("user_age", "data"),
    Input("age", "value")
)
def update_age(age):
    if age: 
        return age
    raise PreventUpdate

@callback(
    Output("user_info", "data"),
    Input("info_button", "n_clicks"),
    State("user_data", "data")
)
def update_info(n_clicks, user_data):
    if n_clicks:
        info = slow_processing(user_data) 
        return info
    raise PreventUpdate

# ... other callbacks 

# then we update the user_data:
@callback(
    Output("user_data", "data"),
    Input("user_first_name", "data"),
    Input("user_last_name", "data"),
    Input("user_age", "data"),
    Input("user_info", "data"),
    prevent_initial_call=True,
)
def update_user_data(first_name, last_name, age, info):
    return {
        "first_name": first_name,
        "last_name": last_name,
        "age": age,
        "info": info,
        # ... other properties and values?
    }</code></pre>



<p>If all callbacks are triggered at the same time, <code>update_user_data</code> will be the last one triggered, and all input values will be filled before it can run. That&#8217;s a good way to make callbacks <em>sequential</em>.</p>



<p>The <strong>good</strong> thing about this solution is that we can get rid of <code>allow_duplicate=True</code> too. The <strong>bad</strong> thing is that it is poorly scalable: we would need to add as many stores and callbacks as we have keys to update in <code>user_data</code>.</p>



<p>As you can see, the initial problem was made possible because of <code>allow_duplicate=True</code>. If it wasn&#8217;t an option, we might have used this solution in the first place. And even if it&#8217;s a verbose solution, it&#8217;s a good simple solution. <strong><code>allow_duplicate=True</code> is a fortunate option to use in some cases, but it often leads to bad callback design.</strong></p>



<p class="callout"><strong>Pro-tip:</strong> 99% of the time you don&#8217;t need <code>allow_duplicate=True</code>. Try to avoid it as much as possible. It can creates callback mess.</p>



<h2 id='solution-4-using-partial-update'  id="boomdevs_5" class="wp-block-heading" >Solution 4: using Partial Update</h2>



<p>The above solutions will rely on the fact that we get <code>user_data</code> as a <code>State</code>.<br>But what if <code>user_data</code> is very large? Dash callbacks are HTTP requests, so a large input or state would result in a large HTTP request. Depending on the user&#8217;s bandwidth, it can take time.</p>



<p>Instead, we could use <code>Patch()</code> (<a href="https://dash.plotly.com/partial-properties">documentation link</a>) to only update one key at a time, not the whole <code>user_data</code> dictionary.</p>



<p>Let&#8217;s see the code:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import Patch

@callback(
    Output("user_data", "data", allow_duplicate=True),
    Input("first_name", "value"),
    prevent_initial_call=True,
)
def update_first_name(first_name):

    # Just update user_data with the first name
    if first_name: 
        user_data = Patch()
        user_data["first_name"] = first_name
        return user_data

    raise PreventUpdate

@callback(
    Output("user_data", "data", allow_duplicate=True),
    Input("last_name", "value"),
    prevent_initial_call=True,
)
def update_last_name(last_name):

    # Just update user_data with the last name
    if last_name: 
        user_data = Patch()
        user_data["last_name"] = last_name
        return user_data

    raise PreventUpdate</code></pre>



<p>And that&#8217;s it. :-). <strong>So, how does it works ?</strong> </p>



<ul class="wp-block-list">
<li>In the other solutions, Dash will send the whole <code>user_data</code> to be modified in a callback, gets the new full value, and updates in its storage.  </li>



<li>With Patch(), Dash will just send the input and get just the piece of information that was modified. It then updates the full object in its storage with the new piece of information. </li>



<li>Unless two callbacks modifies the same piece of information (<em>come&#8217;on, you want problems</em>), this solution do not require sequential execution.</li>
</ul>



<p>It&#8217;s maybe the most powerful solution, especially if <code>user_data</code> is very large. It can work with solution n°1, n°2 and n°3. We still use <code>allow_duplicate</code>, but it is not a problem anymore.</p>



<h2 id='conclusion'  id="boomdevs_6" class="wp-block-heading" >Conclusion</h2>



<p>So what&#8217;s the best solution? As always, it depends on your use case.</p>



<p>But here key takeaways:</p>



<ul class="wp-block-list">
<li>If you can, try to avoid using <code>allow_duplicate=True</code> and have one callback update one output</li>



<li>If you can, try to merge multiple callbacks into one callback. It&#8217;s more efficient, and you will de-facto solve the synchronization problem</li>



<li>If you have large data, <code>Patch()</code> is probably the best solution.</li>
</ul>



<p>I hope this article helped you learn about different approaches and how things can get complex with Dash callbacks. Feel free to ask questions or join the discussion here. </p>



<p>Happy coding! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>L’article <a href="https://dash-resources.com/avoiding-desynchronized-state-in-dash-sequential-callbacks-patch-and-better-design-patterns/">The dark side of allow_duplicate &amp; making callbacks sequential</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		<enclosure url="https://dash-resources.com/wp-content/uploads/2025/03/dash-too-many-circular-callbacks.mp4" length="558049" type="video/mp4" />

			</item>
		<item>
		<title>Dash plotly vs. Streamlit: what are the differences?</title>
		<link>https://dash-resources.com/dash-plotly-vs-streamlit-what-are-the-differences/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Thu, 13 Mar 2025 21:19:42 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=812</guid>

					<description><![CDATA[<p>Dash and Streamlit are two Python frameworks that can be used to build data applications and interactive dashboards. But what makes them different? And which [...]</p>
<p>L’article <a href="https://dash-resources.com/dash-plotly-vs-streamlit-what-are-the-differences/">Dash plotly vs. Streamlit: what are the differences?</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Dash and Streamlit are two Python frameworks that can be used to build data applications and interactive dashboards. But what makes them different? And which one should you pick for your projects?</p>



<p>You will find in this article a short description of both frameworks and <strong>two code examples</strong> of use cases that suit the former and the latter. At the end, we summarize the comparison for Dash vs. Streamlit in comprehensive tables. Let&#8217;s dive in! </p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#about-streamlit">About Streamlit</a>
      </li>
      <li>
        <a href="#about-dash">About Dash</a>
      </li>
      <li>
        <a href="#code-comparison">Code comparison</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#where-streamlit-is-better">Where Streamlit is better</a>
          </li>
          <li class="last">
            <a href="#where-dash-is-better">Where Dash is better</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#dash-plotly-vs-streamlit-table-comparison">Dash plotly vs. Streamlit: table comparison</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#web-application-features">Web Application Features</a>
          </li>
          <li>
            <a href="#data-visualization-interactivity">Data Visualization &amp; Interactivity</a>
          </li>
          <li class="last">
            <a href="#development-deployment">Development &amp; Deployment</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#my-personal-opinion">My personal opinion</a>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p class="callout"><strong>TL;DR:</strong>&nbsp;Streamlit is very good for building tools, Dash is very good for building apps.</p>



<h2 id='about-streamlit'  id="boomdevs_1" class="wp-block-heading" ><strong>About Streamlit</strong></h2>


<div class="wp-block-image">
<figure class="aligncenter is-resized"><img decoding="async" src="https://streamlit.io/images/brand/streamlit-logo-primary-colormark-darktext.png" alt="" style="width:318px;height:auto"/></figure>
</div>


<p><a href="https://streamlit.io/">Streamlit</a>, is designed for creating data apps with minimal effort, focusing on simplicity and fast development cycles. Its key strengths are:</p>



<ul class="wp-block-list">
<li>Fast prototyping of data applications</li>



<li>Simple, declarative syntax that follows a linear execution flow</li>



<li>Built-in widgets and layout components</li>



<li>Automatic reactivity &#8211; the entire app reruns on any user interaction</li>



<li>Effortless deployment through Streamlit Cloud</li>
</ul>



<p class="callout"><span style="text-decoration: underline;"><strong>Example</strong>:</span> Streamlit is an excellent choice if you want to quickly transform a Jupyter notebook into an interactive web application or create internal tools for data exploration without spending time on frontend development.</p>



<p>Streamlit has grown significantly in popularity due to its simplicity and ease of use. It&#8217;s worth mentioning that deploying a Streamlit app and making it accessible on the internet is super easy with Streamlit Cloud (just a few clicks).</p>



<h2 id='about-dash'  id="boomdevs_2" class="wp-block-heading" ><strong>About Dash</strong></h2>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="858" height="600" src="https://dash-resources.com/wp-content/uploads/2025/03/image-2.png" alt="" class="wp-image-814" style="width:165px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2025/03/image-2.png 858w, https://dash-resources.com/wp-content/uploads/2025/03/image-2-300x210.png 300w, https://dash-resources.com/wp-content/uploads/2025/03/image-2-768x537.png 768w" sizes="auto, (max-width: 858px) 100vw, 858px" /></figure>
</div>


<p><a href="https://plotly.com/dash/">Dash Plotly</a>, on the other hand, is specifically designed for building analytical web applications and data visualization dashboards. Its key strengths are:</p>



<ul class="wp-block-list">
<li>Creating interactive web applications 100% in Python</li>



<li>Real-time data updates and callbacks</li>



<li>Deep integration with Plotly&#8217;s visualization library</li>



<li>Building data-heavy applications for data scientists and analysts</li>



<li>Highly customizable layouts and components</li>
</ul>



<p class="callout"><strong><span style="text-decoration: underline;">Example:</span></strong> Dash is an excellent choice if want to build a data web application that requires complex interactions between components or if you need pixel-perfect control over your visualizations, such as financial analytics platforms or enterprise-grade dashboards.</p>



<p>Dash is a growing framework with a vibrant community. Recent updates have expanded Dash beyond just data visualization, moving towards becoming a more complete Python web framework with features like built-in URL routing, authentication, and advanced component libraries. This evolution makes Dash increasingly suitable for building full-featured web applications entirely in Python.</p>



<h2 id='code-comparison'  id="boomdevs_3" class="wp-block-heading" ><strong>Code comparison</strong></h2>



<p>I assume that if you read this, you are somehow familiar with Python <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" />. So let&#8217;s take a look at some tangible examples, with code. That will be much more illustrative.</p>



<h3 id='where-streamlit-is-better'  id="boomdevs_4" class="wp-block-heading" ><strong>Where Streamlit is better</strong></h3>



<p>Streamlit truly shines in its simplicity and speed of development. Here&#8217;s how easy it is to create a simple data app:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">###### STREAMLIT EXAMPLE ######
import streamlit as st
import pandas as pd
import plotly.express as px

st.title("Simple Data Explorer")

# Load data
uploaded_file = st.file_uploader("Upload CSV", type=['csv'])
if uploaded_file:
    df = pd.read_csv(uploaded_file)

# Simple controls
    x_column = st.selectbox("X axis", df.columns)
    y_column = st.selectbox("Y axis", df.columns)

# Create and display plot
    fig = px.scatter(df, x=x_column, y=y_column)
    st.plotly_chart(fig)

# Show data summary
    st.write("Data Summary:", df.describe())


# If you want to run this example, open the terminal and:
# pip install streamlit pandas plotly
# streamlit run streamlit_app.py
</code></pre>



<p>This concise code creates a complete app with file uploading, interactive controls, visualization, and data summary:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="984" height="1388" src="https://dash-resources.com/wp-content/uploads/2025/03/image-3.png" alt="" class="wp-image-815" srcset="https://dash-resources.com/wp-content/uploads/2025/03/image-3.png 984w, https://dash-resources.com/wp-content/uploads/2025/03/image-3-213x300.png 213w, https://dash-resources.com/wp-content/uploads/2025/03/image-3-726x1024.png 726w, https://dash-resources.com/wp-content/uploads/2025/03/image-3-768x1083.png 768w" sizes="auto, (max-width: 984px) 100vw, 984px" /><figcaption class="wp-element-caption">Illustration: the resulting streamlit app after uploading a simple csv file and selecting X and Y columns to plot.  </figcaption></figure>



<p>To be honest, how straightforward and readable is this code is blows my mind <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f92f.png" alt="🤯" class="wp-smiley" style="height: 1em; max-height: 1em;" />. That&#8217;s difficult to do simpler, <strong>well done Streamlit!</strong></p>



<p>Each line does exactly one thing: getting data from the user (reading the file, or the dropdown input) or udpating a visual (chart or table). One thing is handled by one function, keeping things clear and easy to understand. </p>



<p>Now let&#8217;s see the equivalent in Dash:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">###### DASH EQUIVALENT ######
import io
import pandas as pd
import plotly.express as px
import base64
import dash_bootstrap_components as dbc
from dash import Dash, dcc, html, Input, Output, State, no_update

app = Dash(
    __name__, 
    external_stylesheets=[dbc.themes.BOOTSTRAP], 
    suppress_callback_exceptions=True
)

# Custom CSS for the upload component
upload_style = {
    'width': '100%',
    'height': '60px',
    'lineHeight': '60px',
    'borderWidth': '1px',
    'borderStyle': 'dashed',
    'borderRadius': '5px',
    'textAlign': 'center',
    'margin': '10px 0',
    'backgroundColor': '#fafafa',
    'cursor': 'pointer',
    'transition': 'border .24s ease-in-out',
}

# Define the full layout upfront
app.layout = dbc.Container([
    html.H1("Simple Data Explorer", className="text-center my-4"),

    # Upload component
    dcc.Upload(
        id='upload-data',
        children=html.Div([
            'Drag and Drop or ',
            html.A('Select CSV', className="text-primary")
        ]),
        style=upload_style,
        multiple=False
    ),

    # Store for the dataframe
    dcc.Store(id='dataframe', storage_type='memory'),

    # Analysis section (hidden by default)
    dbc.Container([
        dbc.Row([
            dbc.Col([
                html.Label("X axis:", className="mt-3"),
                dcc.Dropdown(id='x-column', options=[], value=None),
                html.Label("Y axis:", className="mt-3"),
                dcc.Dropdown(id='y-column', options=[], value=None),
            ], width=12)
        ]),

        html.Div(id='graph-container', className="my-4"),

        html.H3("Data Summary", className="mt-4"),
        html.Div(id='data-summary')
    ], id='analysis-container', style={'display': 'none'})

], fluid=False, style={'maxWidth': '800px'})

@app.callback(
    [Output('dataframe', 'data'),
     Output('analysis-container', 'style'),
     Output('x-column', 'options'),
     Output('y-column', 'options')],
    Input('upload-data', 'contents'),
    State('upload-data', 'filename')
)
def process_upload(contents, filename):
    if contents is None:
        return None, {'display': 'none'}, [], []

    # Decode the base64 encoded content
    content_type, content_string = contents.split(',')
    decoded = base64.b64decode(content_string)

    try:
        # Read the CSV file
        df = pd.read_csv(io.StringIO(decoded.decode('utf-8')), sep=";")
        options = [{'label': i, 'value': i} for i in df.columns]
        return (
            df.to_json(),
            {'display': 'block'},
            options,
            options
        )
    except Exception as e:
        return None, {'display': 'none'}, [], []

@app.callback(
    Output('data-summary', 'children'),
    Input('dataframe', 'data')
)
def update_summary(json_data):
    if not json_data:
        return no_update
    
    df = pd.read_json(json_data)
    return dbc.Table.from_dataframe(
        df.describe().reset_index(),
        striped=True,
        bordered=True,
        hover=True,
        responsive=True
    )

@app.callback(
    Output('graph-container', 'children'),
    [Input('x-column', 'value'),
     Input('y-column', 'value'),
     Input('dataframe', 'data')]
)
def update_graph(x_col, y_col, json_data):
    if not json_data or not x_col or not y_col:
        return html.Div("Please select X and Y columns")

    df = pd.read_json(json_data)
    fig = px.scatter(df, x=x_col, y=y_col)
    return dcc.Graph(figure=fig)

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

# If you want to run the app, do:
# pip install dash dash-bootstrap-components pandas plotly
# python dash_app.py</code></pre>



<p>Which is giving the following app:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1488" height="1668" src="https://dash-resources.com/wp-content/uploads/2025/03/image-8.png" alt="" class="wp-image-821" srcset="https://dash-resources.com/wp-content/uploads/2025/03/image-8.png 1488w, https://dash-resources.com/wp-content/uploads/2025/03/image-8-268x300.png 268w, https://dash-resources.com/wp-content/uploads/2025/03/image-8-913x1024.png 913w, https://dash-resources.com/wp-content/uploads/2025/03/image-8-768x861.png 768w, https://dash-resources.com/wp-content/uploads/2025/03/image-8-1370x1536.png 1370w" sizes="auto, (max-width: 1488px) 100vw, 1488px" /><figcaption class="wp-element-caption">Illustration: the resulting streamlit app after uploading a simple csv file and selecting X and Y columns to plot.  </figcaption></figure>



<p>As you can see, the Dash implementation requires way more code, and more complex code. <strong>Why such a difference ? </strong></p>



<p>In Dash, the components are not &#8216;alive&#8217; by themselves, meaning you need to create explicit callbacks with inputs and outputs to create interactivity —which Streamlit provides automatically:</p>



<ul class="wp-block-list">
<li>Instead of using <code>st.file_uploader</code>, we need to handle the upload of the file in a callback by reading the base64 content ;</li>



<li>Instead of waiting for <code>x_column</code> and <code>y_column</code> to be filled and create the graph, we need to create a callback that will use them as input and create a graph as an output;</li>
</ul>



<p>Also, styling the Dash app requires a little more effort than with Streamlit. Which can be both a good thing and a bad thing, as we&#8217;ll see later.</p>



<h3 id='where-dash-is-better'  id="boomdevs_5" class="wp-block-heading" ><strong>Where Dash is better</strong></h3>



<p>While Streamlit excels in simplicity, Dash is better in customizability and scalability. Let&#8217;s see why.</p>



<p><strong>Customization.</strong></p>



<p>Dash is much more customizable and scalable because you can fully customize how the page looks, create multi-page apps effortlessly, and specify precisely how each component interacts.</p>



<p>Here&#8217;s an example of what&#8217;s possible with Dash: </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2940" height="1676" src="https://dash-resources.com/wp-content/uploads/2025/03/image-6.png" alt="" class="wp-image-819" srcset="https://dash-resources.com/wp-content/uploads/2025/03/image-6.png 2940w, https://dash-resources.com/wp-content/uploads/2025/03/image-6-300x171.png 300w, https://dash-resources.com/wp-content/uploads/2025/03/image-6-1024x584.png 1024w, https://dash-resources.com/wp-content/uploads/2025/03/image-6-768x438.png 768w, https://dash-resources.com/wp-content/uploads/2025/03/image-6-1536x876.png 1536w, https://dash-resources.com/wp-content/uploads/2025/03/image-6-2048x1167.png 2048w" sizes="auto, (max-width: 2940px) 100vw, 2940px" /><figcaption class="wp-element-caption">Illustration: screenshot of the Materials Explorer project from Berkeley Labs (<a href="https://next-gen.materialsproject.org/materials/mp-493#properties">screenshot page url</a>).</figcaption></figure>



<p>The <a href="https://next-gen.materialsproject.org/">Materials Explorer project</a> is one of the most interesting publicly available Dash app, in my opinion. It features custom layouts, menus, popups, sidebar, visualizations and multiple pages. I encourage you to pause reading this article and explore it (but come back after!).</p>



<p>You can find many more examples of sophisticated Dash applications in <a href="https://dash.gallery/">this gallery</a> and on the <a href="https://plotly.com/examples/">examples page</a>: </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2218" height="1462" src="https://dash-resources.com/wp-content/uploads/2025/03/image-5.png" alt="" class="wp-image-818" srcset="https://dash-resources.com/wp-content/uploads/2025/03/image-5.png 2218w, https://dash-resources.com/wp-content/uploads/2025/03/image-5-300x198.png 300w, https://dash-resources.com/wp-content/uploads/2025/03/image-5-1024x675.png 1024w, https://dash-resources.com/wp-content/uploads/2025/03/image-5-768x506.png 768w, https://dash-resources.com/wp-content/uploads/2025/03/image-5-1536x1012.png 1536w, https://dash-resources.com/wp-content/uploads/2025/03/image-5-2048x1350.png 2048w" sizes="auto, (max-width: 2218px) 100vw, 2218px" /><figcaption class="wp-element-caption">Illustration: the amount of different dashboards and apps you can create is unlimited. </figcaption></figure>



<p>Looking at the&nbsp;<a href="https://streamlit.io/gallery">Streamlit gallery</a>, you&#8217;ll notice that despite the variety of applications, they all follow the same visual structure and design patterns:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1854" height="1204" src="https://dash-resources.com/wp-content/uploads/2025/03/image-7.png" alt="" class="wp-image-820" srcset="https://dash-resources.com/wp-content/uploads/2025/03/image-7.png 1854w, https://dash-resources.com/wp-content/uploads/2025/03/image-7-300x195.png 300w, https://dash-resources.com/wp-content/uploads/2025/03/image-7-1024x665.png 1024w, https://dash-resources.com/wp-content/uploads/2025/03/image-7-768x499.png 768w, https://dash-resources.com/wp-content/uploads/2025/03/image-7-1536x997.png 1536w" sizes="auto, (max-width: 1854px) 100vw, 1854px" /></figure>



<p>If it is possible to change a few colors, Streamlit is simply not made for high customization. </p>



<p><strong>Scalability</strong>.</p>



<p>If your app grows, you might need more components and more interactions between them. With Dash, you can scale easily to hundreds of components and callbacks to handle interactions. Dash callbacks only update what&#8217;s necessary, providing better performance for complex applications.</p>



<p>Streamlit is not made the same way. When a user changes anything in Streamlit, the entire app runs again from top to bottom: a component changing at the very bottom of your app will trigger a full reload of other components, full reload of data, etc. This means developers must implement optimizations (e.g., using&nbsp;<code>@st.cache_data</code>) or the app will run slowly very quickly as it grows.</p>



<h2 id='dash-plotly-vs-streamlit-table-comparison'  id="boomdevs_6" class="wp-block-heading" ><strong>Dash plotly vs. Streamlit: table comparison</strong></h2>



<p>We compared each framework feature regarding how it is either built-in (<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" />), possible with extension (—), or needs to be implemented manually (<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" />).</p>



<h3 id='web-application-features'  id="boomdevs_7" class="wp-block-heading" ><strong>Web Application Features</strong></h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Feature</th><th>Dash</th><th>Streamlit</th><th>Notes</th></tr></thead><tbody><tr><td>Routing</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Dash has built-in routing; Streamlit provides st.pages</td></tr><tr><td>Custom Components</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Both support custom components</td></tr><tr><td>Authentication</td><td>—</td><td>—</td><td>Both require extensions</td></tr><tr><td>Layout Flexibility</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Dash offers more granular control, Streamlit follows a strict top-down layout</td></tr><tr><td>Mobile Responsiveness</td><td>—</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Streamlit is responsive by default, while it&#8217;s up to you to make it with Dash.</td></tr></tbody></table></figure>



<p>For web application features, Dash provides more built-in capabilities with flexible layouts, custom components, and mobile support. While both frameworks handle routing and require authentication extensions, Streamlit&#8217;s simpler structure makes it less adaptable for complex web applications requiring custom layouts.</p>



<h3 id='data-visualization-interactivity'  id="boomdevs_8" class="wp-block-heading" ><strong>Data Visualization &amp; Interactivity</strong></h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Feature</th><th>Dash</th><th>Streamlit</th><th>Notes</th></tr></thead><tbody><tr><td>Interactive Graphs</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Both excellent but different approaches</td></tr><tr><td>Real-time Updates</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Streamlit reruns entire script</td></tr><tr><td>Callback System</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/274c.png" alt="❌" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Dash&#8217;s core feature vs. Streamlit&#8217;s reactivity</td></tr><tr><td>Component State</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Dash: per-component; Streamlit: session state</td></tr><tr><td>Custom Visualizations</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>—</td><td>Dash offers more customization</td></tr><tr><td>Widget Variety</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Both offer rich UI elements, but Streamlit offer more easy to use components like&nbsp;<code>st.file_uploader</code></td></tr></tbody></table></figure>



<p>Both frameworks excel at data visualization, but their approaches differ significantly. Dash offers more precise control over interactions through its callback system, while Streamlit provides automatic reactivity at the cost of running the entire script on each interaction.</p>



<h3 id='development-deployment'  id="boomdevs_9" class="wp-block-heading" ><strong>Development &amp; Deployment</strong></h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Feature</th><th>Dash</th><th>Streamlit</th><th>Notes</th></tr></thead><tbody><tr><td>Learning Curve</td><td>—</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Streamlit is easier to learn</td></tr><tr><td>Development Speed</td><td>—</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Streamlit enables faster prototyping</td></tr><tr><td>Testing</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>—</td><td>Dash has better testing infrastructure</td></tr><tr><td>Deployment Options</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Both have cloud options, but Streamlit made it really easy.</td></tr><tr><td>Performance</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>—</td><td>Dash scales better for complex apps</td></tr><tr><td>Community &amp; Resources</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Both have active communities</td></tr><tr><td>Integration with Data Science</td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2705.png" alt="✅" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>Streamlit workflow is more notebook-like</td></tr></tbody></table></figure>



<p>Streamlit is the clear winner for development speed and ease of use, making it perfect for rapid prototyping. Dash, however, offers better performance for complex applications and scales better for production use.</p>



<h2 id='my-personal-opinion'  id="boomdevs_10" class="wp-block-heading" ><strong>My personal opinion</strong></h2>



<p>Even if you are on &#8220;<a href="https://dash-resources.com">dash-resources.com</a>&#8220;, I really tried to be as neutral as possible for this article. But you might guess that I might be biaised <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f604.png" alt="😄" class="wp-smiley" style="height: 1em; max-height: 1em;" />, so here is my full opinion on this comparison.</p>



<p>I found that both Streamlit and Dash required similar time investment to learn the basic concepts and components. Learning the Dash API (<code>dcc.Upload</code>, <code>dcc.Button</code>, <code>dcc.Input</code>, &#8230;) will asked me the same effort as learning the Streamlit API (<code>st.file_uploader</code>, <code>st.button</code>, <code>st.text_input</code>, <code>st.selectbox</code>, <code>st.slider</code>, &#8230;). Dash callbacks are quite easy to create.</p>



<p>However, I quickly felt limitated with Streamlit in the way that I could customize my app interactions and layout. Dash has proven to be significantly more flexible in the long run (my SaaS is still running on Dash&#8230;).</p>



<p>That being said, I understand why some developers prefer Streamlit, particularly for specific use cases like ML model training and inference interfaces, where they want something that feels like a Jupyter notebook but with a user interface. For these focused applications where extensive customization isn&#8217;t needed, Streamlit&#8217;s notebook-like approach can be a good fit.</p>



<p><strong>I think Streamlit is very good to build tools, and Dash is very good to build apps.</strong></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="callout"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b06.png" alt="⬆" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> <strong>Level up your Dash skills!</strong> <strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b06.png" alt="⬆" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> I wrote a full course to help you <a href="https://dash-resources.com/dash-plotly-course/">learn how to think and design a Dash app with simple and advanced callbacks</a>. Real pro-tips, from a real human expert!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id='conclusion'  id="boomdevs_11" class="wp-block-heading" ><strong>Conclusion</strong></h2>



<p>As you now understand, the difference between these two Python frameworks isn&#8217;t about their capability — they both provide the ability to develop data science oriented applications. It is more about what you want your app to look like at the end. </p>



<p>At the end, the choice depends on 1) the type of data app you need to build and 2) your skill set and workflow preferences:</p>



<ul class="wp-block-list">
<li><strong>Choose Streamlit</strong>&nbsp;if you need to build a streamlined app interface quickly. It&#8217;s perfect for people that work with notebooks and want to keep that workflow and feeling. Streamlit&#8217;s simplicity makes it ideal for data scientists who want to share their work without learning web development.</li>



<li><strong>Choose Dash</strong>&nbsp;if you want to create dashboards with no limits in terms of display and features. It&#8217;s perfect for people that want to build scalable apps (that can grow in time in terms of features) and SaaS data applications. Dash&#8217;s component-based architecture and callback system provide the flexibility needed for enterprise-grade applications.</li>
</ul>



<p>I hope this article helped you better understand the differences between Dash and Streamlit! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f600.png" alt="😀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>If you want to learn more about Dash plotly, you&#8217;re in the right place! We have a <a href="https://dash-resources.com/dash-plotly-course/">course</a> and a lot of <a href="https://dash-resources.com/tutorial-dash-callbacks-schemas-examples/">articles</a> with <a href="https://dash-resources.com/build-a-chatbot-web-app-under-5min-in-python/">examples</a> and <a href="https://dash-resources.com/dash-app-callback-performance-a-real-world-debugging-example/">practical solutions</a>. Hope to see you there <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>L’article <a href="https://dash-resources.com/dash-plotly-vs-streamlit-what-are-the-differences/">Dash plotly vs. Streamlit: what are the differences?</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Understanding dcc.Store in Dash Plotly</title>
		<link>https://dash-resources.com/understanding-dcc-store-in-dash-plotly/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Wed, 05 Mar 2025 16:59:05 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[storage]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=792</guid>

					<description><![CDATA[<p>Dash applications often require sharing data between callbacks without rerunning expensive computations or relying on an external database. This is where dcc.Store comes in to [...]</p>
<p>L’article <a href="https://dash-resources.com/understanding-dcc-store-in-dash-plotly/">Understanding dcc.Store in Dash Plotly</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Dash applications often require sharing data between callbacks without rerunning expensive computations or relying on an external database. This is where <code>dcc.Store</code> comes in to store your app data on the client side.</p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'><ul>
  <li class="first">
    <span></span>
    <ul class="menu_level_1">
      <li class="first">
        <a href="#what-is-dcc-store-and-why-do-we-need-it">What is dcc.Store, and why do we need it?</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#a-bit-of-history">A bit of history</a>
          </li>
          <li>
            <a href="#introduction-to-dcc-store">Introduction to dcc.Store</a>
          </li>
          <li class="last">
            <a href="#why-it-is-needed">Why it is needed</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#understanding-dcc-store-storage-types">Understanding dcc.Store Storage Types</a>
        <ul class="menu_level_2">
          <li class="first last">
            <a href="#how-much-can-you-store">How much can you store?</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#how-to-debug-dcc-store">How to debug dcc.Store</a>
      </li>
      <li class="last">
        <a href="#some-best-practices-for-dcc-store">Some best practices for dcc.Store</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#&#x27a1;-serialize-data-properly"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Serialize data properly</a>
          </li>
          <li>
            <a href="#&#x27a1;-don-t-store-large-objects-or-dataframes"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Don’t store large objects or DataFrames</a>
          </li>
          <li>
            <a href="#&#x27a1;-use-the-right-storage-type"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Use the right storage type</a>
          </li>
          <li class="last">
            <a href="#&#x27a1;-trigger-callbacks-efficiently"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Trigger callbacks efficiently</a>
          </li>
        </ul>
      </li>
    </ul>
  </li>
  <li class="last">
    <a href="#conclusion">Conclusion</a>
  </li>
</ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>In this tutorial, we’ll explore what <code>dcc.Store</code> is, how it works, and best practices for using it effectively in Dash applications.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="callout"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b06.png" alt="⬆" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> <strong>Level up your Dash skills!</strong> <strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b06.png" alt="⬆" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> I wrote a full course to help you <a href="https://dash-resources.com/dash-plotly-course/">learn how to think and design a Dash app with simple and advanced callbacks</a>. Real pro-tips, from a real human expert!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id='what-is-dcc-store-and-why-do-we-need-it'  id="boomdevs_1" class="wp-block-heading" >What is <code>dcc.Store</code>, and why do we need it?</h2>



<h3 id='a-bit-of-history'  id="boomdevs_2" class="wp-block-heading" >A bit of history</h3>



<p>Initially, people used to store data for Dash apps in hidden HTML components. However, that wasn’t an ideal solution: there was no way to retain data after reloading the page or closing the tab, and embedding large amounts of data in HTML could bloat the app and cause serious slowdowns.</p>



<p>As a result, the Plotly team introduced <code>dcc.Store</code> in Dash 0.32, and it has become one of the most essential components.</p>



<h3 id='introduction-to-dcc-store'  id="boomdevs_3" class="wp-block-heading" >Introduction to dcc.Store</h3>



<p>The <code>dcc.Store</code> component serves multiple important functions: it enables data sharing between callbacks without repeatedly passing large objects, stores intermediate calculation results to prevent costly recomputations, and can maintain data persistence during page reloads or multi-page navigation—when configured with the appropriate storage type.</p>



<p>It has four key properties:</p>



<ul class="wp-block-list">
<li><strong><code>id</code></strong>: Like any other regular Dash component, this is used for callbacks.</li>



<li><strong><code>storage_type</code></strong>: Either <code>memory</code>, <code>session</code>, or <code>local</code> (more on this below).</li>



<li><strong><code>data</code></strong>: The initial value for the store when the app loads.</li>



<li><strong><code>modified_timestamp</code></strong>: The latest modification timestamp.</li>
</ul>



<p>You can create as many <code>dcc.Store</code> components as you need and include them in your app’s layout. Here’s an example:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import Dash, dcc, html, Input, Output, State

app = Dash(__name__)

app.layout = html.Div([
    html.H1("My app"),
    # ... other components

    # Memory stores examples
    dcc.Store("user_id_store", storage_type="memory"),
    dcc.Store("dashboard_option_store", storage_type="local", data=False),
])

# A callback example 
@app.callback(
    Output("user_id_store", "data"),
    Input("button_login", "n_clicks"),
)
def login_callback(n_clicks):
    if n_clicks:
        # ... get user_id from login form
        return user_id
        
# Another callback example with user_id as state
@app.callback(
    Output("dashboard_option_store", "data"),
    Input("dashboard_option_dropdown", "value"),
    State("user_id_store", "data"),
)
def dashboard_option_callback(dashboard_option, user_id):
    if user_id:
        # ... get dashboard option for this specific user_id
        return dashboard_option</code></pre>



<p>In this example, we set up a <code>user_id_store</code> component to keep the current user ID. That information can then be passed as state to many callbacks requiring knowledge of which user is performing a particular action, such as selecting a dashboard option.</p>



<h3 id='why-it-is-needed'  id="boomdevs_4" class="wp-block-heading" >Why it is needed</h3>



<p>Dash operates as a stateless framework, meaning each callback function operates independently—processing requests and discarding user-specific data afterward. This architecture makes global variables ineffective for data persistence across callback executions (you should never use global variables in Dash apps!).</p>



<p>The <code>dcc.Store</code> component provides client-side storage for JSON-serializable data directly in the user’s browser, making it easy to retain and share data across multiple interactions (i.e., callbacks). It is perfect for storing IDs, or small size data.  </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1718" height="882" src="https://dash-resources.com/wp-content/uploads/2025/03/image.png" alt="" class="wp-image-793" srcset="https://dash-resources.com/wp-content/uploads/2025/03/image.png 1718w, https://dash-resources.com/wp-content/uploads/2025/03/image-300x154.png 300w, https://dash-resources.com/wp-content/uploads/2025/03/image-1024x526.png 1024w, https://dash-resources.com/wp-content/uploads/2025/03/image-768x394.png 768w, https://dash-resources.com/wp-content/uploads/2025/03/image-1536x789.png 1536w" sizes="auto, (max-width: 1718px) 100vw, 1718px" /><figcaption class="wp-element-caption">Illustration: A callback execution involving client-side stored data (with <code>dcc.Store</code>) and database data retrieval and processing.</figcaption></figure>



<p>However, if you want to maintain access to larger data between interactions (i.e. callbacks), you will need alternative storage methods like an external database (Redis, SQLite, PostgreSQL, MongoDB) because stored data on the clientside would require transmitting it over the network every time, which could be a huge bottleneck.</p>



<p class="callout"><strong>Learn</strong> best practices for callbacks here: <a href="https://dash-resources.com/dash-callbacks-best-practices-with-examples/">Dash callbacks best practices (with examples)</a> </p>



<h2 id='understanding-dcc-store-storage-types'  id="boomdevs_5" class="wp-block-heading" >Understanding <code>dcc.Store</code> Storage Types</h2>



<p>A key feature of <code>dcc.Store</code> is its flexibility in how data is stored. It offers three different storage types, each with unique characteristics:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Three different ways to initialize a store
dcc.Store(id='memory-store', storage_type='memory')  # Default
dcc.Store(id='session-store', storage_type='session')
dcc.Store(id='local-store', storage_type='local')</code></pre>



<p>Each storage type serves different purposes:</p>



<ul class="wp-block-list">
<li><strong>Memory (<code>storage_type='memory'</code>)</strong>: Data persists only for the current page session. If the user refreshes the page, the data is lost. This is the default and is best for temporary data that can be regenerated easily.</li>



<li><strong>Session (<code>storage_type='session'</code>)</strong>: Data persists across page refreshes within the same browser tab but is cleared when that tab (or the browser) is closed. This uses the browser’s <code>sessionStorage</code> API.</li>



<li><strong>Local (<code>storage_type='local'</code>)</strong>: Data persists indefinitely, even after the browser is closed and reopened. This uses the browser’s <code>localStorage</code> API and is great for user preferences or application settings.</li>
</ul>



<p>Your choice depends on your specific requirements. For instance, use <code>local</code> for user preferences that should persist across sessions, <code>session</code> for data that should survive a refresh but not a browser restart, and <code>memory</code> for transient data like intermediate computational results.</p>



<p class="callout"><strong>Pro-tip</strong>: It can be helpful to add a suffix like <code>-store</code> or <code>_memory</code> to all your <code>dcc.Store</code> IDs. This makes it easy to identify which inputs and outputs are stores in your callbacks.</p>



<h3 id='how-much-can-you-store'  id="boomdevs_6" class="wp-block-heading" >How much can you store?</h3>



<p>The amount of data that can be stored in <code>dcc.Store</code> depends on the selected <code>storage_type</code>:</p>



<ul class="wp-block-list">
<li><strong>Memory</strong>: Limited only by the browser’s available memory but cleared on page refresh.</li>



<li><strong>Session and Local</strong>: These rely on the browser’s <code>sessionStorage</code> and <code>localStorage</code> APIs, which typically allow up to <strong>10MB</strong> per domain (<a href="https://developer.mozilla.org/en-US/docs/Web/API/Storage_API/Storage_quotas_and_eviction_criteria#how_much_data_can_be_stored">source</a>). However, this limit varies between browsers and may be smaller on mobile devices.</li>
</ul>



<p>Additionally, performance considerations matter when using <code>dcc.Store</code> for large datasets. Each time a callback accesses or updates <code>dcc.Store</code>, Dash serializes and transmits the data over the network. If you store large JSON objects (for example, a DataFrame of thousands of rows), it can lead to significant bandwidth usage and slow performance, especially for users with limited internet speeds. In such cases, consider using server-side caching or database storage instead.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id='how-to-debug-dcc-store'  id="boomdevs_7" class="wp-block-heading" >How to debug <code>dcc.Store</code></h2>



<p>Depending on the storage type, you can inspect, update, or delete storage values, which can be useful during development, especially for <code>session</code> and <code>local</code> types.</p>



<p>For this example, I will use a To-Do app built in Dash. You can find how I created it in this tutorial: <a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-2-3/">Build a To-Do app in Python with Dash (part 2/3)</a> and open the live app <a href="https://scripts.dash-resources.com/todo_app/app5.py/">here</a>.</p>



<ol class="wp-block-list">
<li>Open your browser’s Developer Tools (press <code>F12</code> or right-click on the page and select <strong>Inspect</strong>).</li>



<li>Navigate to <strong>Application</strong> (or a similar tab), where you can find Local Storage and Session Storage.</li>
</ol>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="1418" src="https://dash-resources.com/wp-content/uploads/2025/03/image-1.png" alt="" class="wp-image-794" srcset="https://dash-resources.com/wp-content/uploads/2025/03/image-1.png 2048w, https://dash-resources.com/wp-content/uploads/2025/03/image-1-300x208.png 300w, https://dash-resources.com/wp-content/uploads/2025/03/image-1-1024x709.png 1024w, https://dash-resources.com/wp-content/uploads/2025/03/image-1-768x532.png 768w, https://dash-resources.com/wp-content/uploads/2025/03/image-1-1536x1064.png 1536w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /><figcaption class="wp-element-caption">Illustration: an example of localStorage in my to-do app. We can see the exact content of two dcc.Store components: <code>current_index_memory</code> and <code>list_data_memory</code>. The other entries (<code>iconify-count</code> and <code>iconify-version</code>) are from a third-party library.</figcaption></figure>



<p>You will find the <em>Local Storage</em> and <em>Session Storage</em> list. However, store components using <code>storage_type="memory"</code> will not appear: the data is stored as a React state. It’s encapsulated in the JavaScript memory of the page and is not easily accessible. It’s also not particularly useful to access it directly.</p>



<p>When clicking, you can see the list of <code>dcc.Store</code> keys. Each store will have one entry for the content (JSON-serialized) and one entry for the <code>modified_timestamp</code>.</p>



<p>A few important notes:</p>



<ul class="wp-block-list">
<li><strong>Confidentiality:</strong> Notice how <strong>visible</strong> the information stored in a store component is to the end user! In the example above, the entire list of tasks is clearly visible. As a result, you should always remember <strong>not</strong> to store any confidential information in a <code>dcc.Store</code> component. This is especially true for <code>localStorage</code>, which can persist indefinitely (someone accessing your Dash app months later could see the same data as another user from months prior).</li>



<li><strong>Sharing storage:</strong> <code>localStorage</code> and <code>sessionStorage</code> are shared with other libraries in your app. So if you use third-party libraries, you may see their entries here. For example, I use a chatbot in one of my apps, and this chatbot extension stores its own info in <code>localStorage</code>.</li>
</ul>



<p class="callout"><strong>Pro-tip #1</strong>: When working with <code>storage_type="local"</code>, remember to delete your local storage entries when you need to simulate a fresh app initialization.<br><br><strong>Pro-tip #2</strong>: If you develop many apps at the same local URL (e.g., <code>http://127.0.0.1:8050/</code>), you can accidentally overwrite your local storage data across apps if you use the same store IDs. Keep this in mind to avoid unexpected bugs!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 id='some-best-practices-for-dcc-store'  id="boomdevs_8" class="wp-block-heading" >Some best practices for <code>dcc.Store</code></h2>



<h3 id='&#x27a1;-serialize-data-properly'  id="boomdevs_9" class="wp-block-heading" ><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Serialize data properly</h3>



<p>A common error is attempting to store non-JSON-serializable objects, such as Pandas DataFrames, NumPy arrays, or custom Python objects. Since <code>dcc.Store</code> supports only JSON-compatible data, make sure to convert these objects first (using <code>.to_json()</code>, <code>.tolist()</code>, <code>json.dumps()</code>, etc.):</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><em>TypeError: Object of type DataFrame is not JSON serializable.</em></p>
</blockquote>



<p>If your data is not JSON serializable, it&#8217;s a strong indication that you should rely on an external source (e.g. a database). Note that for small images, you can still <a href="https://www.base64-image.de/">encode them in base64</a> to get a string. </p>



<h3 id='&#x27a1;-don-t-store-large-objects-or-dataframes'  id="boomdevs_10" class="wp-block-heading" ><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Don’t store large objects or DataFrames</h3>



<p>Avoid storing large datasets in <code>dcc.Store</code>. It’s better to use an external database and only store dataset IDs in the <code>dcc.Store</code> component.</p>



<p>There’s no hard limit for maximum size -it depends on your bandwidth and dataset. However, I would recommend keeping it under 10 MB for high-speed connections, 100KB-1MB for a production ready app.</p>



<p class="callout"><strong>Good to know:</strong> If you want to see a practical example of the impact it can have and how to debug performance issues with network development tools, see here: <a href="https://dash-resources.com/dash-app-callback-performance-a-real-world-debugging-example/">Dash app callback performance: a real-world debugging example</a></p>



<h3 id='&#x27a1;-use-the-right-storage-type'  id="boomdevs_11" class="wp-block-heading" ><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Use the right storage type</h3>



<p>Select storage type <code>memory</code>, <code>session</code>, or <code>local</code> based on how long the data should persist. If you don’t need any persistence, keep it light with <code>memory</code> or <code>session</code>. And remember that you have a limited amount of storage made possible by the browser.</p>



<h3 id='&#x27a1;-trigger-callbacks-efficiently'  id="boomdevs_12" class="wp-block-heading" ><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/27a1.png" alt="➡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Trigger callbacks efficiently</h3>



<p>You can use the <code>modified_timestamp</code> property instead of <code>data</code> if you only need the store as a trigger (without the data itself). This reduces the data transmitted over the network. You can also use <code>modified_timestamp</code> to trigger a callback at initialization time. </p>



<p class="callout"><strong>Good to know: </strong>See an example of how you can use <code>modified_timestamp</code> instead of <code>data</code> here.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h1 id='conclusion'  id="boomdevs_13" class="wp-block-heading" >Conclusion</h1>



<p>I hope this article was helpful. My goal was to provide complementary information to the <a href="https://dash.plotly.com/dash-core-components/store">official documentation</a>! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p><code>dcc.Store</code> is an essential component that simplifies state management in Dash apps. By using it, you can avoid redundant computations, maintain user session data, and enhance performance. As your app grows, choosing the right storage type and structuring your callbacks efficiently will become increasingly important.</p>



<ul class="wp-block-list">
<li>If you want to learn more, take a look at the To-Do app tutorial, which provides a practical example of how to use <code>dcc.Store</code>: <a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-1-3/">Build a To-Do app in Python with Dash (part 1/3)</a>.</li>



<li>If you have any questions, feel free to ask on the Plotly forum <a href="https://community.plotly.com/t/a-dcc-store-tutorial-how-it-works-storage-types-and-more/90866">here</a>.</li>
</ul>



<p>Happy coding <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" />!</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p class="callout"><strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b06.png" alt="⬆" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> <strong>Level up your Dash skills!</strong> <strong><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b06.png" alt="⬆" class="wp-smiley" style="height: 1em; max-height: 1em;" /></strong> I wrote a full course to help you <a href="https://dash-resources.com/dash-plotly-course/">learn how to think and design a Dash app with simple and advanced callbacks</a>. Real pro-tips, from a real human expert!</p>
<p>L’article <a href="https://dash-resources.com/understanding-dcc-store-in-dash-plotly/">Understanding dcc.Store in Dash Plotly</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Dash app callback performance: a real-world debugging example</title>
		<link>https://dash-resources.com/dash-app-callback-performance-a-real-world-debugging-example/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Thu, 20 Feb 2025 10:07:28 +0000</pubDate>
				<category><![CDATA[Experienced level]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=756</guid>

					<description><![CDATA[<p>While working on my app, I noticed a discrepancy in performance between the deployed version and my local environment. There was a little delay to [...]</p>
<p>L’article <a href="https://dash-resources.com/dash-app-callback-performance-a-real-world-debugging-example/">Dash app callback performance: a real-world debugging example</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>While working on my app, I noticed a discrepancy in performance between the deployed version and my local environment. There was a little delay to display some information, that I found annoying.</p>



<p>In this blog post, I’ll walk you through how I quickly enhanced my app’s performance by analyzing Dash callback execution, which hopefully give you some ideas for your debugging too. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#context">Context</a>
      </li>
      <li>
        <a href="#so-how-to-debug-this">So how to debug this?</a>
      </li>
      <li>
        <a href="#resolution">Resolution</a>
      </li>
      <li>
        <a href="#result">Result</a>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<h2 id='context'  id="boomdevs_1" class="wp-block-heading" >Context</h2>



<p>My dash application is essentially a real estate map which displays information when clicking on map points.</p>



<p>The click that displayed information felt instant when running locally, but when running in production, it had about a one-second delay. I also noticed that the more elements I displayed on my map, the worse the performance became.</p>



<figure class="wp-block-video"><video height="1266" style="aspect-ratio: 1694 / 1266;" width="1694" autoplay controls loop muted src="https://dash-resources.com/wp-content/uploads/2025/02/prod-app.mp4"></video><figcaption class="wp-element-caption">Illustration: you can see a 1-2sec delay between the click and the display of the information on the sidebar.</figcaption></figure>



<p>My production app and local app differ in some ways, and I could imagine at least two reasons:</p>



<ul class="wp-block-list">
<li><strong>Maybe callbacks are slower</strong> due to design differences between production and local development. For example, the cache used is not the same: I use Flask cache disk locally, which is faster, while my app uses a Redis backend to cache results. Redis introduces additional overhead due to the external request it must handle, compared to just RAM or disk caching on the same device.</li>



<li><strong>Maybe the request itself is slower</strong> due to upload/download speed limits. When deployed, my app is on a remote web server, whereas locally it runs directly on my computer. Obviously, this introduces extra overhead. It’s much faster for my browser to communicate with a local app on the same device as with a remote app over the internet.</li>
</ul>



<h2 id='so-how-to-debug-this'  id="boomdevs_2" class="wp-block-heading" >So how to debug this?</h2>



<p>To make it short, I tried to replicate the issue locally (which is always a good way to debug things). I started by inspecting the triggered callbacks after a click on the map. You might know that all Dash callbacks translate to HTTP requests, so I used the developer tools and network tab to analyze what was happening.</p>



<p class="callout">If you don’t know that callbacks translate to HTTP request, I recommend you to download my guide “<strong>Master Dash Callbacks: Architecture &amp; Best Practices (Free PDF)</strong>” <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f609.png" alt="😉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> find it on the sidebar &#8212;-&gt; or below this article on mobile.</p>



<p>To simulate a “distant” request from my computer to a remote server, I set the network throttling to “Good 3G” to mimic a 3G mobile connection. <strong>This was key in debugging.</strong> Without this, everything appeared snappy in my browser.</p>



<p>I discovered that eight callbacks were triggered (directly or indirectly) after clicking on the map. Here is the debugging process in video:</p>



<figure class="wp-block-video"><video height="1266" style="aspect-ratio: 1694 / 1266;" width="1694" autoplay controls loop muted src="https://dash-resources.com/wp-content/uploads/2025/02/dev-debug.mp4"></video><figcaption class="wp-element-caption">Illustration: you can see no delay in debug mode. But when simulating network throttling, the delay became visible again.</figcaption></figure>



<p>As you can see in the video, <strong>it</strong> <strong>literally took around 14 seconds</strong> (!!) to complete the eight callbacks after clicking on the map.</p>



<p>Two requests appeared to be blocking the others, leading to huge delays: between 1 and 8 seconds for some callbacks. This is significant.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="501" src="https://dash-resources.com/wp-content/uploads/2025/02/image-9.png" alt="Illustration of the network debugging tools and the corresponding http requests to the callbacks." class="wp-image-759" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-9.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-300x73.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-1024x251.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-768x188.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-1536x376.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-9-1320x323.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>



<p>When inspecting the first request, I quickly realized that the bottleneck was the time spent “Sending” the data: 4.05s. The “Waiting” time (when the browser waits for the server response) was only 64ms. So, the callback processing was not the problem—the data upload was.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="501" src="https://dash-resources.com/wp-content/uploads/2025/02/image-10.png" alt="Illustration of the timings for the first request." class="wp-image-760" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-10.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-300x73.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-1024x251.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-768x188.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-1536x376.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-10-1320x323.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>



<p>When inspecting the second request, I saw that it was also blocked for 4.05s, meaning it was simply not triggered immediately.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="557" src="https://dash-resources.com/wp-content/uploads/2025/02/image-11.png" alt="Illustration of the timings for the second request." class="wp-image-761" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-11.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-300x82.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-1024x279.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-768x209.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-1536x418.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-11-1320x359.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>



<p>The third request showed the same problem as the first one: too much time spent sending data, with only 108ms processing on the server side and no delay in receiving the result.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="555" src="https://dash-resources.com/wp-content/uploads/2025/02/image-12.png" alt="Illustration of the timings for the third request." class="wp-image-762" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-12.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-300x81.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-1024x278.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-768x208.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-1536x416.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-12-1320x358.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>



<p>At this point, it was clear that two callbacks were problematic. To identify which callbacks these HTTP requests were triggering, I inspected the request details. The names of outputs, inputs, and states are visible as part of the information Dash sends with each callback:</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2048" height="1048" src="https://dash-resources.com/wp-content/uploads/2025/02/image-13.png" alt="Illustration of debugging the inputs/states/outputs of a callback request" class="wp-image-763" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-13.png 2048w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-300x154.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-1024x524.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-768x393.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-1536x786.png 1536w, https://dash-resources.com/wp-content/uploads/2025/02/image-13-1320x675.png 1320w" sizes="auto, (max-width: 2048px) 100vw, 2048px" /></figure>



<p>I realized that the full map data (<code>map_figure_data</code>) was being sent. For my app, that meant all the points and their hover information (descriptions, images, etc.).</p>



<p>Even though it wasn’t a huge amount of data, it posed two problems:</p>



<ul class="wp-block-list">
<li>Upload speeds are usually slower than download speeds. For slow connections like mobile networks, this delay is unbearable.</li>



<li>The issue worsens as the number of data points on my map increases (!)</li>
</ul>



<p>The second callback had the same problem. This time, the map data was an input. The goal was to see how I could eliminate this <code>map_figure_data</code> dependency.</p>



<h2 id='resolution'  id="boomdevs_3" class="wp-block-heading" >Resolution</h2>



<p>I then looked at my two callbacks. Here’s what they originally looked like.</p>



<p><strong>callback 1 &#8211; before</strong></p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("map_selection_memory", "data"),
    [
        Input("map", "selectedData"),
        Input("location_data_memory", "data"),
        Input("layer_selection_memory", "data"),
    ],
    [
        State("map_curve_mapping_memory", "data"),
        State("map_selection_memory", "data"),
        State("map_figure_data", "data"),  # the problematic state
    ]
)
def store_map_selection(
    selected_data, location_data, layer, curve_mapping, selected_ids, figure_data
):
    """A callback that Stores the selected ids in memory."""
</code></pre>



<p>While reviewing the callback code, I realized that <code>figure_data</code> wasn’t even used. So, I removed it and it solved the problem for this callback… (yes, I guess that these things happens!).</p>



<p><strong>callback 1 &#8211; after (removed useless state!)</strong></p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("map_selection_memory", "data"),
    [
        Input("map", "selectedData"),
        Input("location_data_memory", "data"),
        Input("layer_selection_memory", "data"),
    ],
    [
        State("map_curve_mapping_memory", "data"),
        State("map_selection_memory", "data"),
        # just removed the useless state
    ]
)
def store_map_selection(
    selected_data, location_data, layer, curve_mapping, selected_ids
):
    """A callback that Stores the selected ids in memory."""
</code></pre>



<p>Now, looking at the second callback.</p>



<p><strong>callback 2 &#8211; before</strong></p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("cache_data_metadata_memory", "data"),
    [
        Input("map_figure_data", "data"),  # this is problematic
        Input("map_selection_memory", "data"),
        Input("data_modal_unchecked_memory", "data"),
    ],
    State("address_input_memory", "data"),
    State("layer_selection_memory", "data"),
    State("filters_selection_memory", "data"),
    prevent_initial_call=True,
)
def update_cache_data_metadata_memory(
    fig_data, selected_ids, unchecked_ids, address, layer, filters
):
    """Update metadata dict (...)"""
</code></pre>



<p>Again, <code>fig_data</code> wasn’t used. However, I still needed <code>map_figure_data</code> to trigger updates, so I changed the input property to <code>modified_timestamp</code>. This enables the callback to be triggered when <code>map_figure_data</code> changes (its timestamp is updated) while not transferring the whole data.</p>



<p><strong>callback 2 &#8211; after (changed to modified_timestamp)</strong></p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("cache_data_metadata_memory", "data"),
    [
        Input("map_figure_data", "modified_timestamp"),  # this is efficient
        Input("map_selection_memory", "data"),
        Input("data_modal_unchecked_memory", "data"),
    ],
    State("address_input_memory", "data"),
    State("layer_selection_memory", "data"),
    State("filters_selection_memory", "data"),
    prevent_initial_call=True,
)
def update_cache_data_metadata_memory(
    map_update_timestamp, selected_ids, unchecked_ids, address, layer, filters
):
    """Update metadata dict (...)"""
</code></pre>



<p>And that did the job perfectly!</p>



<p><strong>But what if I actually had to keep the full figure data</strong> ? I would have had two choices:</p>



<ul class="wp-block-list">
<li>Create another input, that is lighter than the figure data</li>



<li>Process the figure data in a clientside callback to avoid a roundtrip to the server.</li>
</ul>



<h2 id='result'  id="boomdevs_4" class="wp-block-heading" >Result</h2>



<p>These changes literally took 20 seconds to implement. The result was instantly better, even with the &#8220;Good 3G&#8221; mobile throttling still enabled:</p>



<figure class="wp-block-video"><video height="1266" style="aspect-ratio: 1694 / 1266;" width="1694" autoplay controls loop muted src="https://dash-resources.com/wp-content/uploads/2025/02/prod-app-fixed.mp4"></video><figcaption class="wp-element-caption">Illustration: no more delay on the debug app, even with 3G network throttling. </figcaption></figure>



<p>After making these adjustments, I ensured there were no other instances of <code>map_figure_data</code> being used as either an Input or Output.</p>



<h2 id='conclusion'  id="boomdevs_5" class="wp-block-heading" >Conclusion</h2>



<p>When debugging this issue, I thought it would be an interesting use case to share, because: </p>



<ul class="wp-block-list">
<li>Using <strong>developer tools</strong> is a powerful way to debug slow callbacks and sluggish Dash apps. Keep in mind that all inputs and states trigger data uploads, which in some cases can be extremely slow.</li>



<li>Using <strong>throttling mode</strong> allows you to simulate production conditions or at least non-optimal conditions, unlike debugging locally.</li>
</ul>



<p>I hope you found this interesting. Such performance explanations and debugging techniques are part of my <a href="https://dash-resources.com/dash-plotly-course/"><strong>Dash course</strong></a>, where you can find more <strong>detailed explanations and videos</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>If you have questions, feel free to ask them <a href="https://community.plotly.com/t/dash-app-callback-performance-a-real-world-debugging-example/90653">here</a> on Plotly&#8217;s forum.</p>



<p>Happy coding! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b50.png" alt="⭐" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>L’article <a href="https://dash-resources.com/dash-app-callback-performance-a-real-world-debugging-example/">Dash app callback performance: a real-world debugging example</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		<enclosure url="https://dash-resources.com/wp-content/uploads/2025/02/prod-app.mp4" length="378456" type="video/mp4" />
<enclosure url="https://dash-resources.com/wp-content/uploads/2025/02/dev-debug.mp4" length="1929505" type="video/mp4" />
<enclosure url="https://dash-resources.com/wp-content/uploads/2025/02/prod-app-fixed.mp4" length="785372" type="video/mp4" />

			</item>
		<item>
		<title>A guide to beautiful Dashboards (basic design principles)</title>
		<link>https://dash-resources.com/a-guide-to-beautiful-dashboards-basic-design-principles/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Sun, 09 Feb 2025 15:26:47 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=707</guid>

					<description><![CDATA[<p>So you’ve started building dashboards with Dash Plotly. Bravo! But you soon realize that even if it is easy to build dashboards, it is somehow [...]</p>
<p>L’article <a href="https://dash-resources.com/a-guide-to-beautiful-dashboards-basic-design-principles/">A guide to beautiful Dashboards (basic design principles)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>So you’ve started building dashboards with Dash Plotly. Bravo! But you soon realize that even if it is easy to build dashboards, it is somehow <strong>not easy to make them look good</strong>. So how do you design a beautiful dashboard with Dash?</p>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#introduction">Introduction</a>
      </li>
      <li>
        <a href="#pick-a-font">Pick a font</a>
      </li>
      <li>
        <a href="#pick-colors-correctly">Pick colors correctly</a>
      </li>
      <li>
        <a href="#beware-of-the-hierarchy">Beware of the hierarchy</a>
      </li>
      <li>
        <a href="#space-things-out">Space things out!</a>
      </li>
      <li>
        <a href="#keep-things-homogeneous">Keep things homogeneous</a>
      </li>
      <li>
        <a href="#remember-to-style-charts-too">Remember to style charts too</a>
      </li>
      <li>
        <a href="#use-pre-made-components">Use pre-made components</a>
        <ul class="menu_level_2">
          <li class="first last">
            <a href="#see-also">See also:</a>
          </li>
        </ul>
      </li>
      <li class="last">
        <a href="#conclusion">Conclusion</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>In this tutorial, I’ll give you <strong>tips</strong> and <strong>advices</strong> to keep in mind to create visually appealing dashboards and Dash apps, to increase the usability and aesthetics of your project.</p>



<h2 id='introduction'  id="boomdevs_1" class="wp-block-heading" >Introduction</h2>



<p>Let’s be honest: most dashboards are functional but completely fail in terms of UI/UX (user interface/user experience). And that’s normal—most of us are data analysts, data scientists, or Python developers, and UI/UX is not our job.</p>



<p>If you are building a dashboard for your own use or designed for technical people, you might not care about aesthetics. But if you want people outside your team to engage with and use the amazing dashboard that you made, you should invest time in making your dashboard visually appealing.</p>



<figure class="wp-block-image size-full is-style-default"><img loading="lazy" decoding="async" width="1378" height="1076" src="https://dash-resources.com/wp-content/uploads/2025/02/image.png" alt="Illustration: a dashboard with all possible visual problems." class="wp-image-708" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image.png 1378w, https://dash-resources.com/wp-content/uploads/2025/02/image-300x234.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-1024x800.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-768x600.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-1320x1031.png 1320w" sizes="auto, (max-width: 1378px) 100vw, 1378px" /><figcaption class="wp-element-caption">Can you even look at this dashboard ? It’s really hurting the eyes.</figcaption></figure>



<p>This is even more true if you are creating an app used by non-technical people (not in your field) or if you are developing a SaaS product for a broader audience with Dash.</p>



<p>Fortunately, by following a few rules, you can significantly improve your skills in building beautiful dashboards and apps. Let’s dive in. <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f642.png" alt="🙂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 id='pick-a-font'  id="boomdevs_2" class="wp-block-heading" >Pick a font</h2>



<p>The harsh truth: the <strong>lack of a personalized font</strong> is one of the common traits of ugly dashboards. That’s why I put it at the top of this list. You’ll never see a professional-looking app that sticks to &#8220;<em>Times New Roman</em>&#8221; (i.e., the default font in most web browsers).</p>



<p>This doesn’t mean this font should never be used, but it means that default fonts quickly show that no effort was put into aesthetics. So, setting a proper font is a real quick win.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1382" height="1063" src="https://dash-resources.com/wp-content/uploads/2025/02/image-1.png" alt="Illustration: the same dashboard with fonts fixed." class="wp-image-709" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-1.png 1382w, https://dash-resources.com/wp-content/uploads/2025/02/image-1-300x231.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-1-1024x788.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-1-768x591.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-1-1320x1015.png 1320w" sizes="auto, (max-width: 1382px) 100vw, 1382px" /><figcaption class="wp-element-caption">The same dashboard, but we changed font: it looks instantly different.</figcaption></figure>



<p>Serif fonts often imply tradition and formality (<em>Times New Roman</em> is widely used in newspapers and books, and there’s nothing wrong with that if your app is focused on reports, articles, or anything content-heavy). But usually, dashboards benefit from sans-serif fonts for a cleaner, more modern look.</p>



<p>Recommended fonts are the basic ones:</p>



<ul class="wp-block-list">
<li><em>Arial, Open Sans, Verdana, Helvetica Neue</em>.</li>
</ul>



<p>If you need more stylish and trendy fonts, here are some great options:</p>



<ul class="wp-block-list">
<li><em>Roboto</em> (modern and widely used in web design)</li>



<li><em>Lato</em> (clean and friendly, great for dashboards)</li>



<li><em>Montserrat</em> (sleek and professional)</li>



<li><em>Inter</em> (highly readable and trendy for UI design)</li>
</ul>



<p>If you need more inspiration, you can explore <a href="https://fonts.google.com/">Google Fonts</a> or <a href="https://fonts.adobe.com/">Adobe Fonts</a> to find stylish options that fit your dashboard’s aesthetic. But being too exotic is not a good idea either.</p>



<p class="callout"><strong>Note:</strong> You should always set a font because otherwise, your app might look different on various devices (Windows, Mac, Linux, etc.) and browsers, which may pick different default fonts.</p>



<h2 id='pick-colors-correctly'  id="boomdevs_3" class="wp-block-heading" >Pick colors correctly</h2>



<p><strong>No colors is terrible</strong></p>



<p>Colors help with contrast, and contrast is absolutely necessary if you want to ease the eye. A dashboard with no contrast is hard to read, making users tire quickly. If everything looks the same, users won’t know where to focus.</p>



<p>Using just black and white or a single muted color can make your app look dull and unstructured. Instead, introduce some contrast using different shades and tones, but in a subtle way. Use colors to differentiate sections, highlight key metrics, and guide user attention effortlessly.</p>



<p><strong>Too many colors is terrible</strong></p>



<p>On the flip side, throwing in too many colors makes everything look chaotic. Too much contrast can make your dashboard overwhelming and confusing. A rainbow-colored dashboard is not visually appealing—it’s distracting.</p>



<p>Go easy on gradients. Too many gradients will make your app look like outdated WordArt! Use soft gradients sparingly—to highlight a few buttons or text, but not everywhere. Stick to a defined color palette and ensure your colors complement each other.</p>



<p><strong>Importance of color choice</strong></p>



<p>Colors enhance the appearance of your app but also improve user experience: you can emphasize the importance of a button by its color alone, meaning the user instantly understands its significance.</p>



<p>People also associate certain colors with specific actions—red buttons usually indicate critical actions, while blue or black buttons tend to be more neutral. Green often signals success, and yellow can be used for warnings.</p>



<p>Just like fonts, using default colors (100% red, 100% green, or 100% blue) will make your dashboard look unpolished. Instead, pick variants of these colors—softer shades, deeper tones, or slight variations.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1376" height="1083" src="https://dash-resources.com/wp-content/uploads/2025/02/image-2.png" alt="Illustration: the same dashboard with better colors." class="wp-image-710" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-2.png 1376w, https://dash-resources.com/wp-content/uploads/2025/02/image-2-300x236.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-2-1024x806.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-2-768x604.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-2-1320x1039.png 1320w" sizes="auto, (max-width: 1376px) 100vw, 1376px" /><figcaption class="wp-element-caption">Our dashboard after changing the colors for softer colors. We also got rid of contrasts problems like a dark text on a dark button and removed old-looking gradients.</figcaption></figure>



<p class="callout"><strong>Pro tip:</strong> If you don’t know what colors to use, you can rely on predefined color palettes from <a href="https://mantine.dev/theming/colors/">Mantine</a>, <a href="https://getbootstrap.com/docs/4.0/utilities/colors/">Bootstrap</a>, or websites like <a href="https://flatuicolors.com/">Flat UI Colors</a>.</p>



<p>Also remember that users have different visual impairments (e.g., color blindness). Learn more about it here: <a href="https://davidmathlogic.com/colorblind/">coloring for colorblindness</a>.</p>



<h2 id='beware-of-the-hierarchy'  id="boomdevs_4" class="wp-block-heading" >Beware of the hierarchy</h2>



<p>Hierarchy isn&#8217;t just about making titles bigger —it&#8217;s about creating a <strong>natural flow for the eyes</strong>. When someone looks at your dashboard, they should naturally understand what&#8217;s important and what&#8217;s secondary.</p>



<p>A simple but effective approach is to stick to 3-4 text sizes for your entire app. Your main title should be the biggest (around 32px), followed by section titles (24px), and then your regular text (14-16px). Don&#8217;t go smaller than 12px &#8211; nobody likes squinting at tiny text!</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1384" height="1118" src="https://dash-resources.com/wp-content/uploads/2025/02/image-3.png" alt="Illustration: the same dashboard with better hierarchy." class="wp-image-712" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-3.png 1384w, https://dash-resources.com/wp-content/uploads/2025/02/image-3-300x242.png 300w, https://dash-resources.com/wp-content/uploads/2025/02/image-3-1024x827.png 1024w, https://dash-resources.com/wp-content/uploads/2025/02/image-3-768x620.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-3-1320x1066.png 1320w" sizes="auto, (max-width: 1384px) 100vw, 1384px" /><figcaption class="wp-element-caption">Now titles are bigger, as well as the most important data (sales, revenue, profit). The “Recent activity” section is larger and the text below is smaller.</figcaption></figure>



<p>But size isn&#8217;t everything. You can also create hierarchy through weight (bold vs regular) and color intensity. Just like in a newspaper, the most important stuff should catch your eye first.</p>



<p>Think about it: when you look at a well-designed website, you instantly know where to focus, right? That&#8217;s good hierarchy at work.</p>



<h2 id='space-things-out'  id="boomdevs_5" class="wp-block-heading" >Space things out!</h2>



<p>Let your components breathe:</p>



<ul class="wp-block-list">
<li>Add margins around text and padding inside buttons.</li>



<li>Text should not be stuck to the border of its container—use proper padding and margins.</li>
</ul>



<p>The easiest way to handle spacing is to pick a basic unit (like 8 pixels) and use multiples of it. Put a little space between related items (8px), more space between different elements (16px), and even more space between major sections (24px or 32px).</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1385" height="1637" src="https://dash-resources.com/wp-content/uploads/2025/02/image-4.png" alt="Illustration: the same dashboard with better spacing." class="wp-image-713" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-4.png 1385w, https://dash-resources.com/wp-content/uploads/2025/02/image-4-254x300.png 254w, https://dash-resources.com/wp-content/uploads/2025/02/image-4-866x1024.png 866w, https://dash-resources.com/wp-content/uploads/2025/02/image-4-768x908.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-4-1300x1536.png 1300w, https://dash-resources.com/wp-content/uploads/2025/02/image-4-1320x1560.png 1320w" sizes="auto, (max-width: 1385px) 100vw, 1385px" /><figcaption class="wp-element-caption">Now we can breathe! We added space between each section along with borders. We also fix alignment of the info icon, of the logout button and of the activities. The main buttons are spaced gracefully too, as well as the main data.</figcaption></figure>



<p>Remember: <strong>white space isn&#8217;t wasted space</strong>. It&#8217;s like the margins in a book &#8211; without them, everything would be a mess and hard to read. Give your components some breathing room. Your users&#8217; eyes will thank you.</p>



<h2 id='keep-things-homogeneous'  id="boomdevs_6" class="wp-block-heading" >Keep things homogeneous</h2>



<p>In art, <strong>proportions</strong> are an integral part of the works of the greatest masters. Many sculptures and paintings, for example, use the <a href="https://en.wikipedia.org/wiki/Golden_ratio">golden ratio</a>.</p>



<p>The same applies to interfaces: consistency is key to making dashboards look polished and professional.</p>



<p>But what does this mean in practice?</p>



<ul class="wp-block-list">
<li>Apply the same margins and padding throughout, ensuring a balanced layout while respecting hierarchy.</li>



<li>Stick to 2-3 main colors with one accent color for emphasis. Example: if you use blue buttons, don’t randomly switch to green unless it serves a purpose.</li>



<li>Keep shapes and styles uniform—rounded buttons should stay rounded, and shadows should be consistent across cards.</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1385" height="1637" src="https://dash-resources.com/wp-content/uploads/2025/02/image-5.png" alt="Illustration: the same dashboard with better proportions." class="wp-image-714" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-5.png 1385w, https://dash-resources.com/wp-content/uploads/2025/02/image-5-254x300.png 254w, https://dash-resources.com/wp-content/uploads/2025/02/image-5-866x1024.png 866w, https://dash-resources.com/wp-content/uploads/2025/02/image-5-768x908.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-5-1300x1536.png 1300w, https://dash-resources.com/wp-content/uploads/2025/02/image-5-1320x1560.png 1320w" sizes="auto, (max-width: 1385px) 100vw, 1385px" /><figcaption class="wp-element-caption">We homogenized the buttons, so that only the most important (export data) stands out. By their size and color, the user now guesses instantly what are the most importants buttons.</figcaption></figure>



<p>Small details make a big difference!</p>



<h2 id='remember-to-style-charts-too'  id="boomdevs_7" class="wp-block-heading" >Remember to style charts too</h2>



<p>By default, Plotly charts do not inherit the styles of your app. So, be sure to at least set the font style and colors to match your dashboard.</p>



<p>Also, tweak chart elements like gridlines, legend placement, and axis labels to keep the same spacing you used before (to keep things homogeneous!).</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1391" height="1755" src="https://dash-resources.com/wp-content/uploads/2025/02/image-7.png" alt="Illustration: the same dashboard with better chart styling." class="wp-image-716" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-7.png 1391w, https://dash-resources.com/wp-content/uploads/2025/02/image-7-238x300.png 238w, https://dash-resources.com/wp-content/uploads/2025/02/image-7-812x1024.png 812w, https://dash-resources.com/wp-content/uploads/2025/02/image-7-768x969.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-7-1217x1536.png 1217w, https://dash-resources.com/wp-content/uploads/2025/02/image-7-1320x1665.png 1320w" sizes="auto, (max-width: 1391px) 100vw, 1391px" /><figcaption class="wp-element-caption">We changed the color and the font familiy of the plotly chart to use the same as the rest of the dashboard. You will often need to hardcode the values in Python, in the figure configuration / layout parameters.</figcaption></figure>



<p class="callout">Another <strong>pro tip</strong>: If your dashboard has multiple charts, keep them consistent in terms of styling. Same font, same color scheme, same layout rules—it all helps with clarity and makes your dashboard look like a professionally designed tool.</p>



<h2 id='use-pre-made-components'  id="boomdevs_8" class="wp-block-heading" >Use pre-made components</h2>



<p>Last but not least: you should use component libraries like <a href="https://dash-bootstrap-components.opensource.faculty.ai/">Dash Bootstrap Components</a> (DBC) or <a href="https://www.dash-mantine-components.com/">Dash Mantine Components</a> (DMC). These libraries come with a lot of default CSS rules and choices, a technic known as a <a href="https://meyerweb.com/eric/tools/css/reset/">CSS reset</a>.</p>



<p>These rules ensure that HTML elements are correctly placed, well-spaced, have the right font sizes, and maintain a consistent, homogeneous style. Exactly everything we did before, so it will make you gain time.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1377" height="2113" src="https://dash-resources.com/wp-content/uploads/2025/02/image-8.png" alt="Illustration: the same dashboard with pre made component library." class="wp-image-717" srcset="https://dash-resources.com/wp-content/uploads/2025/02/image-8.png 1377w, https://dash-resources.com/wp-content/uploads/2025/02/image-8-196x300.png 196w, https://dash-resources.com/wp-content/uploads/2025/02/image-8-667x1024.png 667w, https://dash-resources.com/wp-content/uploads/2025/02/image-8-768x1178.png 768w, https://dash-resources.com/wp-content/uploads/2025/02/image-8-1001x1536.png 1001w, https://dash-resources.com/wp-content/uploads/2025/02/image-8-1335x2048.png 1335w, https://dash-resources.com/wp-content/uploads/2025/02/image-8-1320x2026.png 1320w" sizes="auto, (max-width: 1377px) 100vw, 1377px" /><figcaption class="wp-element-caption">We applied the Mantine components styles to our dashboard: buttons are different, and containers have some remarkable shadow. The good thing is that we don’t need all the previous steps to get to this one.</figcaption></figure>



<p>However, these component libraries are popular which is a good and a bad thing:</p>



<ul class="wp-block-list">
<li>The good: <strong>people like them</strong>. Remember, users don’t want anything too crazy—they prefer familiar, intuitive designs. They will like your design because they are used to it.</li>



<li>The bad: <strong>all websites look the same</strong>. However, with the choices you have (layout, colors, fonts, etc.), you can still make your dashboard unique while keeping it aesthetically pleasant.</li>



<li></li>
</ul>



<p class="callout"><strong>My 2 cents:</strong> I always use at least DBC or Mantine in my Dash projects, whether I create custom components or not, because they bring a CSS reset and a lot of CSS utils.</p>



<h3 id='see-also'  id="boomdevs_9" class="wp-block-heading" >See also:</h3>



<ul class="wp-block-list">
<li><a href="https://dash-resources.com/writing-in-process/">How to use Dash Bootstrap Components (soon)</a></li>



<li><a href="https://dash-resources.com/writing-in-process/?article=dash-mantine-components-tutorial">How to use Dash Mantine Components (soon)</a></li>
</ul>



<h2 id='conclusion'  id="boomdevs_10" class="wp-block-heading" >Conclusion</h2>



<p>Let&#8217;s wrap this up with an important reminder: <strong>these aren&#8217;t strict commandments set in stone</strong>. Think of them more as helpful guidelines that, when mixed and matched thoughtfully, can lead to visually appealing dashboards. You don&#8217;t need to follow every single rule.</p>



<p>The best way to improve your design skills is to <strong>experiment and stay observant</strong>. Pay attention to the apps and tools you enjoy using. What makes them pleasant to work with? How do they handle fonts, colors, and spacing? <strong>Take inspiration</strong> from what works well in your favorite applications.</p>



<p>And here&#8217;s a final thought: people tend to find &#8220;normal&#8221; things beautiful.</p>



<ul class="wp-block-list">
<li><strong>You don&#8217;t need to reinvent the wheel </strong>or create the most unique dashboard ever seen. Aim for clean, familiar, and professional rather than wildly original.</li>



<li>A “dash” <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f643.png" alt="🙃" class="wp-smiley" style="height: 1em; max-height: 1em;" /> of creativity is great, but remember that if your dashboard <strong>looks too exotic</strong>, it might end up being harder to use!</li>
</ul>



<p>The goal is simply to create something that looks professional and is pleasant to use.</p>



<p>Follow these guidelines, trust your eye, and you&#8217;ll be well on your way to creating dashboards that both you and your users will enjoy.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>I hope you liked this guide and learn. This is part of my <a href="https://dash-resources.com/dash-plotly-course/">Dash plotly Course</a>, be sure to check it out if you want to learn more about building dashboards. </p>



<p>If you have any question, join us on the dedicated topic on Plotly’s forum: <a href="https://community.plotly.com/t/a-guide-to-beautiful-dashboards-basic-design-principles-dash-resources-com/90495/4">here</a>. </p>
<p>L’article <a href="https://dash-resources.com/a-guide-to-beautiful-dashboards-basic-design-principles/">A guide to beautiful Dashboards (basic design principles)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to animate Dash Graphs</title>
		<link>https://dash-resources.com/how-to-animate-dash-graphs/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Sun, 02 Feb 2025 21:47:38 +0000</pubDate>
				<category><![CDATA[Examples]]></category>
		<category><![CDATA[graph]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?page_id=659</guid>

					<description><![CDATA[<p>The animate and animation_options parameters in Dash&#8217;s Graph component control how your visualizations transition between updates. These parameters are essential for creating smooth, interactive data [...]</p>
<p>L’article <a href="https://dash-resources.com/how-to-animate-dash-graphs/">How to animate Dash Graphs</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="breadcrumbs align  wp-block-bcn-breadcrumb-trail has-text-color has-background" vocab="https://schema.org/" typeof="BreadcrumbList">
	<span></span>
	</div>



<h1 id=''  id="boomdevs_1" class="wp-block-heading is-style-default" ></h1>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#how-do-animations-work">How do animations work?</a>
      </li>
      <li>
        <a href="#official-documentation">Official Documentation</a>
        <ul class="menu_level_2">
          <li class="first last">
            <a href="#parameter-values">Parameter Values:</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#code-examples-and-use-cases">Code Examples and Use Cases</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#1-basic-animation-animate=true">1. Basic Animation (animate=True)</a>
          </li>
          <li class="last">
            <a href="#2-customized-animation-with-options">2. Customized Animation with Options</a>
          </li>
        </ul>
      </li>
      <li class="last">
        <a href="#best-practices-tips">Best Practices &amp; Tips</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<p>The <code>animate</code> and <code>animation_options</code> parameters in Dash&#8217;s Graph component control how your visualizations transition between updates. These parameters are essential for creating smooth, interactive data visualizations that respond to user input or real-time data updates.</p>



<h2 id='how-do-animations-work'  id="boomdevs_2" class="wp-block-heading" >How do animations work?</h2>



<p>When enabled, animations create smooth transitions between graph updates instead of abrupt changes. This includes transitions for data points, axes ranges, and other visual elements. The animations can be customized using <code>animation_options</code> to control timing, easing, and transition modes.</p>



<h2 id='official-documentation'  id="boomdevs_3" class="wp-block-heading" >Official Documentation</h2>



<p>From the <a href="https://dash.plotly.com/dash-core-components/graph#graph-properties">documentation</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong><code>animate</code></strong> (<em>boolean</em>; default <code>False</code>): Beta: If True, animate between updates using plotly.js&#8217;s <code>animate</code> function.</p>



<p><strong><code>animation_options</code></strong> (<em>dict</em>; default <code>{ frame: { redraw: False, }, transition: { duration: 750, ease: 'cubic-in-out', },}</code>): Beta: Object containing animation settings. Only applies if <code>animate</code> is <code>True</code>.</p>
</blockquote>



<h3 id='parameter-values'  id="boomdevs_4" class="wp-block-heading" >Parameter Values:</h3>



<h4 id='animate'  id="boomdevs_5" class="wp-block-heading" >animate</h4>



<ol class="wp-block-list">
<li><code>True</code>: Enables automatic animations for all updates</li>



<li><code>False</code> (default): Disables animations</li>
</ol>



<h4 id='animate-options'  id="boomdevs_6" class="wp-block-heading" >animate_options</h4>



<p>The <code>animate_options</code> dictionary can include:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">{
    'frame': {
        'redraw': True    # Whether to redraw the entire graph
    },
    'transition': {
        'duration': 500,  # Duration of transition between frames
        'easing': 'cubic-in-out'  # Easing function to use
    },
    'mode': 'immediate'  # Animation mode
}</code></pre>



<p>The documentation do not say exactly what <code>redraw</code> does. As its impact is unclear, we recommend keeping it to <code>True</code>. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Defining <code>redraw</code>: Setting <code>redraw: false</code> is an optimization for scatter plots so that animate just makes changes without redrawing the whole plot. For other plot types, such as contour plots, every frame <strong>must</strong> be a total plot redraw, i.e. <code>redraw: true</code>.</p>
</blockquote>



<p class="callout"><strong>Good to know</strong>: under the hood, the plotly charting library is handling animations. You will therefore find more information on this page: <a href="https://plotly.com/python/animations/">https://plotly.com/python/animations/</a>.</p>



<p></p>



<h2 id='code-examples-and-use-cases'  id="boomdevs_7" class="wp-block-heading" >Code Examples and Use Cases</h2>



<h3 id='1-basic-animation-animate=true'  id="boomdevs_8" class="wp-block-heading" >1. Basic Animation (animate=True)</h3>



<p>Use this for simple animations with default settings:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">html.Div([
    dcc.Graph(
        animate=True,
        figure=base_figure,
        style={'height': '100%'}
    )
], style={
    'width': '100%',
    'height': '400px',
    'border': '1px solid #ddd',
    'padding': '10px'
})</code></pre>



<iframe loading="lazy" src="https://scripts.dash-resources.com/graph/animate1.py/" width="100%" height="500" frameBorder="0"></iframe>



<h3 id='2-customized-animation-with-options'  id="boomdevs_9" class="wp-block-heading" >2. Customized Animation with Options</h3>



<p>Use this when you need precise control over animation timing and behavior:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Example of linear, 2s animation
html.Div([
    dcc.Graph(
        figure=fig
        animate=True,
        animation_options={
            'transition': {
                'duration': 500,
                'easing': 'linear'
            }
        },
    )
])</code></pre>



<iframe loading="lazy" src="https://scripts.dash-resources.com/graph/animate2.py/" width="100%" height="600" frameBorder="0"></iframe>



<p>The easing parameter will accept all following values (<a href="https://plotly.com/python/reference/layout/#layout-transition-easing">source</a>):</p>



<p><code>"linear"</code> | <code>"quad"</code> | <code>"cubic"</code> | <code>"sin"</code> | <code>"exp"</code> | <code>"circle"</code> | <code>"elastic"</code> | <code>"back"</code> | <code>"bounce"</code> | <code>"linear-in"</code> | <code>"quad-in"</code> | <code>"cubic-in"</code> | <code>"sin-in"</code> | <code>"exp-in"</code> | <code>"circle-in"</code> | <code>"elastic-in"</code> | <code>"back-in"</code> | <code>"bounce-in"</code> | <code>"linear-out"</code> | <code>"quad-out"</code> | <code>"cubic-out"</code> | <code>"sin-out"</code> | <code>"exp-out"</code> | <code>"circle-out"</code> | <code>"elastic-out"</code> | <code>"back-out"</code> | <code>"bounce-out"</code> | <code>"linear-in-out"</code> | <code>"quad-in-out"</code> | <code>"cubic-in-out"</code> | <code>"sin-in-out"</code> | <code>"exp-in-out"</code> | <code>"circle-in-out"</code> | <code>"elastic-in-out"</code> | <code>"back-in-out"</code> | <code>"bounce-in-out"</code></p>



<h2 id='best-practices-tips'  id="boomdevs_10" class="wp-block-heading" >Best Practices &amp; Tips</h2>



<ul class="wp-block-list">
<li>Keep transitions under 1000ms for optimal user experience (150-300ms are good!).</li>



<li>Remember to update the figure layout (x_min, x_max, &#8230;) if you add new points or modify data, otherwise it might not become visible.</li>
</ul>



<p></p>
<p>L’article <a href="https://dash-resources.com/how-to-animate-dash-graphs/">How to animate Dash Graphs</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>How to make responsive Dash Graphs</title>
		<link>https://dash-resources.com/how-to-make-responsive-dash-graphs/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Sun, 02 Feb 2025 19:49:35 +0000</pubDate>
				<category><![CDATA[Examples]]></category>
		<category><![CDATA[graph]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?page_id=631</guid>

					<description><![CDATA[<p>How works the responsive parameter? The responsive parameter in Dash&#8217;s Graph component controls how your graph responds to window resizing and container element changes. This [...]</p>
<p>L’article <a href="https://dash-resources.com/how-to-make-responsive-dash-graphs/">How to make responsive Dash Graphs</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[<div class="breadcrumbs align  wp-block-bcn-breadcrumb-trail has-text-color has-background" vocab="https://schema.org/" typeof="BreadcrumbList">
	<span></span>
	</div>


        
            
            <div class="fit_content">
                <div class="bd_toc_container" data-fixedWidth="">
                    <div class="bd_toc_wrapper" data-wrapperPadding="48px">
                        <div class="bd_toc_wrapper_item">
                            <div class="bd_toc_header active" data-headerPadding="2px">
                                <div class="bd_toc_header_title">
                                    Table of Contents                                </div>
                                <div class="bd_toc_switcher_hide_show_icon">
                                    <span class="bd_toc_arrow"></span>                                </div>
                            </div>
                            <div class="bd_toc_content list-type-disc">
                                <div class="bd_toc_content_list ">
                                    <div class='bd_toc_content_list_item'>    <ul>
      <li class="first">
        <a href="#how-works-the-responsive-parameter">How works the responsive parameter?</a>
      </li>
      <li>
        <a href="#official-documentation">Official documentation</a>
        <ul class="menu_level_2">
          <li class="first last">
            <a href="#parameter-values">Parameter Values:</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#code-examples-and-use-cases">Code Examples and Use Cases</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#1-fully-responsive-graph-responsive=true">1. Fully Responsive Graph (responsive=True)</a>
          </li>
          <li>
            <a href="#2-non-responsive-graph-responsive=false">2. Non-Responsive Graph (responsive=False)</a>
          </li>
          <li>
            <a href="#3-auto-responsive-graph-responsive=-auto">3. Auto-Responsive Graph (responsive=&#039;auto&#039;)</a>
          </li>
          <li class="last">
            <a href="#4-responsive-with-constraints">4. Responsive with Constraints</a>
          </li>
        </ul>
      </li>
      <li class="last">
        <a href="#best-practices-tips">Best practices &amp; tips</a>
      </li>
    </ul>
</div>                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="layout_toggle_button">
                        <span class="bd_toc_arrow"></span>
                    </div>
                </div>
            </div>




<h2 id='how-works-the-responsive-parameter'  id="boomdevs_1" class="wp-block-heading" >How works the responsive parameter?</h2>



<p>The <code>responsive</code> parameter in Dash&#8217;s Graph component controls how your graph responds to window resizing and container element changes. This is crucial for creating graphs that work well across different screen sizes and layouts.</p>



<h2 id='official-documentation'  id="boomdevs_2" class="wp-block-heading" >Official documentation</h2>



<p>From the <a href="https://dash.plotly.com/dash-core-components/graph">documentation</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong><code>responsive</code></strong> (<em>a value equal to: true, false or &#8216;auto&#8217;</em>; default <code>'auto'</code>): If True, the Plotly.js plot will be fully responsive to window resize and parent element resize event. This is achieved by overriding <code>config.responsive</code> to True, <code>figure.layout.autosize</code> to True and unsetting <code>figure.layout.height</code> and <code>figure.layout.width</code>. If False, the Plotly.js plot not be responsive to window resize and parent element resize event. This is achieved by overriding <code>config.responsive</code> to False and <code>figure.layout.autosize</code> to False. If &#8216;auto&#8217; (default), the Graph will determine if the Plotly.js plot can be made fully responsive (True) or not (False) based on the values in <code>config.responsive</code>, <code>figure.layout.autosize</code>, <code>figure.layout.height</code>, <code>figure.layout.width</code>. This is the legacy behavior of the Graph component. Needs to be combined with appropriate dimension / styling through the <code>style</code> prop to fully take effect.</p>
</blockquote>



<h3 id='parameter-values'  id="boomdevs_3" class="wp-block-heading" >Parameter Values:</h3>



<ol class="wp-block-list">
<li><code>True</code>: Forces the graph to be fully responsive</li>



<li><code>False</code>: Disables responsiveness</li>



<li><code>'auto'</code> (default): Determines responsiveness based on other settings</li>
</ol>



<h2 id='code-examples-and-use-cases'  id="boomdevs_4" class="wp-block-heading" >Code Examples and Use Cases</h2>



<h3 id='1-fully-responsive-graph-responsive=true'  id="boomdevs_5" class="wp-block-heading" >1. Fully Responsive Graph (responsive=True)</h3>



<p>Use this when you want your graph to always adapt to its container size, horizontally and vertically:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Container with fluid width
html.Div([ 
      dcc.Graph(
          responsive=True,
          figure=base_figure, 
          style={'height': '100%'}  # Necessary to adapt vertically
      )
  ], style={
      'width': '100%',  # Fluid width
      'height': '400px',
      'border': '2px solid #2196F3',
      'padding': '10px',
      'margin': '10px 0'
  })
</code></pre>



<iframe loading="lazy" src="https://scripts.dash-resources.com/graph/responsive1.py/" width="100%" height="700" frameBorder="0"></iframe>



<p>In most cases, the container should have a fixed height to make this work. You can however make the container height 100% of its own height using CSS flexbox.</p>



<h3 id='2-non-responsive-graph-responsive=false'  id="boomdevs_6" class="wp-block-heading" >2. Non-Responsive Graph (responsive=False)</h3>



<p>Use this when you need fixed dimensions regardless of screen size:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Container with fixed width
html.Div([
    dcc.Graph(
        responsive=False,
        figure={
            'data': fig['data'],
            'layout': {
                'width': 500,  # Fixed width
                'height': 300  # Fixed height
            }
        }
    )
], style={
    'width': '800px',
    'height': '400px',
    'border': '2px solid #4CAF50',
    'padding': '10px'
})
</code></pre>



<iframe loading="lazy" src="https://scripts.dash-resources.com/graph/responsive2.py/" width="100%" height="700" frameBorder="0"></iframe>



<p>Note that in this case, the graph will take the default dimensions in the figure layout. The container should have greater width and heights than the figure, or the graph will overflow.</p>



<p>Setting CSS style with <code>width</code> and <code>height</code> properties on the graph will have the negative effect of making the container believe that your graph is smaller or larger than what it is really.</p>



<h3 id='3-auto-responsive-graph-responsive=-auto'  id="boomdevs_7" class="wp-block-heading" >3. Auto-Responsive Graph (responsive=&#8217;auto&#8217;)</h3>



<p>Use this default setting when you want the graph to determine responsiveness based on its layout settings:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Container with max-width constraint
html.Div([
    dcc.Graph(
        responsive='auto',
        figure={
            'data': fig['data'],
            'layout': {
                'title': 'Auto-Responsive Graph',
                'autosize': True,
                'height': 350  # Fixed height only
            }
        }
    )
], style={
    'width': '100%',
    'height': '400px',
    'border': '2px solid #FF9800',
    'padding': '10px'
})
</code></pre>



<iframe loading="lazy" src="https://scripts.dash-resources.com/graph/responsive3.py/" width="100%" height="700" frameBorder="0"></iframe>



<p>In this example, the graph is horizontally responsive but not vertically responsive as its height is fixed in the figure layout.</p>



<p>The &#8216;auto&#8217; setting essentially lets you have fine-grained control over what aspects of your graph should be responsive while preserving other dimensional constraints you&#8217;ve set. Using <code>responsive=True</code> or <code>False</code> is often clearer.</p>



<h3 id='4-responsive-with-constraints'  id="boomdevs_8" class="wp-block-heading" >4. Responsive with Constraints</h3>



<p>Use this when you want responsiveness within certain bounds:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Container with min/max constraints
html.Div([
    dcc.Graph(
        responsive=True,
        figure=fig
    )
], style={
    'width': '100%',
    'minWidth': '200px',
    'maxWidth': '500px',
    'height': '400px',
    'border': '2px solid #E91E63',
    'padding': '10px'
})
</code></pre>



<iframe loading="lazy" src="https://scripts.dash-resources.com/graph/responsive4.py/" width="100%" height="700" frameBorder="0"></iframe>



<p>This case differs from the first one in the sense that 1) the graph height is not fluid and 2) there are maximum and minimum width specified on the container.</p>



<h2 id='best-practices-tips'  id="boomdevs_9" class="wp-block-heading" >Best practices &amp; tips</h2>



<ul class="wp-block-list">
<li>If you want the graph to have the same height as the container: always explicit the container’s height, otherwise setting <code>height: 100%</code> on the graph will not work.</li>



<li>To simplify things, keep the graph has the only child of your container.</li>



<li>Add padding to prevent graph from touching container edges, use the CSS <code>overflow</code> if you need to hide the overflowing content or make scrollable container.</li>



<li>Use percentage widths for fluid layouts, combined with CSS properties like <code>max-width</code> and <code>max-height</code>.</li>
</ul>



<p></p>
<p>L’article <a href="https://dash-resources.com/how-to-make-responsive-dash-graphs/">How to make responsive Dash Graphs</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
