<?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>Archives des Beginner level | dash-resources.com</title>
	<atom:link href="https://dash-resources.com/category/beginner-level/feed/" rel="self" type="application/rss+xml" />
	<link>https://dash-resources.com/category/beginner-level/</link>
	<description>Learn to build interactive web applications with Python and Dash plotly</description>
	<lastBuildDate>Wed, 18 Feb 2026 09:08:44 +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>Archives des Beginner level | dash-resources.com</title>
	<link>https://dash-resources.com/category/beginner-level/</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>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>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>Build a chatbot web app under 5min in Python</title>
		<link>https://dash-resources.com/build-a-chatbot-web-app-under-5min-in-python/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Sat, 25 Jan 2025 15:45:44 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=576</guid>

					<description><![CDATA[<p>In this tutorial, we&#8217;ll build a ChatGPT-like web application using Dash and OpenAI&#8217;s GPT models. We will create a simple but powerful interface that allows [...]</p>
<p>L’article <a href="https://dash-resources.com/build-a-chatbot-web-app-under-5min-in-python/">Build a chatbot web app under 5min in Python</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&#8217;ll build a ChatGPT-like web application using Dash and OpenAI&#8217;s GPT models. We will create a simple but powerful interface that allows users to interact with an AI assistant, with persistent message storage using browser local storage.</p>



<p>You&#8217;ll find below a <a href="#app-video">demo video</a> of the app.</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="#prerequisites">Prerequisites</a>
      </li>
      <li>
        <a href="#step-1-setting-up-the-environment">Step 1: Setting up the Environment</a>
      </li>
      <li>
        <a href="#step-2-creating-the-application-layout">Step 2: Creating the application layout</a>
      </li>
      <li>
        <a href="#step-3-implementing-the-chat-callback">Step 3: Implementing the chat callback</a>
      </li>
      <li>
        <a href="#step-4-running-the-app">Step 4: Running the app</a>
      </li>
      <li>
        <a href="#full-code">Full code</a>
      </li>
      <li>
        <a href="#going-further">Going further</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 get started!</p>



<h2 id='prerequisites'  id="boomdevs_1" class="wp-block-heading" >Prerequisites</h2>



<p>Before we begin, make sure you have Python 3.9 or later installed. We then need the following:</p>



<ul class="wp-block-list">
<li><em>Dash Plotly</em>: the best Python framework for building interactive web apps and dashboards;</li>



<li><em>Dash-chat component</em>: a community-supported tool for creating chat user interfaces (UI);</li>



<li><em>OpenAI package</em>: used to retrieve responses from OpenAI GPT models.</li>
</ul>



<p class="callout"><strong>Important</strong> : you&#8217;ll need an OpenAI API key to run the following code. You can get one from the <a href="https://platform.openai.com/">OpenAI platform</a>.</p>



<h2 id='step-1-setting-up-the-environment'  id="boomdevs_2" class="wp-block-heading" >Step 1: Setting up the Environment</h2>



<p>First, let’s install the packages using <code>pip</code>:</p>



<pre class="wp-block-code"><code lang="bash" class="language-bash">pip install dash dash-chat openai</code></pre>



<p>Then, let&#8217;s import our required packages and set up our OpenAI client:</p>



<pre class="wp-block-code"><code lang="Python" class="language-Python">import os
import dash
from dash import callback, html, Input, Output, State, dcc
from dash_chat import ChatComponent
from openai import OpenAI

# Initialize OpenAI client
api_key = os.environ.get("OPENAI_API_KEY")
client = OpenAI(api_key=api_key)</code></pre>



<p>Note: We&#8217;re getting the API key from environment variables for security. It’s a best practice not to hardcode keys in your code to avoid sharing it by mistaking.</p>



<h2 id='step-2-creating-the-application-layout'  id="boomdevs_3" class="wp-block-heading" >Step 2: Creating the application layout</h2>



<p>Next, we&#8217;ll create our Dash application and define its layout:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">app = dash.Dash(__name__)

# Define default messages for the chat
default_messages = [
    {"role": "assistant", "content": "Hello!"},
]

app.layout = html.Div(
    [
        ChatComponent(
            id="chat-component",
            messages=[]  # Initialize empty since we'll load from storage
        ),
        
        # The store component help use save messages
        dcc.Store("chat-memory", data=default_messages, storage_type="local"),
    ],
    
    # Some basic CSS styling for the app
    style={
        "max-width": "800px",
        "margin": "0 auto",
        "font-family": "Arial, sans-serif",
        "padding": "20px",
    }
)
</code></pre>



<p>The Dash Chat component follows the same structure as the OpenAI conversation API with <code>role</code> and <code>content</code>. The AI messages will have <code>role="assistant"</code> while the user messages will have <code>role="user"</code>.</p>



<p>Instead of passing the messages directly to the <code>ChatComponent</code>, we save them into a <code>dcc.Store</code> component. This component will enable us to persist the messages in the browser’s session: the discussion will still show up if we reload the page, but will be cleared if we close the tab/browser.</p>



<p class="callout"><strong>Good to know</strong>: setting storage type to &#8220;local&#8221; enable saving the discussion even when the browser is closed. You might want to create a reset button if you do so!</p>



<p>We also added basic CSS styling for the app. In Dash, you can either add styling with the <code>style</code> parameters on most components or include your styles in an external stylesheet (see more).</p>



<h2 id='step-3-implementing-the-chat-callback'  id="boomdevs_4" class="wp-block-heading" >Step 3: Implementing the chat callback</h2>



<p>Callbacks make Dash applications interactive by linking <code>Input</code> components (user actions) to <code>Output</code> components (updates in the app) while handling logic on the server:</p>



<ul class="wp-block-list">
<li><strong>Input</strong> triggers a callback based on user actions, such as typing a message in the chat.</li>



<li><strong>Output</strong> defines what part of the app updates, like refreshing the chat interface with new messages.</li>



<li><strong>State</strong> retrieves current values without triggering a callback, useful for accessing stored data like chat history.</li>
</ul>



<p>Here’s how it works:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@callback(
    Output("chat-component", "messages"),
    Output("chat-memory", "data"),
    Input("chat-component", "new_message"),
    State("chat-memory", "data")
)
def handle_chat(new_message, messages):
    # If new_message is None, just return the stored messages
    # This is run at page load
    if not new_message:
        return messages, messages

    # If we have a user message, concatenate it to the list of messages
    updated_messages = messages + [new_message]

    # If the new message comes from the user, trigger the OpenAI API
    if new_message["role"] == "user":
        # We use the OpenAI completion API to get an answer to the user message
        response = client.chat.completions.create(
            model="gpt-4",
            messages=updated_messages,
        )
        bot_response = {
            "role": "assistant",
            "content": response.choices[0].message.content.strip()
        }

        # Append the new message to the message list
        updated_messages += [bot_response]

    # We update both the chat component and the chat memory
    return updated_messages, updated_messages
</code></pre>



<p>In this case, our input is the chat user input, provided by dash-chat component. The processing achieved by the callback will involve retrieving an answer from the OpenAI API, and the output is the updated list of messages.</p>



<p>Straightforward and simple <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 class="callout"><strong>Learn more</strong> on callbacks with this tutorial: <a href="https://dash-resources.com/tutorial-dash-callbacks-schemas-examples/">Dash callbacks, schemas and examples.</a></p>



<h2 id='step-4-running-the-app'  id="boomdevs_5" class="wp-block-heading" >Step 4: Running the app</h2>



<p>We finally add the code to run our server:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">if __name__ == "__main__":
    app.run_server(debug=True)
</code></pre>



<p>Here is the result:</p>



<figure id="app-video" class="wp-block-video"><video height="1124" style="aspect-ratio: 1442 / 1124;" width="1442" controls src="https://dash-resources.com/wp-content/uploads/2025/01/dash-chat.mp4"></video></figure>



<p>You can try reloading the page; you’ll see the chat output is kept. But if you close the tab, a new session will be created next time.</p>



<h2 id='full-code'  id="boomdevs_6" class="wp-block-heading" >Full code</h2>



<p>You can download the full code below, with guided instructions on how to run the app:</p>



                <div class="ml-embedded" data-form="FTXAyP"></div>
            



<h2 id='going-further'  id="boomdevs_7" class="wp-block-heading" >Going further</h2>



<p>Here are a few ideas to improve this chat app:</p>



<ul class="wp-block-list">
<li>add a &#8220;clear chat&#8221; button that triggers a callback which reset the messages sent ;</li>



<li>add an input to join a file, so that the AI can answer based on content ;</li>



<li>save messages in a proper database instead of browser storage.</li>
</ul>



<p>The Dash Chat component is a recent work in progress, so be sure to check out the coming updates <a href="https://community.plotly.com/t/dash-chat-component/89514">here</a>.</p>



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



<p>I hope this short tutorial convinced you how easy and beginner-friendly it is to build a chatbot app with Dash plotly! With just a few lines of Python, you can create an interactive, AI-powered chat interface that works seamlessly in the browser.</p>



<p>Dash plotly can be used for both small data apps, AI apps, dashboard or real SaaS applications. It makes it easy to connect frontend and backend with pure Python. Check-out all the examples on plotly’s website: <a href="https://plotly.com/examples/">https://plotly.com/examples/</a></p>



<p>If you want more inspiration, here are a few other apps:</p>



<ul class="wp-block-list">
<li><a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-1-3/">A classic Todo app</a>: A simple yet powerful app to manage your daily tasks, showcasing Dash’s flexibility for non-AI use cases.</li>



<li><a href="https://quizdash.onrender.com">QuizDash</a>: A fun app that generates quizzes on any topic using the OpenAI API.</li>



<li><a href="https://stocktistics.com/stocksaavy">StockSaavy</a>: An AI powered chat-agent that answers questions about recent news articles pertaining to stocks in the S&amp;P500.</li>
</ul>



<p>Happy coding! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f44b.png" alt="👋" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>
<p>L’article <a href="https://dash-resources.com/build-a-chatbot-web-app-under-5min-in-python/">Build a chatbot web app under 5min in Python</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/01/dash-chat.mp4" length="1513612" type="video/mp4" />

			</item>
		<item>
		<title>How to create tables in plotly Dash (dbc.Table, DataTable, AG Grid comparison)</title>
		<link>https://dash-resources.com/dash-table-datatable-ag-grid-comparison/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Tue, 14 Jan 2025 11:00:22 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[ag grid]]></category>
		<category><![CDATA[datatable]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=476</guid>

					<description><![CDATA[<p>Are you working with excel/CSV data and looking to display it in your Dash application? This comprehensive guide will walk you through all the available [...]</p>
<p>L’article <a href="https://dash-resources.com/dash-table-datatable-ag-grid-comparison/">How to create tables in plotly Dash (dbc.Table, DataTable, AG Grid comparison)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Are you working with excel/CSV data and looking to display it in your Dash application? This comprehensive guide will walk you through all the available options for creating tables in Dash, from the simplest to the most advanced solutions.</p>



<p>There are <strong>three main approaches to display tables</strong>, each with its own strengths:</p>



<ol class="wp-block-list">
<li><code>dbc.Table.from_dataframe</code>: A simple, static table from Dash Bootstrap Components.</li>



<li><code>dash_table.DataTable</code>: The built-in interactive table component by Plotly.</li>



<li><code>dash-ag-grid</code>: A powerful wrapper around AG Grid for advanced functionality.</li>
</ol>


        
            
            <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="#1-quick-start-the-bootstrap-table-approach">1. Quick Start: The Bootstrap Table Approach</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#styling-with-dbc-table">Styling with dbc.Table</a>
          </li>
          <li class="last">
            <a href="#why-choose-bootstrap-table">Why choose Bootstrap Table?</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#2-interactive-tables-with-dash-datatable">2. Interactive Tables with Dash DataTable</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#styling-with-datatable">Styling with DataTable</a>
          </li>
          <li class="last">
            <a href="#why-choose-datatable">Why choose dataTable?</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#3-advanced-tables-with-dash-ag-grid">3. Advanced tables with Dash AG Grid</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#styling-with-ag-grid">Styling with AG Grid</a>
          </li>
          <li class="last">
            <a href="#why-choose-ag-grid">Why choose AG Grid?</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#4-not-recommended-approaches">4. Not recommended approaches</a>
      </li>
      <li>
        <a href="#5-feature-comparison-bootstrap-table-vs-datatable-vs-ag-grid">5. Feature comparison: (bootstrap) Table vs DataTable vs AG Grid</a>
      </li>
      <li class="last">
        <a href="#6-conclusion">6. 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='1-quick-start-the-bootstrap-table-approach'  id="boomdevs_1" class="wp-block-heading" >1. Quick Start: The Bootstrap Table Approach</h2>



<p>The simplest way to display a DataFrame as a table is by using <a href="https://dash-bootstrap-components.opensource.faculty.ai/">Dash Bootstrap Components</a>&#8216; <code>Table.from_dataframe</code> function. It offers a clean, professional appearance with responsive design and basic styling options like striped rows, borders, and hover effects. </p>



<p>It requires to install the community package:</p>



<pre class="wp-block-code"><code lang="bash" class="language-bash">pip install dash-bootstrap-components</code></pre>



<p>Here is a an example code loading the titanic dataset and displaying it:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">import dash
from dash import html
import dash_bootstrap_components as dbc
import pandas as pd

# Load Titanic dataset and select subset of columns for clarity
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)
df = df[['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'Fare']]

# Modify columns for better readability
df['Survived'] = df['Survived'].map({0: '<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;" />', 1: '<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;" />'})
df['Fare'] = df['Fare'].map(lambda x: f"${x:.2f}")

# Create the Dash app
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

# Generate the table using dbc.Table.from_dataframe
table = dbc.Table.from_dataframe(
    df,
    # Key styling options:
    striped=True,
    bordered=True,
    hover=True,
)

# Define the app layout
app.layout = html.Div([table])

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



<p>Try it below (or <a href="https://scripts.dash-resources.com/tables/app_dbc1.py/">click here</a>):</p>



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



<p>As you can see, we get a pretty decent table for consulting data. But <strong>Bootstrap tables are static</strong>: you cannot filter rows or sort column values. Other advanced features like pagination or cell editing (that come built-in with DataTable or AG Grid) are nonexistent, and implementing these would require significant development effort.</p>



<p>However, the Bootstrap table makes it easy to embed any Dash element inside each cell, giving you complete control over the content (e.g. adding a button or link or image in a cell is as simple as for anywhere in your dash app layout).</p>



<p class="callout"><strong>Note</strong>: under the hood, <code>dbc.Table.from_dataframe</code> is just building a table as we could do we <code>html.Table</code>, <code>html.Tr</code>, <code>html.Td</code>. It&#8217;s just an easier way to produce the tanle from a DataFrame plus having a good styling by default.</p>



<h3 id='styling-with-dbc-table'  id="boomdevs_2" class="wp-block-heading" >Styling with dbc.Table</h3>



<p>It is easy to use CSS to style Dash Bootstrap tables. It is also possible to modify the table created to add styles to each cell, or each row.</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Modify the 'Survived' cells to apply conditional styling
for i, tr in enumerate(table.children[1].children):  # Iterate over rows (tbody &gt; tr)
    for td in tr.children:  # Iterate over cells (td)
        if df.iloc[i]['Survived'] == '<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.style = {'color': 'red'}</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1798" height="676" src="https://dash-resources.com/wp-content/uploads/2025/01/image-6.png" alt="Illustration: adding styles with dbc.Table." class="wp-image-502" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-6.png 1798w, https://dash-resources.com/wp-content/uploads/2025/01/image-6-300x113.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-6-1024x385.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-6-768x289.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-6-1536x577.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-6-1320x496.png 1320w" sizes="auto, (max-width: 1798px) 100vw, 1798px" /><figcaption class="wp-element-caption">Illustration: adding styles with dbc.Table.</figcaption></figure>



<p>This way, we can easily implement our own styling rules on the generated table, without having to know extensive parameters (which you&#8217;ll see, is not the case for DataTable and AG Grid).</p>



<h3 id='why-choose-bootstrap-table'  id="boomdevs_3" class="wp-block-heading" >Why choose Bootstrap Table?</h3>



<p>You should start with a Dash Bootstrap table when your data does not require filtering or sorting features, and if you plan to display a small dataset (i.e. &lt; 100 rows). </p>



<p>It’s also maybe the easiest choice if you plan to mix the table content with other Dash components (checkboxes, dropdowns, buttons, etc.).</p>



<h2 id='2-interactive-tables-with-dash-datatable'  id="boomdevs_4" class="wp-block-heading" >2. Interactive Tables with Dash DataTable</h2>



<p>When you need interactivity, Dash&#8217;s built-in <a href="https://dash.plotly.com/datatable">DataTable</a> provides an excellent middle ground.</p>



<p>It comes included with the <code>dash</code> package and offers many features that work out-of-the-box, including editing cells, sorting, filtering, pagination, and column resizing.</p>



<pre class="wp-block-code"><code lang="python" class="language-python">from dash import Dash, dash_table
import pandas as pd

app = Dash(__name__)

# Load Titanic dataset and select subset of columns for clarity
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)
df = df[['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'Fare']]

# Modify columns for better readability
# Notice that Fare is formatted inside the DataTable component
df['Survived'] = df['Survived'].map({0: '<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;" />', 1: '<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;" />'})

# Create the DataTable
app.layout = dash_table.DataTable(
    data=df.to_dict('records'),

    # Note: columns parameter is mandatory
    columns=[
        {'name': 'Passenger ID', 'id': 'PassengerId', 'type': 'numeric', 'selectable': True, 'clearable': True, 'editable': True},
        {'name': 'Survived', 'id': 'Survived', 'type': 'numeric'},
        {'name': 'Class', 'id': 'Pclass', 'type': 'numeric'},
        {'name': 'Name', 'id': 'Name', 'type': 'text'},
        {'name': 'Sex', 'id': 'Sex', 'type': 'text'},
        {'name': 'Age', 'id': 'Age', 'type': 'numeric'},
        {'name': 'Fare', 'id': 'Fare', 'type': 'numeric', 'format': {'specifier': '$.2f'}},
    ],
    
    # Enable key features
    editable=True,
    sort_action='native',
    filter_action='native',
    row_selectable="multi",
    row_deletable=True,
    page_size=15,

    # Additional styling
    style_cell={
        'fontFamily': 'Arial',
        'padding': '5px 10px'
    },
    style_header={
        'backgroundColor': 'rgb(230, 230, 230)',
        'fontWeight': 'bold'
    },
)

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



<p>Try to filter and /or order rows below (or <a href="https://scripts.dash-resources.com/tables/app_datatable1.py/">click here</a>):</p>



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



<p><strong>DataTable provides sorting and filtering capabilities out-of-the-box</strong>, either on the client-side (handled by the browser) or the backend (using python, with a callback). These features make it easy to visualize and manage a lot of data.</p>



<p class="callout"><strong>Note: </strong>Using backend pagination and filtering can be particularly advantageous for large datasets. Instead of loading the entire dataset at once, only the required chunks of data are retrieved and processed on the server as needed. This approach reduces load times, minimizes bandwidth usage, and significantly improves performance for end users. A similar approach is <a href="https://dash.plotly.com/datatable/virtualization">Virtual Scrolling </a>(or Infinite Scrolling).</p>



<p>DataTable also provides <a href="https://dash.plotly.com/datatable/editable">editing</a> out-of-the-box, which is not available with Bootstrap tables (unless you handle it yourself). Users can modify the cell content, which can trigger callbacks to update your underlying data.</p>



<p>While DataTable doesn&#8217;t support other Dash components inside cells, it does offer alternatives: you can use markdown for basic styling (bold, italic, &#8230;) and create clickable links. There also are some parameters you can use for <a href="https://dash.plotly.com/datatable/tooltips">display tooltips</a> and embed <a href="https://dash.plotly.com/datatable/dropdowns">dropdowns</a>. But the content you can embed is limited compared to the Dash Bootstrap table.</p>



<h3 id='styling-with-datatable'  id="boomdevs_5" class="wp-block-heading" >Styling with DataTable</h3>



<p>Dash DataTable allows for extensive <a href="https://dash.plotly.com/datatable/style">styling</a> through properties like <code>style_header</code>, <code>style_data</code> and and <code>style_cell</code>. <a href="https://dash.plotly.com/datatable/style#conditional-formatting">Conditional formatting</a> lets you dynamically style cells or rows based on data values:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">    # Style rows based on data
    style_data_conditional=[
        {
            'if': {'filter_query': '{Survived} = "<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;" />"'},
            'color': 'red'
        },
        {
            'if': {'row_index': 'odd'},
            'backgroundColor': 'rgb(220, 220, 220)',
        }
    ],</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1806" height="598" src="https://dash-resources.com/wp-content/uploads/2025/01/image-5.png" alt="Illustration: adding styles with DataTable." class="wp-image-501" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-5.png 1806w, https://dash-resources.com/wp-content/uploads/2025/01/image-5-300x99.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-5-1024x339.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-5-768x254.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-5-1536x509.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-5-1320x437.png 1320w" sizes="auto, (max-width: 1806px) 100vw, 1806px" /><figcaption class="wp-element-caption">Illustration: adding styles with DataTable.</figcaption></figure>



<p>The difference is that styling or formatting is done using datatable parameters. Why? Because the datatable must separate the model (data representation) from the view (how it is displayed) to ensure filtering and sorting capabilities (look at how <code>Fare</code> is formatted in the example).</p>



<h3 id='why-choose-datatable'  id="boomdevs_6" class="wp-block-heading" >Why choose dataTable?</h3>



<p>Go for a DataTable if you need an interactive table, for a few or for many rows. The built-in sorting, filtering, and pagination work seamlessly with backend data processing, making it efficient as well for large datasets.</p>



<p>If you need a table that feels like a spreadsheet but don&#8217;t require the full complexity of AG Grid, DataTable is often the right choice.</p>



<h2 id='3-advanced-tables-with-dash-ag-grid'  id="boomdevs_7" class="wp-block-heading" >3. Advanced tables with Dash AG Grid</h2>



<p>Dash AG Grid is a wrapper around <a href="https://www.ag-grid.com/">AG Grid</a><strong>, a dedicated library for displaying tables</strong>. </p>



<p>As a result, AG Grid offers the most comprehensive feature set, bringing an Excel-like experience to your web application. While it shares many features with DataTable, it excels in advanced functionality like row grouping, pivoting, and tree data structures.</p>



<figure class="wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio"><div class="wp-block-embed__wrapper">
<iframe loading="lazy" title="React Data Grid" width="800" height="450" src="https://www.youtube.com/embed/bcMvTUVbMvI?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
</div></figure>



<p class="callout"><strong>Important note:</strong> AG Grid requires an <a href="https://dash.plotly.com/dash-ag-grid/enterprise-ag-grid">enterprise license</a> to access its most advanced features, such as pivoting, advanced filtering, and enterprise-ready exporting options. However, the main features are accessible in the free, community version.</p>



<pre class="wp-block-code"><code lang="python" class="language-python">import dash
from dash import html
import dash_ag_grid as dag
import pandas as pd

app = dash.Dash(__name__)

# Load Titanic dataset and select subset of columns for clarity
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)
df = df[['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'Fare']]

# Modify columns for better readability
df['Survived'] = df['Survived'].map({0: '<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;" />', 1: '<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;" />'})

columnDefs = [
    {
        'field': 'PassengerId',
        'headerName': 'Passenger ID',
        # Add a checkbox on this column
        'checkboxSelection': True
    },
    {
        'field': 'Survived',
        'width': 100,
    },
    {
        'field': 'Pclass',
        'headerName': 'Class',
        'width': 90
    },
    {
        'field': 'Name',
        'width': 300
    },
    {
        'field': 'Sex',
        'width': 90
    },
    {
        'field': 'Age',
        'width': 90
    },
    {
        'field': 'Fare',
        'width': 100,
        # The formatting requires a JavaScript function to be passed as a string
        'valueFormatter': {'function': 'd3.format("$.2f")(params.value)'}
    },
]

# Create AG Grid component
app.layout = html.Div([
    dag.AgGrid(
        rowData=df.to_dict('records'),
        columnDefs=columnDefs,

        # Enable key features
        defaultColDef={
            'editable': True,
            'resizable': True,
            'sortable': True,
            'filter': True
        },
        dashGridOptions={
            'pagination': True,
            'paginationAutoPageSize': True,
            'animateRows': True,
            'enableRangeSelection': True
        },

        style={"height": "100vh"}  # Take 100% of window height
    )
])

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



<p>Try it below (or <a href="https://scripts.dash-resources.com/tables/app_dag1.py/">click here</a>):</p>



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



<p>As you can see, the AG Grid table comes with a <strong>clean, modern user interface and user experience </strong>(UI/UX). Editable cells, pagination, filtering, ordering, <a href="https://dash.plotly.com/dash-ag-grid/infinite-scroll">infinite scrolling</a> are supported out-of-the-box. It also comes with powerful features like row selection, cell pinning, and row dragging. You can even create advanced layouts like row trees or enable dragging rows between tables.</p>



<p>However, advanced layout customization comes at the price of <strong>complexity</strong>. For example, <a href="https://dash.plotly.com/dash-ag-grid/cell-renderer-components?_gl#example-2:-button-component-with-callback">embedding a button</a> in an AG Grid table is possible but will be a little more complicated than with the Bootstrap option. For instance, AG Grid supports custom components, but implementing them requires knowledge of JavaScript/React.</p>



<p class="callout"><strong>Note</strong>: I could not summerize here all the tabular use cases made possible with AG Grid. I encourage you to visit the extensive <a href="https://dash.plotly.com/dash-ag-grid">Dash AG Grid documentation and examples</a>, it&#8217;s really interesting.</p>



<h3 id='styling-with-ag-grid'  id="boomdevs_8" class="wp-block-heading" >Styling with AG Grid</h3>



<p>AG Grid is really flexible, but the configuration for styling can take some time compared to the other options. You can apply conditional styling to cells, rows, or columns, and even use custom renderers for advanced visual customizations: </p>



<pre class="wp-block-code"><code lang="python" class="language-python">getRowStyle = {
    "styleConditions": [
        {
            "condition": "params.data.Survived === '<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;" />'",
            "style": {"color": "red"},
        },
    ],
}</code></pre>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1810" height="704" src="https://dash-resources.com/wp-content/uploads/2025/01/image-3.png" alt="Illustration: adding styles with AG Grid." class="wp-image-498" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-3.png 1810w, https://dash-resources.com/wp-content/uploads/2025/01/image-3-300x117.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-3-1024x398.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-3-768x299.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-3-1536x597.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-3-1320x513.png 1320w" sizes="auto, (max-width: 1810px) 100vw, 1810px" /><figcaption class="wp-element-caption">Illustration: adding styles with AG Grid.</figcaption></figure>



<p>You will notice the subtle difference: the <code>condition</code> code is now written in JavaScript, not Python. Which makes things a little more complex if you don&#8217;t know the very basics of this language.</p>



<h3 id='why-choose-ag-grid'  id="boomdevs_9" class="wp-block-heading" >Why choose AG Grid?</h3>



<p>AG Grid is the best choice when you&#8217;re building enterprise-level applications or need to replicate desktop spreadsheet functionality in the browser. It&#8217;s particularly valuable when your users are coming from Excel backgrounds and expect similar functionality in your web application. </p>



<p>In short, choose AG Grid when you need to provide the best user experience on your data table. But be prepared to spend more time tweaking the parameters of AG Grid.</p>



<h2 id='4-not-recommended-approaches'  id="boomdevs_10" class="wp-block-heading" >4. Not recommended approaches</h2>



<p>There are two approaches that, while possible, are generally not recommended:</p>



<ol class="wp-block-list">
<li>Using HTML Table Components (<code>html.Table</code>, <code>html.Td</code>, <code>html.Tr</code>): While this gives you full control over the HTML, you&#8217;ll need to implement all functionality from scratch, making it tedious and time-consuming. This is essentially what Dash Bootstrap does under the hood.</li>



<li>Using Plotly Graph Objects Table (<code>go.Table</code>): This is designed for displaying tables within chart contexts and lacks the flexibility and features of the dedicated table components discussed above.</li>
</ol>



<h2 id='5-feature-comparison-bootstrap-table-vs-datatable-vs-ag-grid'  id="boomdevs_11" class="wp-block-heading" >5. Feature comparison: (bootstrap) Table vs DataTable vs AG Grid</h2>



<p>Now that we have reviewed the basic features, let’s summarize them in a detailed table:</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th><strong>Feature</strong></th><th><strong>Bootstrap Table</strong></th><th><strong>DataTable</strong></th><th><strong>AG Grid</strong></th></tr></thead><tbody><tr><td><strong>Basic Display</strong></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><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></tr><tr><td><strong>Custom Components in Cells</strong></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;" /> (Markdown only)</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;" /> (JS required)</td></tr><tr><td><strong>Pagination</strong></td><td>Manual Implementation</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;" /> Built-in</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;" /> Built-in</td></tr><tr><td><strong>Sorting</strong></td><td>Manual Implementation</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;" /> Built-in</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;" /> Built-in</td></tr><tr><td><strong>Filtering</strong></td><td>Manual Implementation</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;" /> Built-in</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;" /> Built-in</td></tr><tr><td><strong>Column Resizing</strong></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><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></tr><tr><td><strong>Column Reordering</strong></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><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></tr><tr><td><strong>Fixed/Frozen Columns</strong></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><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></tr><tr><td><strong>Row Selection</strong></td><td>Manual Implementation</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></tr><tr><td><strong>Cell Editing</strong></td><td>Manual Implementation</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></tr><tr><td><strong>CSV Export</strong></td><td>Manual Implementation</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></tr><tr><td><strong>Excel Export</strong></td><td>Manual Implementation</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/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Enterprise Only</td></tr><tr><td><strong>Row Grouping</strong></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><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><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></tr><tr><td><strong>Pivoting</strong></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><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><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Enterprise Only</td></tr><tr><td><strong>Tree Data</strong></td><td>Manual Implementation</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><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></tr><tr><td><strong>Virtual Scrolling</strong></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><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></tr><tr><td><strong>Backend Pagination</strong></td><td>Manual Implementation</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></tr><tr><td><strong>Row Dragging</strong></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><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><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></tr><tr><td><strong>Column Spanning</strong></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><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></tr><tr><td><strong>Row Spanning</strong></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><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></tr><tr><td><strong>Setup Complexity</strong></td><td>Simple</td><td>Moderate</td><td>Complex</td></tr><tr><td><strong>Performance (Large Data)</strong></td><td>Poor</td><td>Good</td><td>Good</td></tr><tr><td><strong>Learning Curve</strong></td><td>Minimal</td><td>Moderate</td><td>Steep</td></tr></tbody></table></figure>



<p><strong>Legend</strong>: <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;" /> Supported out-of-the-box |&nbsp;<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;" /> Not supported | <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Enterprise feature. ‘<em>Manual Implementation</em>’ means possible but requires custom development</p>



<h2 id='6-conclusion'  id="boomdevs_12" class="wp-block-heading" >6. Conclusion</h2>



<p>We reviewed three ways to turn your CSV/Excel data into a table with Dash Python.</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1920" height="1080" src="https://dash-resources.com/wp-content/uploads/2025/01/Dash-tables-1.png" alt="" class="wp-image-511" srcset="https://dash-resources.com/wp-content/uploads/2025/01/Dash-tables-1.png 1920w, https://dash-resources.com/wp-content/uploads/2025/01/Dash-tables-1-300x169.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/Dash-tables-1-1024x576.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/Dash-tables-1-768x432.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/Dash-tables-1-1536x864.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/Dash-tables-1-1320x743.png 1320w" sizes="auto, (max-width: 1920px) 100vw, 1920px" /></figure>



<p>At the end, how to make your choice? </p>



<ul class="wp-block-list">
<li>Choose <strong>Bootstrap Table</strong> if you need a simple static display for small datasets (&lt; 100 lines). It&#8217;s perfect for straightforward displays with minimal setup.</li>



<li>Choose <strong>DataTable</strong> when you need built-in interactivity like sorting and filtering for medium-sized datasets. It&#8217;s also excellent for editable tables and offers a good balance of features without complexity.</li>



<li>Choose <strong>AG Grid</strong> if you need to provide the best user experience on your data table, with Excel-like functionality and advanced styling. But it comes with the need of additional Javascript knowledge.</li>
</ul>



<p class="callout"><strong>Opinion.</strong> I personally prefer to use Dash Bootstrap for static table displays. For more advanced interactive data display, I prefer AG Grid for its user experience, but achieving what I want to do is often complex.</p>



<p>Remember, you can always start with the simplest solution that meets your needs —many applications work perfectly fine with a basic Bootstrap table, and you can upgrade to more advanced components as your requirements grow.</p>



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



<p>I hope this article helped you to grasp the differences between the table providers! <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>Feel free to join the discussion or ask question on the associated topic <a href="https://community.plotly.com/t/how-to-create-tables-in-plotly-dash-dbc-table-datatable-ag-grid-comparison/89901">here</a>.</p>
<p>L’article <a href="https://dash-resources.com/dash-table-datatable-ag-grid-comparison/">How to create tables in plotly Dash (dbc.Table, DataTable, AG Grid comparison)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Build a To-Do app in Python with Dash (part 1/3)</title>
		<link>https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-1-3/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Wed, 08 Jan 2025 18:58:49 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[pattern-matching]]></category>
		<category><![CDATA[tutorial]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=409</guid>

					<description><![CDATA[<p>In this tutorial, we&#8217;ll build a To-Do application 100% in Python using Dash plotly and the community extension Dash Mantine Components. We&#8217;ll take an iterative [...]</p>
<p>L’article <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> 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&#8217;ll build a To-Do application 100% in Python using Dash plotly and the community extension Dash Mantine Components. We&#8217;ll take an iterative approach, starting with the basics and gradually adding complexity as we understand why each piece is needed.</p>



<p>The tutorial is in three parts:</p>



<ol class="wp-block-list">
<li>Setup the layout, handle a minimal task list (part 1 &#8211; this article)</li>



<li><a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-2-3/">Handle multiple lists and save tasks on page reload (part 2)</a></li>



<li><a href="https://dash-resources.com/writing-in-process/">Improve scalability and performance with Patch (part 3)</a></li>
</ol>



<p>At the end of this article, you’ll know how to build the following To-Do app with Dash python:</p>



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



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



<h2 id='introduction-to-dash-mantine-components'  id="boomdevs_1" class="wp-block-heading" >Introduction to Dash Mantine Components</h2>



<p>Before we dive in, let&#8217;s understand what <a href="https://www.dash-mantine-components.com/">Dash Mantine Components</a> (DMC) offers us. DMC is a component library that provides pre-built React components with a modern design system. It includes layout helpers (e.g. grid systems, centered container, &#8230;) and out-of-the-box components (e.g. Modal, Radio Button, Slider, etc.) </p>



<figure class="wp-block-image size-full is-style-default"><img loading="lazy" decoding="async" width="1796" height="684" src="https://dash-resources.com/wp-content/uploads/2025/01/image.png" alt="Illustration: a modal is a typical example of what DMC can provide out-of-the-box." class="wp-image-410" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image.png 1796w, https://dash-resources.com/wp-content/uploads/2025/01/image-300x114.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-1024x390.png 1024w, https://dash-resources.com/wp-content/uploads/2025/01/image-768x292.png 768w, https://dash-resources.com/wp-content/uploads/2025/01/image-1536x585.png 1536w, https://dash-resources.com/wp-content/uploads/2025/01/image-1320x503.png 1320w" sizes="auto, (max-width: 1796px) 100vw, 1796px" /><figcaption class="wp-element-caption">Illustration: a modal is a typical example of what DMC can provide out-of-the-box.</figcaption></figure>



<p>Some key components we&#8217;ll use:</p>



<ul class="wp-block-list">
<li><code>dmc.Container</code>: A centered container with max-width</li>



<li><code>dmc.Paper</code>: A white background container with optional shadow</li>



<li><code>dmc.Grid</code> and <code>dmc.GridCol</code>: Flexbox-based grid system</li>



<li><code>dmc.Button</code>, <code>dmc.Checkbox</code>, <code>dmc.ActionIcon</code>: Interactive components</li>
</ul>



<p>DMC uses several spacing and positioning attributes:</p>



<ul class="wp-block-list">
<li><code>mt</code>: margin-top (e.g., <code>mt="md"</code> for medium margin-top)</li>



<li><code>mb</code>: margin-bottom</li>



<li><code>px</code>: padding on x-axis (left and right)</li>



<li><code>p</code>: padding on all sides</li>



<li>Size values: &#8220;xs&#8221;, &#8220;sm&#8221;, &#8220;md&#8221;, &#8220;lg&#8221;, &#8220;xl&#8221; or numeric pixels</li>
</ul>



<p>This will be useful for the rest of the article. If you encounter something you don’t understand, just go back here or search the component in <a href="https://www.dash-mantine-components.com/">DMC documentation</a>.</p>



<h2 id='step-1-setting-up-the-layout'  id="boomdevs_2" class="wp-block-heading" >Step 1: Setting up the layout</h2>



<h3 id='1-data-structure'  id="boomdevs_3" class="wp-block-heading" >1. Data structure</h3>



<p>Let&#8217;s start by defining how we&#8217;ll store our task data. This will actually help us shape the layout.</p>



<p>We want to create and save tasks. Each task should have a text content, and a checkbox status. That means having something like:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">sample_list_data = {
    "title": "My Tasks",
    "tasks_list": [
        {
            "content": "Task A",
            "checked": True,
        },
        {
            "content": "Task B",
            "checked": False,
        },
        {
            "content": "Task C",
            "checked": False,
        },
    ],
}

</code></pre>



<p>From this structure, we can build the layout function to display tasks.</p>



<h3 id='2-creating-reusable-layout-functions'  id="boomdevs_4" class="wp-block-heading" >2. Creating reusable layout functions</h3>



<p>A key aspect of our design is creating separate functions for each part of the layout. This isn&#8217;t just for code organization &#8211; it&#8217;s crucial for the dynamic nature of our app. Later, when we add or remove tasks, we&#8217;ll need to recreate portions of the layout dynamically. By having these functions, we can easily generate new task elements or update existing ones.</p>



<p>Let&#8217;s start with a function to render a single task using DMC&#8217;s Grid system:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def get_task(task_dict):
    """ Returns a single task layout """
    text = task_dict["content"]
    checked = task_dict["checked"]

    content = dmc.Grid(
        [
            # Checkbox column
            dmc.GridCol(
                dmc.Checkbox(
                    checked=checked,
                    mt=2  # Align checkbox vertically
                ),
                span="content"  # Take only needed space
            ),
            # Task text column - Using Input for editability
            dmc.GridCol(
                dmc.Text(  # Wrap in Text component for consistent styling
                    dcc.Input(
                        text,
                        className="shadow-input",  # Custom styling for input
                        debounce=True              # For callbacks
                    )
                ),
                span="auto"  # Take remaining space
            ),
            # Delete button column
            dmc.GridCol(
                dmc.ActionIcon(
                    DashIconify(icon="tabler:x", width=20),
                    variant="transparent",
                    color="gray",
                    className="task-del-button"
                ),
                span="content"
            ),
        ],
        className="task-container"
    )

    return content

</code></pre>



<p>We used <code>dmc.Grid</code> to easily get a 3 column structure. The first column has the checkbox, the second the text content, and the third has the delete task button.</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="836" height="624" src="https://dash-resources.com/wp-content/uploads/2025/01/image-1.png" alt="Illustration for the 3 column grid structure." class="wp-image-411" style="width:389px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2025/01/image-1.png 836w, https://dash-resources.com/wp-content/uploads/2025/01/image-1-300x224.png 300w, https://dash-resources.com/wp-content/uploads/2025/01/image-1-768x573.png 768w" sizes="auto, (max-width: 836px) 100vw, 836px" /><figcaption class="wp-element-caption">Illustration for the 3 column grid structure.</figcaption></figure>
</div>


<p>You might wonder why we use a <code>dcc.Input</code> wrapped in a <code>dmc.Text</code> component. The idea is that we want to modify the text just by clicking on it. The most similar example is the <a href="https://developer.mozilla.org/fr/docs/Web/HTML/Global_attributes/contenteditable">“contenteditable” elements</a> in HTML : but they aren’t available in Dash.</p>



<p>Instead, we will style the app with some CSS to make the input look like normal text when it is not focused (i.e. being modified).</p>



<p class="callout"><strong>Note</strong>: the <code>debounce</code> attribute is used to trigger the input “update” event when the user finishes typing, not every time a key is pressed. In this case, it offers a better user experience.</p>



<h3 id='3-creating-the-tasks-list-and-main-container'  id="boomdevs_5" class="wp-block-heading" >3. Creating the tasks list and main container</h3>



<p>Now let&#8217;s create a function to render all tasks. It’s simply the for loop over the <code>get_task</code> function:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def get_tasks_layout(tasks_list):
    """ Returns the list of tasks """
    tasks = []
    for task_dict in tasks_list:
        task_layout = get_task(task_dict)
        tasks.append(task_layout)
    return tasks

</code></pre>



<p>We&#8217;ll wrap everything in a main container with a title and &#8220;Add a new ask&#8221; button:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def get_list_layout(list_data):
    """ Returns the list container with title and tasks """
    tasks_layout = get_tasks_layout(list_data["tasks_list"])

    content = dmc.Paper(
        [
            dmc.Title(list_data["title"], order=2),

            # Tasks container
            dmc.Container(
                tasks_layout,
                id="main_task_container",
                px=0,  # No horizontal padding
                mt="md",  # Medium margin top
                mb="md",  # Medium margin bottom
            ),

            # Add task button
            dmc.Button(
                "Add a new task",
                id="new_task_button",
                style={"width": "100%"},
                variant="outline",
                color="gray",
            )
        ],
        shadow="sm",  # Light shadow
        p="md",       # Medium padding
        mt="md",      # Medium margin top
        radius="sm",  # Slightly rounded corners
    )

    return content

</code></pre>



<h3 id='4-styling-with-css'  id="boomdevs_6" class="wp-block-heading" >4. Styling with CSS</h3>



<p>DMC already brings a set of default stylesheet, which make us gain a lot of time. But as said previously, we want our we want our task inputs to look clean and minimal, with appropriate visual feedback for user interactions:</p>



<p>So we create a <code>style.css</code> file in the <code>assets/</code> folder. This will be loaded automatically by Dash:</p>



<pre class="wp-block-code"><code lang="css" class="language-css">/* 
 * Filepath: ./assets/style.css 
 */

.shadow-input {
    display: inline-block;
    width: 100%;
    margin: 0;
    border: 1px solid black;
    border-color: transparent;
    border-radius: 5px;
}
.shadow-input:hover {
    border-color: gray;
}

.shadow-input:active, .shadow-input:focus {
    border-color: black;
}

.task-container:has(.mantine-Checkbox-root[data-checked]) .mantine-Text-root input {
    text-decoration: line-through;
    color: gray;
}

.task-del-button:hover {
    color: red;
}
</code></pre>



<p>This CSS does several things:</p>



<ol class="wp-block-list">
<li>Makes inputs blend seamlessly into the layout with transparent borders</li>



<li>Shows subtle borders on hover and focus for better UX</li>



<li>Applies strikethrough styling to checked tasks</li>



<li>Adds a red hover effect to delete buttons</li>
</ol>



<p>A key design decision here is handling the strikethrough effect with CSS rather than callbacks. By using the <code>:has()</code> selector to detect checked checkboxes, we can apply the strikethrough style directly in CSS. This is more efficient than using a callback, as it eliminates unnecessary round-trips to the server and provides instant visual feedback.</p>



<h3 id='5-running-the-app'  id="boomdevs_7" class="wp-block-heading" >5. Running the app</h3>



<p>Now let&#8217;s put everything together and start our application:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">import dash_mantine_components as dmc
from dash import Dash, _dash_renderer, dcc
from dash_iconify import DashIconify
_dash_renderer._set_react_version("18.2.0")  # for mantine

# sample_list_data = ...

## Layout functions:
# get_task()
# get_tasks_layout()
# get_list_layout

app = Dash(__name__)

app.layout = dmc.MantineProvider(
    dmc.Container(
        get_list_layout(sample_list_data),
        size=400,  # Container width
    )
)

# Start the server
if __name__ == '__main__':
    app.run_server(debug=True)

</code></pre>



<p>When you run this code, you&#8217;ll have a basic task list application without interactivity yet:</p>



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



<p><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;" /> <a href="https://github.com/Spriteware/todo-app-python-dash/blob/master/app1.py">See the full code on Github</a> or <a href="https://scripts.dash-resources.com/todo_app/app1.py/">open the app</a>.</p>



<p>Now that we have our layout defined, we can make it interactive with callbacks.</p>



<h2 id='step-2-adding-interactivity-with-callbacks'  id="boomdevs_8" class="wp-block-heading" >Step 2: Adding interactivity with callbacks</h2>



<p>Now comes the interesting part. We need to make our tasks interactive &#8211; we need to handle creating, reading, updating, and deleting tasks (<a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD operations</a>). However, we face an interesting challenge: how do we handle callbacks for elements that don&#8217;t exist when the app starts?</p>



<p>In regular Dash callbacks, you must define both inputs and outputs statically &#8211; they need to exist when the app initializes. This works fine for static elements like a single button or dropdown. But in our task app, we&#8217;re dynamically adding and removing tasks! These elements don&#8217;t exist when the app starts running.</p>



<p>This is where pattern-matching callbacks come in. Instead of targeting specific IDs like &#8220;button-1&#8221; or &#8220;task-input-2&#8221;, pattern-matching callbacks let us define patterns that match multiple components, even ones created after the app starts running. They work by using a dictionary-based ID system and special selectors like <code>ALL</code>.</p>



<p>For example, compare these two approaches:</p>



<pre class="wp-block-code"><code lang="python" class="language-python"># Regular callback - Only works for a specific, existing button
@app.callback(
    Output("static-div", "children"),
    Input("submit-button", "n_clicks")
)

# Pattern-matching callback - Works for ANY button matching the pattern
@app.callback(
    Output({"type": "result", "id": ALL}, "children"),
    Input({"type": "submit", "id": ALL}, "n_clicks")
)
</code></pre>



<p>The pattern-matching version uses a dictionary for the ID with two keys:</p>



<ul class="wp-block-list">
<li>&#8220;type&#8221;: Groups similar components (like all submit buttons)</li>



<li>&#8220;id&#8221;: Uniquely identifies each instance</li>
</ul>



<p>Now we can write a single callback that handles all tasks of the same type, even ones created dynamically after the app starts! When we add a new task with ID <code>{"type": "task", "id": "123"}</code>, the callback will automatically work for it.</p>



<p>This is perfect for our task app where we need to add and remove tasks dynamically. Let’s continue.</p>



<p class="callout"><strong>Learn more</strong> on Pattern-Matching callbacks from the Dash plotly documentation: <a href="https://dash.plotly.com/pattern-matching-callbacks">https://dash.plotly.com/pattern-matching-callbacks</a></p>



<h3 id='1-adding-unique-identifiers'  id="boomdevs_9" class="wp-block-heading" >1. Adding unique identifiers</h3>



<p>As Pattern-Matching callbacks need a unique id, we need to modify our data structure to include unique identifiers for each task. Let&#8217;s add an <code>index</code> field using UUID :</p>



<pre class="wp-block-code"><code lang="python" class="language-python">import uuid

sample_list_data = {
    "title": "My Tasks",
    "tasks_list": [
        {
            "index": uuid.uuid4().hex,  # Add unique identifier
            "content": "Task A",
            "checked": True,
        },
        # ... other tasks
    ],
}

</code></pre>



<p>Example UUID: <code>f4da57942cec46b7ba448c88fad11996</code>. We could as well have used integers. It doesn’t matter as long as the ids are unique.</p>



<p>Now we need to update our <code>get_task</code> function to use these identifiers:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">def get_task(task_dict):
    """ Returns a single task layout """
    text = task_dict["content"]
    checked = task_dict["checked"]
    index = task_dict["index"]  # Get the index

    content = dmc.Grid(
        [
            dmc.GridCol(
                dmc.Checkbox(
                    id={"type": "task_checked", "index": index},  # Add ID
                    checked=checked,
                    mt=2
                ),
                span="content"
            ),
            dmc.GridCol(
                dmc.Text(
                    dcc.Input(
                        text,
                        id={"type": "task_content", "index": index},  # Add ID
                        className="shadow-input",
                        debounce=True,
                    )
                ),
                span="auto"
            ),
            dmc.GridCol(
                dmc.ActionIcon(
                    DashIconify(icon="tabler:x", width=20),
                    id={"type": "task_del", "index": index},  # Add ID
                    variant="transparent",
                    color="gray",
                    className="task-del-button"
                ),
                span="content"
            ),
        ],
        className="task-container"
    )

    return content

</code></pre>



<p>We now have a unique index on the three interactive components : the checkbox, the input and the delete button. Let’s write the callbacks.</p>



<h3 id='2-adding-new-tasks'  id="boomdevs_10" class="wp-block-heading" >2. Adding new tasks</h3>



<p>Now we can implement the &#8220;Add Task&#8221; functionality using callbacks:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output("main_task_container", "children", allow_duplicate=True),
    Input("new_task_button", "n_clicks"),
    State("main_task_container", "children"),
    prevent_initial_call=True,
)
def add_task(n_clicks, current_tasks):
    """ Adds a task to the list """
    if not n_clicks:
        raise PreventUpdate

    # Create new task with unique ID
    new_index = uuid.uuid4().hex
    task_dict = {
        "index": new_index,
        "content": "",
        "checked": False,
    }
    task_layout = get_task(task_dict)

    # Add new task to current tasks
    updated_tasks = current_tasks + [task_layout]
    return updated_tasks

</code></pre>



<p>This callback is triggered with the new_task_button is clicked. It basically takes the existing list of tasks that are in <code>main_task_container</code> and add a new, empty, task inside. Then it returns the new list.</p>



<p>We added <code>prevent_initial_call=True</code> to avoid running this callback when the app is loaded (the default behavior), as we know that this callback should only be triggered on a button action. It reduces unnecessary work and HTTP requests.</p>



<p>We also check the <code>n_clicks</code> is a valid positive value, in case that the callback gets triggered anyway. The <code>raise PreventUpdate</code> stops the callback execution.</p>



<p class="callout"><strong>Notice</strong> the <code>allow_duplicate=True</code> on the <code>Output</code>. It is mandatory as we will have many operations that will update the <code>main_task_container</code> component.</p>



<h3 id='3-removing-tasks'  id="boomdevs_11" class="wp-block-heading" >3. Removing Tasks</h3>



<p>And finally, we implement the “Task deletion” callback following the same scheme:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output("main_task_container", "children", allow_duplicate=True),
    Input({"type": "task_del", "index": ALL}, "n_clicks"),
    State("main_task_container", "children"),
    prevent_initial_call=True,
)
def remove_task(n_clicks, current_tasks):
    """ Remove a task from the list """
    if not any(n_clicks):
        raise PreventUpdate

    print("Entering remove_task callback")
    task_index = ctx.triggered_id["index"]

    # Get the list of existing ids.
    all_ids = [elem["id"] for elem in ctx.inputs_list[0]]

    # Find the position of element in list and remove it
    for i, task_id in enumerate(all_ids):
        if task_id["index"] == task_index:
            del current_tasks[i]
            break 

    return current_tasks
</code></pre>



<p>Here&#8217;s how it works:</p>



<ol class="wp-block-list">
<li>When a delete button is clicked, Dash triggers this callback. We use pattern matching with <code>{"type": "task_del", "index": ALL}</code> to catch clicks from any delete button in our tasks. Each button has a unique index we assigned earlier.</li>



<li>The <code>ctx.triggered_id</code> tells us which specific delete button was clicked &#8211; specifically its index. This works because when the callback fires, Dash knows exactly which component triggered it.</li>



<li>To find and remove the correct task, we need to:
<ul class="wp-block-list">
<li>Get all delete button IDs using <code>ctx.inputs_list[0]</code> which contains the IDs of all components matching our pattern</li>



<li>Map this to just get the ID dictionaries (each with a &#8220;type&#8221; and &#8220;index&#8221;)</li>



<li>Find the position (index) in our task list that matches the triggered button&#8217;s index</li>



<li>Delete that task from <code>current_tasks</code> using <code>del</code></li>
</ul>
</li>
</ol>



<p>As seen previously, the <code>prevent_initial_call=True</code> and <code>if not any(n_clicks)</code> check ensure we don&#8217;t accidentally delete tasks when the app first loads or if the callback is triggered without a click.</p>



<h3 id='4-run-the-app'  id="boomdevs_12" class="wp-block-heading" >4. Run the app</h3>



<p>Let’s run again the code. We preferably place our callbacks before the <code>app.run_server</code> statement:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">import uuid
import dash_mantine_components as dmc
from dash import Dash, _dash_renderer, dcc, ctx, Input, Output, State, ALL
from dash.exceptions import PreventUpdate
from dash_iconify import DashIconify
_dash_renderer._set_react_version("18.2.0")  # for mantine

# sample_list_data = ...

## Layout functions:
# get_task()
# get_tasks_layout()
# get_list_layout

app = Dash(__name__)

# app.layout = ..

## Callbacks
# add_task()
# remove_task()

# Start the server
if __name__ == '__main__':
    app.run_server(debug=True)
</code></pre>



<p>Try to add a task, modify and delete a task in the app below:</p>



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



<p><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;" /> <a href="https://github.com/Spriteware/todo-app-python-dash/blob/master/app2.py">See the full code on Github</a> or <a href="https://scripts.dash-resources.com/todo_app/app2.py/">open the app</a>.</p>



<p>You can also download the full project below:</p>



                <div class="ml-embedded" data-form="LGDFB1"></div>
            



<p>You now have a fully working —yet basic— todo app. Congratulations!</p>



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



<p>Let&#8217;s recap what we covered in this article:</p>



<ul class="wp-block-list">
<li>An introduction to DMC (Dash Mantine Components) and its components</li>



<li>How to use pattern matching callbacks for dynamic interactions</li>



<li>How to use CSS styling in a Dash context</li>
</ul>



<p>In the next part, we will see how to adapt this code to handle multiple lists and keep the tasks saved after page reloads (persistence): <a href="https://dash-resources.com/build-a-to-do-app-in-python-with-dash-part-2-3/">How to build a To-Do app, part 2</a>.</p>



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



<p>I hope you enjoyed this first part of the tutorial! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f680.png" alt="🚀" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p>If you have any question, please join us on the dedicated topic on Plotly’s forum: <a href="https://community.plotly.com/t/tutorial-build-a-to-do-app-in-python-with-dash/89805">here</a>. <strong>Get notified of a new article by subscribing to the newsletter. </strong></p>
<p>L’article <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> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Tutorial: Dash callbacks (schemas &#038; examples)</title>
		<link>https://dash-resources.com/tutorial-dash-callbacks-schemas-examples/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Sun, 29 Dec 2024 15:13:12 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<category><![CDATA[callbacks]]></category>
		<guid isPermaLink="false">https://dash-resources.com/?p=120</guid>

					<description><![CDATA[<p>At its core, Dash Plotly uses a system of &#8220;callbacks&#8221; to create interactive features &#8211; these are Python functions that automatically update parts of your [...]</p>
<p>L’article <a href="https://dash-resources.com/tutorial-dash-callbacks-schemas-examples/">Tutorial: Dash callbacks (schemas &amp; examples)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>At its core, Dash Plotly uses a system of &#8220;callbacks&#8221; to create interactive features &#8211; these are Python functions that automatically update parts of your application in response to user inputs.</p>



<p>There are many ways to design Dash callbacks, and in this dash callbacks tutorial, I&#8217;ll provide a comprehensive, <strong>step-by-step guide with diagrams and code examples</strong>. </p>



<p>By the end of this tutorial, you&#8217;ll have a good understanding of how callbacks work and how to implement interactivity in your own Dash applications.</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="#a-simple-callback">A simple callback</a>
      </li>
      <li>
        <a href="#multi-input-callback">Multi input callback</a>
      </li>
      <li>
        <a href="#multiple-inputs-and-outputs-callbacks">Multiple inputs and outputs callbacks</a>
      </li>
      <li>
        <a href="#multiple-callbacks-with-the-same-inputs">Multiple callbacks with the same inputs</a>
      </li>
      <li>
        <a href="#chained-callbacks-callbacks-triggering-other-callbacks">Chained callbacks: callbacks triggering other callbacks</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#using-state-instead-of-input">Using State instead of Input</a>
          </li>
          <li class="last">
            <a href="#advantages-and-disadvantages-of-chaining-callbacks">Advantages and disadvantages of chaining callbacks</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#other-types-of-callbacks">Other types of callbacks</a>
        <ul class="menu_level_2">
          <li class="first">
            <a href="#background-callbacks">Background Callbacks</a>
          </li>
          <li>
            <a href="#clientside-callbacks">Clientside Callbacks</a>
          </li>
          <li class="last">
            <a href="#pattern-matching-callbacks">Pattern-Matching Callbacks</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>Let&#8217;s start! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4aa.png" alt="💪" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<h2 id='a-simple-callback'  id="boomdevs_1" class="wp-block-heading" >A simple callback</h2>



<p>To get started, let&#8217;s explore a minimal Dash app that features a simple counter and a button to increment its value:</p>



<iframe src="https://scripts.dash-resources.com/callbacks/callbacks1.py/" width="100%" frameBorder="0"></iframe>



<p>The associated code is the following:</p>



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

app = Dash(__name__)

# Declare the layout
app.layout = html.Div([
    html.P('Count: 0', id='counter'),
    html.Button('Click', id='btn', n_clicks=0)
])

# Define the callback
@app.callback(
    Output('counter', 'children'), 
    Input('btn', 'n_clicks')
)
def update(n_clicks):
    return f"Count: {n_clicks}"

# Run the app
if __name__ == '__main__':
    app.run_server(debug=True)
</code></pre>



<p>The first lines define the app layout. The syntax is highly inspired by HTML elements, but elements are called &#8220;<strong>components</strong>&#8221; in Dash. These components are more than just static elements; they are interactive and seamlessly integrate with Python callbacks to enable dynamic updates.</p>



<pre class="wp-block-code"><code lang="python" class="language-python">app.layout = html.Div([
    html.P('Count: 0', id='counter'),  # same as &lt;p id="counter"&gt;Count: 0&lt;/p&gt; in HTML
    html.Button('Click', id='btn', n_clicks=0)  # same as &lt;button id="btn"&gt;Click&lt;/button&gt; in HTML
])</code></pre>



<p>We explicitly set <code>n_clicks</code> to 0 to initialize the button with a default value. Without this, its value would be <code>None</code>, but the <code>update</code> function is supposed to work with an integer.</p>



<p>The next lines define the callback, which is a core concept in Dash. Dash callbacks are just regular Python functions decorated with the <code>@app.callback</code> decorator (or just <code>@callback</code>). This decorator connects input components (such as button clicks) with output components (like the displayed counter text):</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    # the parameters are explicitly written now, but usually we don't
    Output(component_id='counter', component_property='children'), 
    Input(component_id='btn', component_property='n_clicks'),
)
def update_counter(n_clicks):
    # ...</code></pre>



<p>Inputs and Outputs must have a specified component ID and property to connect. This makes it clear which actions update the app and what parts of the app are changed. For example, the button&#8217;s <code>n_clicks</code> property triggers the callback, updating the paragraph&#8217;s <code>children</code> property with the new count.</p>



<p class="callout">If you are not familiar with decorators, I suggest you take a look at this tutorial: <a href="https://realpython.com/primer-on-python-decorators/">https://realpython.com/primer-on-python-decorators/</a></p>



<p>Dash callbacks are essential as they enable interactivity. By linking inputs and outputs, they dynamically update the app in response to user actions.</p>



<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">flowchart LR
    btn["Button&lt;br>id='btn2'"] -->|"n_clicks&lt;br>Input"| update_counter(["update&lt;br>callback"])
    update_counter -->|"children&lt;br>Output"| counter["Paragraph&lt;br>id='counter'"]
    style btn fill:#f9f,stroke:#333
    style counter fill:#bbf,stroke:#333
    style update_counter fill:#ff9,stroke:#333
</pre><img decoding="async" src="https://dash-resources.com/wp-content/uploads/2024/12/merpress-3.png" alt=""/></div>



<p>In this example, the counter is incremented each time the button is pressed. The <code>update_counter</code> function encapsulates this logic and returns the result to be updated within the paragraph.</p>



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



<p>Congratulations, you just learned the most essential feature of Dash <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='multi-input-callback'  id="boomdevs_2" class="wp-block-heading" >Multi input callback</h2>



<p>Now, let’s take it a step further. What if you want multiple components to control the same output? Dash makes this possible by supporting multiple inputs in a single callback.</p>



<p>Imagine you have two buttons: one adds 1 for each click, and the other subtracts 1 for each click. </p>



<iframe src="https://scripts.dash-resources.com/callbacks/callbacks2.py/" width="100%" frameBorder="0"></iframe>



<p>Here’s how you can implement it:</p>



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

app = Dash(__name__)

# Declare the layout
app.layout = html.Div([
    html.P('Count: 0', id='counter'),
    html.Button('Click +', id='btn_add', n_clicks=0), 
    html.Button('Click -', id='btn_sub', n_clicks=0), # two buttons
])

# Define the callback
@app.callback(
    Output('counter', 'children'), 
    Input('btn_add', 'n_clicks'),
    Input('btn_sub', 'n_clicks') # now we have 2 inputs
)
def update_counter(n_clicks1, n_clicks2):
    return f"Count: {n_clicks1-n_clicks2}"

# Run the app
if __name__ == '__main__':
    app.run_server(debug=True)
</code></pre>



<p>To do so, we simply added an extra Input in our <code>@app.callback</code> decorator. As a result, we now have 2 possible triggers: </p>



<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">flowchart LR
    btn1["Button&lt;br>id='btn_add'"] -->|"n_clicks&lt;br>Input"| update_counter(["update&lt;br>callback"])
    btn2["Button&lt;br>id='btn_sub'"] -->|"n_clicks&lt;br>Input"| update_counter(["update&lt;br>callback"])
    update_counter -->|"children&lt;br>Output"| counter["Paragraph&lt;br>id='counter'"]
    style btn1 fill:#f9f,stroke:#333
    style btn2 fill:#f9f,stroke:#333
    style counter fill:#bbf,stroke:#333
    style update_counter fill:#ff9,stroke:#333
</pre></div>



<p>In some cases, two inputs can be fired at the same time. Dash therefore handles it efficiently by triggering the callback only once. In our example, it means that the counters would update once even if the two buttons are clicked exactly at the same time.</p>



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



<h2 id='multiple-inputs-and-outputs-callbacks'  id="boomdevs_3" class="wp-block-heading" >Multiple inputs and outputs callbacks</h2>



<p>The real excitement begins when we need to update multiple components simultaneously. For example, we can enhance the counter by displaying the result in <span style="color: red;">red</span> if it&#8217;s negative or <span style="color: green;">green</span> if it&#8217;s positive. </p>



<iframe src="https://scripts.dash-resources.com/callbacks/callbacks3.py/" width="100%" frameBorder="0"></iframe>



<p>To achieve this, we update the <code>style</code> property of the component alongside its text. This involves adding another Output in our callback and adjusting the function accordingly:</p>



<pre title="" class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output('counter', 'children'), 
    Output('counter', 'style'),  # here we added property "style"
    Input('btn_add', 'n_clicks'),
    Input('btn_sub', 'n_clicks')
)
def update_counter(n_clicks1, n_clicks2):

    content = f"Count: {n_clicks1-n_clicks2}"

    if n_clicks1-n_clicks2 &gt;= 0:
        style = {'color': 'green'}
    else:
        style = {'color': 'red'}

    return content, style  # as a result, we now return two values
</code></pre>



<p>Notice that now the function returns two variables: the content (for <code>children</code> property), and style (for <code>style</code> property). </p>



<p>If inputs can trigger a callback independantly or together, all outputs of a callback are always modified at the exact same time. It means that Dash will always update <code>children</code> and <code>style</code>, whenever the callback is triggered.</p>



<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">flowchart LR
    btn1["Button&lt;br>id='btn_add'"] -->|"n_clicks&lt;br>Input"| update_counter(["update&lt;br>callback"])
    btn2["Button&lt;br>id='btn_sub'"] -->|"n_clicks&lt;br>Input"| update_counter(["update&lt;br>callback"])
    update_counter -->|"children&lt;br>Output"| counter["Paragraph&lt;br>id='counter'"]
    update_counter -->|"style&lt;br>Output"| counter["Paragraph&lt;br>id='counter'"]
    style btn1 fill:#f9f,stroke:#333
    style btn2 fill:#f9f,stroke:#333
    style counter fill:#bbf,stroke:#333
    style update_counter fill:#ff9,stroke:#333
</pre></div>



<p class="callout"><strong>Good to know</strong>: it is still possible to not update an output by providing <code>dash.no_output</code> as value for an output.</p>



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



<h2 id='multiple-callbacks-with-the-same-inputs'  id="boomdevs_4" class="wp-block-heading" >Multiple callbacks with the same inputs</h2>



<p>Dash does not limit the amount of callbacks attached to an input. However, it is theoretically forbidden to have more than one callback per output (= only one callback should be responsible for the update of a component&#8217;s property).</p>



<p class="callout">I wrote &#8216;<em>theoretically</em>&#8216; because it&#8217;s possible with the callback parameter <code>allow_duplicate_output</code>. However, you should avoid using it a much as possible as it makes a code more complicated to debug. See more here: <a href="https://dash.plotly.com/duplicate-callback-outputs">https://dash.plotly.com/duplicate-callback-outputs</a></p>



<p>Let’s add a Progress bar to our counter. We need to update this progress bar every time that the buttons are clicked, hence a new <code>update_pbar</code> callback:</p>



<iframe src="https://scripts.dash-resources.com/callbacks/callbacks4.py/" width="100%" frameBorder="0"></iframe>



<pre class="wp-block-code"><code lang="Python" class="language-Python"># Declare the layout
app.layout = html.Div([
    html.P('Count: 0', id='counter'),
    html.Progress(id="progress_bar", value=0, max=10), # We add the progress bar component
    html.Br(),
    html.Button('Click +', id='btn_add', n_clicks=0),
    html.Button('Click -', id='btn_sub', n_clicks=0),
])

# (... update counter)

@app.callback(
    Output('progress_bar', 'value'), 
    Input('btn_add', 'n_clicks'),
    Input('btn_sub', 'n_clicks')
)
def update_pbar(n_clicks1, n_clicks2):
    # This is a new callback that only updates the progress bar. 
    return n_clicks1-n_clicks2
</code></pre>



<p>As you can see, this callback is simple, only the output is different from the very first callback we implemented. The callback graph might now look like:</p>



<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">flowchart LR
    btn1["Button&lt;br>id='btn_add'"] -->|"n_clicks&lt;br>Input"| update_counter(["update_counter&lt;br>callback"])
    btn2["Button&lt;br>id='btn_sub'"] -->|"n_clicks&lt;br>Input"| update_counter(["update_counter&lt;br>callback"])
    update_counter -->|"children&lt;br>Output"| counter["Paragraph&lt;br>id='counter'"]
    update_counter -->|"style&lt;br>Output"| counter["Paragraph&lt;br>id='counter'"]
    
    btn1["Button&lt;br>id='btn_add'"] -->|"n_clicks&lt;br>Input"| update_pbar(["update_pbar&lt;br>callback"])
    btn2["Button&lt;br>id='btn_sub'"] -->|"n_clicks&lt;br>Input"| update_pbar(["update_pbar&lt;br>callback"])
    update_pbar -->|"value&lt;br>Output"| pbar["Progress&lt;br>id='progress_bar'"]
    
    style btn1 fill:#f9f,stroke:#333
    style btn2 fill:#f9f,stroke:#333
    style counter fill:#bbf,stroke:#333
    style pbar fill:#bbf,stroke:#333
    style update_counter fill:#ff9,stroke:#333
    style update_pbar fill:#ff9,stroke:#333</pre></div>



<p class="callout"><strong>Note</strong>: In this example, we could have updated both the progress bar and the counter in one callback. Whether to group callbacks or keep them separate depends on your app&#8217;s needs. Take a look at this article: <a href="https://dash-resources.com/dash-callbacks-best-practices-with-examples/">Dash Callbacks best practices</a>.</p>



<p>Theorically, these two callbacks should run at the same time. In practice, dash will first trigger on callback and then the second, with no specific order. If you absolutely need one callback to be triggered before the other, then you can chain callbacks (as described in the next section).</p>



<p>True parallel processing is often more complex : it requires having to CPUs (or two servers) running the app. It depends on how the app is deployed, how the server handles the request, etc&#8230;. Most of the time, the callbacks are just run one after the other.</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='chained-callbacks-callbacks-triggering-other-callbacks'  id="boomdevs_5" class="wp-block-heading" >Chained callbacks: callbacks triggering other callbacks</h2>



<p>As your app grows, the logic for updating outputs may depend on multiple interconnected inputs. This is where <strong>chaining callbacks</strong> becomes an invaluable tool. By breaking down complex updates into smaller, linked callbacks, you can maintain a clear and scalable workflow.</p>



<p>Let’s now update our little app and show a multiplier slider. It will multiply the click count with the slider value :</p>



<iframe src="https://scripts.dash-resources.com/callbacks/callbacks5.py/" width="100%" frameBorder="0"></iframe>



<pre class="wp-block-code"><code lang="python" class="language-python"># Declare the layout
app.layout = html.Div([
    html.P('Count: 0', id='counter'),
    html.Progress(id="progress_bar", value=0, max=50),
    html.Br(),
    html.Button('Click +', id='btn_add', n_clicks=0),
    html.Button('Click -', id='btn_sub', n_clicks=0),
    html.Br(),
    html.Label('Multiplier:'),
    dcc.Slider(id='multiplier', min=1, max=5, value=1, step=1)
], style={"max-width": "300px"})

@app.callback(
    Output('counter', 'children'), 
    Output('counter', 'style'), 
    Input('btn_add', 'n_clicks'),
    Input('btn_sub', 'n_clicks'),
    Input('multiplier', 'value')
)
def update_counter(n_clicks1, n_clicks2, multiplier):
    count = (n_clicks1 - n_clicks2) * multiplier
    content = f"Count: {count}"

    if count &gt;= 0:
        style = {'color': 'green'}
    else:
        style = {'color': 'red'}

    return content, style

@app.callback(
    Output('progress_bar', 'value'), 
    Input('btn_add', 'n_clicks'),
    Input('btn_sub', 'n_clicks'),
    Input('multiplier', 'value')
)
def update_pbar(n_clicks1, n_clicks2, multiplier):
    return (n_clicks1 - n_clicks2) * multiplier

</code></pre>



<p>We now have 3 inputs. And this number will increase as the app gets more complex ; and it will get worse when adding more callbacks that use the count result.</p>



<p>A good solution would be to store this count result into memory using a <code>dcc.Store</code> component. The <code>dcc.Store</code> is a special Dash component that allows you to store data in the browser&#8217;s memory &#8211; think of it as a variable that persists between callbacks. It&#8217;s invisible to users but can hold any JSON-serializable data (numbers, strings, lists, or dictionaries). This component is particularly useful for:</p>



<ul class="wp-block-list">
<li>Sharing data between callbacks without passing it through visible components</li>



<li>Reducing the number of redundant calculations</li>



<li>Storing intermediate results that multiple callbacks need to access</li>
</ul>



<p class="callout">Learn more about the <code>dcc.Store</code> component in the official documentation: <a href="https://dash.plotly.com/sharing-data-between-callbacks">https://dash.plotly.com/sharing-data-between-callbacks</a></p>



<p>Here&#8217;s how we can implement it:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">app.layout = html.Div([
    dcc.Store(id='count_memory', data=0),  # Store component to save the count
    # ...
], style={"max-width": "300px"})

@app.callback(
    Output('count_memory', 'data'),  # Update the stored count
    Input('btn_add', 'n_clicks'),
    Input('btn_sub', 'n_clicks'),
    Input('multiplier', 'value'),
    State('count_memory', 'data')  # Access the current stored count
)
def update_store(n_clicks1, n_clicks2, multiplier, current_count):
    count = (n_clicks1 - n_clicks2) * multiplier
    return count

@app.callback(
    Output('counter', 'children'), 
    Output('counter', 'style'), 
    Input('count_memory', 'data')
)
def update_counter_display(count):
    content = f"Count: {count}"
    style = {'color': 'green'} if count &gt;= 0 else {'color': 'red'}
    return content, style

@app.callback(
    Output('progress_bar', 'value'), 
    Input('count_memory', 'data')
)
def update_pbar(count):
    return count
</code></pre>



<p>We now have a callback responsible for the computation (<code>update_store</code>) and two callbacks responsible for display (<code>update_counter_display</code> and <code>update_pbar</code>). This gives the following diagram:</p>



<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">flowchart LR
    btn1["Button&lt;br>id='btn_add'"] -->|"n_clicks&lt;br>Input"| update_store(["update_store&lt;br>callback"])
    btn2["Button&lt;br>id='btn_sub'"] -->|"n_clicks&lt;br>Input"| update_store
    multiplier["Input&lt;br>id='multiplier'"] -->|"value&lt;br>Input"| update_store
    store["Store&lt;br>id='count_memory'"] -.->|"data&lt;br>State"| update_store
    update_store -->|"data&lt;br>Output"| store
    
    store -->|"data&lt;br>Input"| update_counter(["update_counter&lt;br>callback"])
    update_counter -->|"children&lt;br>Output"| counter["Paragraph&lt;br>id='counter'"]
    update_counter -->|"style&lt;br>Output"| counter
    
    store -->|"data&lt;br>Input"| update_pbar(["update_pbar&lt;br>callback"])
    update_pbar -->|"value&lt;br>Output"| pbar["Progress&lt;br>id='progress_bar'"]

    style btn1 fill:#f9f,stroke:#333
    style btn2 fill:#f9f,stroke:#333
    style multiplier fill:#f9f,stroke:#333
    style counter fill:#bbf,stroke:#333
    style pbar fill:#bbf,stroke:#333
    style update_store fill:#ff9,stroke:#333
    style update_counter fill:#ff9,stroke:#333
    style update_pbar fill:#ff9,stroke:#333
    style store fill:#bbf,stroke:#333</pre></div>



<h3 id='using-state-instead-of-input'  id="boomdevs_6" class="wp-block-heading" >Using State instead of Input</h3>



<p>You&#8217;ll notice in our <code>update_store</code> callback that we use <code>State('count_memory', 'data')</code> instead of <code>Input('count_memory', 'data')</code>. This is because we want to access the current stored count but don&#8217;t want changes to it to trigger this callback. If we used Input, we&#8217;d create a <strong>circular dependency</strong>: the store update would trigger the callback, which would update the store, which would trigger the callback again!</p>



<p>Using <code>State</code> allows us to read the stored value only when we need it, while the actual triggers for our callback remain the button clicks and multiplier changes. This pattern of using State to access stored values is common when working with <code>dcc.Store</code> components &#8211; it gives you access to persistent data without creating unintended callback chains.</p>



<p class="callout"><strong>TL;DR</strong>. A <code>State</code> does not trigger a callback while an <code>Input</code> will. But both can be used to retrieve a component&#8217;s property.</p>



<h3 id='advantages-and-disadvantages-of-chaining-callbacks'  id="boomdevs_7" class="wp-block-heading" >Advantages and disadvantages of chaining callbacks</h3>



<p>Chained callbacks help keep your code <strong>clean and logical</strong>. Instead of cramming everything into one big callback, you can break things down into smaller pieces that each do one job well. For example, one callback handles the data processing while another takes care of displaying it. This approach also opens up more interactive possibilities &#8211; when one callback&#8217;s output feeds into another&#8217;s input, you can create complex interactions like filters that update charts, which then update summary statistics.</p>



<p>But there are some <strong>pitfalls</strong> to be careful about. <strong>Debugging can get tricky</strong> when you have callback A triggering B, which triggers C and D, which then trigger even more callbacks&#8230; well, good luck figuring out where something went wrong!</p>



<p>You should also watch out for <strong>circular dependencies</strong> &#8211; that&#8217;s when your callbacks form a loop (A triggers B triggers C triggers A again). Dash will catch this and throw an error, but it&#8217;s a common gotcha when you&#8217;re building complex apps.</p>



<p class="callout">Want to learn more about avoiding circular dependencies? We have a full article here: <a href="https://dash-resources.com/writing-in-process/?article=avoid-circular-dependancies">How to avoid circular dependancies problem in Dash Plotly</a></p>



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



<h2 id='other-types-of-callbacks'  id="boomdevs_8" class="wp-block-heading" >Other types of callbacks</h2>



<p>While we&#8217;ve covered the main callback patterns, Dash offers several specialized types of callbacks for specific use cases:</p>



<h3 id='background-callbacks'  id="boomdevs_9" class="wp-block-heading" >Background Callbacks</h3>



<p>Background callbacks handle time-consuming operations without blocking your app&#8217;s responsiveness. They run intensive tasks in separate threads, making them perfect for heavy computations or long-running processes:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output("results", "children"),
    Input("start", "n_clicks"),
    background=True
)
def slow_computation(n_clicks):
    # Your heavy computation here
    time.sleep(5)  # Simulating long task
    return "Done!"</code></pre>



<p>Learn more in our guide: <a href="https://dash-resources.com/writing-in-process/?article=how to use bavkground callbacks in dash plotly">How to Use Background Callbacks in Dash Plotly</a></p>



<h3 id='clientside-callbacks'  id="boomdevs_10" class="wp-block-heading" >Clientside Callbacks</h3>



<p>Clientside callbacks execute <strong>directly in the browser</strong>, eliminating server round-trips for faster performance. They&#8217;re ideal for simple UI updates and responsive interactions, but require JavaScript:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">app.clientside_callback(
    """
    function(value) {
        return 'You typed: ' + value
    }
    """,
    Output('output-div', 'children'),
    Input('input-box', 'value')
)</code></pre>



<p>Learn more: <a href="https://dash-resources.com/writing-in-process/?article=when to use clientside calbacks for dash plotly">When to use clientside callbacks for Dash Plotly</a>.</p>



<h3 id='pattern-matching-callbacks'  id="boomdevs_11" class="wp-block-heading" >Pattern-Matching Callbacks</h3>



<p>Pattern-matching callbacks enable handling dynamic sets of components through ID patterns. They&#8217;re particularly useful to attach callbacks to components that are created or removed at runtime:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">@app.callback(
    Output({'type': 'dynamic-output', 'index': MATCH}, 'children'),
    Input({'type': 'dynamic-input', 'index': MATCH}, 'value')
)
def update_dynamic_output(value):
    return f"You entered: {value}"</code></pre>



<p>Read the guide: <a href="https://dash-resources.com/writing-in-process/?article=pattern-matching-callbacks">How to use Pattern Matching Callbacks (Dash Plotly)</a>.</p>



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



<p>In this article, we explored the main types of Dash callbacks, from simple callbacks to multi-input, multi-output, and chained callbacks.</p>



<p>To deepen your understanding of Dash callbacks, you can:</p>



<ul class="wp-block-list">
<li><strong>Read the <a href="https://dash.plotly.com/basic-callbacks">official documentation</a> on Dash callbacks:</strong> it’s dense, but there are much more detailed information than in this article (which was focused on giving simple examples).</li>



<li><strong>Play with the toy examples above:</strong> you could add more inputs, change the computation, add other types of components… be creative! It’s the best to learn.</li>



<li><strong>Go deeper with other types of callbacks</strong>: you now have the fundation for understand how callbacks triggered. Learn how to use background callbacks, clientside callbacks and patter-matching callbacks.</li>
</ul>



<p>I hope you enjoyed this tutorial! <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>



<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></p>
<p>L’article <a href="https://dash-resources.com/tutorial-dash-callbacks-schemas-examples/">Tutorial: Dash callbacks (schemas &amp; examples)</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Dash plotly vs. Django: what are the differences?</title>
		<link>https://dash-resources.com/dash-plotly-vs-django/</link>
		
		<dc:creator><![CDATA[Fran]]></dc:creator>
		<pubDate>Tue, 24 Dec 2024 13:19:32 +0000</pubDate>
				<category><![CDATA[Beginner level]]></category>
		<guid isPermaLink="false">http://dash-resources.com/?p=1</guid>

					<description><![CDATA[<p>Dash and Django are two Python frameworks that can be used to build websites and web apps. But what makes them different? And which one [...]</p>
<p>L’article <a href="https://dash-resources.com/dash-plotly-vs-django/">Dash plotly vs. Django: 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 Django are two<strong> </strong>Python frameworks that can be used to build websites and web apps. 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 two <strong>code examples</strong> of uses cases that suit the former and the latter. At the end, we summarize the comparison for Dash vs. Django in <strong>comprehensive tables</strong>. 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-django">About Django</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-dash-is-better">Where Dash is better</a>
          </li>
          <li class="last">
            <a href="#where-django-is-better">Where Django is better</a>
          </li>
        </ul>
      </li>
      <li>
        <a href="#dash-plotly-vs-django-table-comparison">Dash plotly vs. Django: 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 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>. While Django is a perfect framework for traditional websites, Dash shines in creating interactive web applications easily. The choice depends on what you need to build and if you want 100% Python or not.</p>



<h2 id='about-django'  id="boomdevs_1" class="wp-block-heading" >About Django</h2>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" width="794" height="468" src="https://dash-resources.com/wp-content/uploads/2024/12/image-1.png" alt="" class="wp-image-47" style="width:228px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2024/12/image-1.png 794w, https://dash-resources.com/wp-content/uploads/2024/12/image-1-300x177.png 300w, https://dash-resources.com/wp-content/uploads/2024/12/image-1-768x453.png 768w" sizes="auto, (max-width: 794px) 100vw, 794px" /></figure>
</div>


<p><a href="https://www.djangoproject.com/">Django</a> is a full-featured web framework that&#8217;s designed for building complex web applications. It  excels at:</p>



<ul class="wp-block-list">
<li>Building traditional web applications with user authentication, database management, and admin interfaces</li>



<li>Content management systems and e-commerce platforms</li>



<li>RESTful API development</li>



<li>Handling complex database operations with its powerful ORM</li>



<li>Managing user sessions, forms, and security features</li>
</ul>



<p class="callout"><strong><span style="text-decoration: underline;">Example:</span></strong> Django is an excellent solution if you&#8217;re building an e-commerce website that needs user registration, product management, shopping carts, and payment processing.</p>



<p>It is also noteworthy that Django has a strong extension ecosystem and <a href="https://www.djangoproject.com/community/">large community</a>.</p>



<h2 id='about-dash'  id="boomdevs_2" class="wp-block-heading" >About Dash</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/2024/12/image.png" alt="" class="wp-image-46" style="width:218px;height:auto" srcset="https://dash-resources.com/wp-content/uploads/2024/12/image.png 858w, https://dash-resources.com/wp-content/uploads/2024/12/image-300x210.png 300w, https://dash-resources.com/wp-content/uploads/2024/12/image-768x537.png 768w" sizes="auto, (max-width: 858px) 100vw, 858px" /></figure>
</div>


<p><a href="https://dash.plotly.com/">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>Rapid prototyping of data visualization interfaces</li>
</ul>



<p class="callout"><strong><span style="text-decoration: underline;">Example :</span></strong> Dash is an excellent choice if you&#8217;re providing a web application with data visualization that requires a lot of interactivity, such as a monitoring &amp; analysis platform.</p>



<p>Dash is a growing framework with a vibrant community. The recent framework updates tend to fill the gap for more classic website features (e.g. <a href="https://dash.plotly.com/urls">built-in URL routing</a> was added in 2022).</p>



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



<p>I assume that if you read this, you are somehow familiar with Python. So let&#8217;s take a look at some tangible examples, with code. That will be much more illustrative.</p>



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



<p>The most distinctive built-in feature of Dash is its reactive callback system for building interactive data visualizations without JavaScript.</p>



<p>Here is a code example for Dash: </p>



<pre class="wp-block-code"><code lang="python" class="language-python">###### DASH EXAMPLE ######
from dash import Dash, dcc, html, Input, Output
import plotly.express as px

app = Dash(__name__)

app.layout = html.Div([
    dcc.Dropdown(
        id='category-dropdown',
        options=[{'label': x, 'value': x} for x in ['A', 'B', 'C']],
        value='A'
    ),
    dcc.Graph(id='interactive-graph')
])

@app.callback(
    Output('interactive-graph', 'figure'),
    Input('category-dropdown', 'value')
)
def update_graph(selected_category):
    # This automatically re-renders when dropdown changes
    df = get_data(selected_category)
    return px.scatter(df, x='x', y='y')</code></pre>



<p>The code illustrates how straightforward it is. First we define the app layout containing a graph, then this graph is updated with a callback function (<code>update_graph</code>). That&#8217;s it!</p>



<p> It does not require any JavaScript code, <strong>everything is handled in Python</strong>.</p>



<p>To achieve the same in Django, we would need to build an API endpoint and use JavaScript to get the data, then plot the data. Let&#8217;s see the code:</p>



<pre class="wp-block-code"><code lang="python" class="language-python">###### DJANGO EXAMPLE ######
# urls.py
urlpatterns = [
    path('', views.dashboard, name='dashboard'),
    path('api/data/&lt;str:category>', views.get_graph_data, name='get_graph_data'),
]

# views.py
def dashboard(request):
    return render(request, 'dashboard.html', {
        'categories': ['A', 'B', 'C']
    })

def get_graph_data(request, category):
    df = get_data(category)
    return JsonResponse({
        'x': df['x'].tolist(),
        'y': df['y'].tolist()
    })

# dashboard.html
{% block content %}
&lt;select id="category-dropdown">
    {% for category in categories %}
        &lt;option value="{{ category }}">{{ category }}&lt;/option>
    {% endfor %}
&lt;/select>
&lt;div id="graph">&lt;/div>

&lt;script>
// Need to write custom JavaScript for interactivity
document.getElementById('category-dropdown').addEventListener('change', function(e) {
    fetch(`/api/data/${e.target.value}`)
        .then(response => response.json())
        .then(data => {
            Plotly.newPlot('graph', [{
                x: data.x,
                y: data.y,
                type: 'scatter'
            }]);
        });
});
&lt;/script>
{% endblock %}</code></pre>



<p>In Django, every interaction must be handcrafted (in vanilla JS or with a framework such as React or Angular). Dash provides it &#8220;<em>out-of-the-box</em>&#8221; and as well be paired with JavaScript or extended with React components if needed. </p>



<h3 id='where-django-is-better'  id="boomdevs_5" class="wp-block-heading" >Where Django is better</h3>



<p>Even if we can handle user login with Dash and flask-login, the out-of-the-box <a href="https://dash.plotly.com/authentication">authentication</a> is really basic. On the other hand, Django automatically handles CSRF protection, password hashing, and session security.</p>



<p>It also provides built-in protection against common vulnerabilities (XSS, CSRF, SQL injection) :</p>



<pre class="wp-block-code"><code lang="python" class="language-python">###### DJANGO EXAMPLE ######
# views.py
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect

def register(request):
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            form.save()  # Handles password hashing, validation
            return redirect('login')
    return render(request, 'register.html', {'form': form})

# template
&lt;form method="post">
    {% csrf_token %}  # CSRF protection automatic
    {{ form.as_p }}   # XSS protection automatic
    &lt;button type="submit">Register&lt;/button>
&lt;/form></code></pre>



<p>With Dash, you need to implement these security features yourself (at the risk of not doing them correctly):</p>



<pre class="wp-block-code"><code lang="python" class="language-python">###### DASH EXAMPLE ######
from dash import Dash, html, dcc, Input, Output, State
from werkzeug.security import generate_password_hash
import secrets

app = Dash(__name__)
app.server.secret_key = secrets.token_hex(16)

app.layout = html.Div([
    # Must manually add CSRF token
    html.Div(id='csrf-token', style={'display': 'none'}, 
            children=secrets.token_hex(16)),
    dcc.Input(id='username', type='text'),
    dcc.Input(id='password', type='password'),
    html.Button('Register', id='register-button'),
])

@app.callback(
    Output('output', 'children'),
    Input('register-button', 'n_clicks'),
    State('username', 'value'),
    State('password', 'value'),
    State('csrf-token', 'children')
)
def register(n_clicks, username, password, csrf_token):
    if n_clicks:
        # Must manually implement:
        # - CSRF validation
        # - Input sanitization (XSS protection)
        # - Password validation
        hashed = generate_password_hash(password)
        # Manual database insertion with SQL injection protection</code></pre>



<p>In that case, working with Django provides a safer and more stable base.</p>



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



<p>We compared each framework feature regarding how it is either <em>built-in</em> (<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;" />), <em>possible with extension</em> (—), or <em>needs to be implemented manually</em> (<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" >Web Application Features</h3>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Feature</th><th>Django</th><th>Dash</th><th>Notes</th></tr></thead><tbody><tr><td>Admin Interface</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>Django&#8217;s auto-admin is unique</td></tr><tr><td>Database ORM</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>Django ORM vs SQLAlchemy/SQL</td></tr><tr><td>Authentication</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>Built-in vs Flask-Login</td></tr><tr><td>Template Engine</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>Django templates vs React</td></tr><tr><td>URL 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>Django routing is much more scalable</td></tr><tr><td>Internationalization (i18n)</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>Django has built-in i18n tools</td></tr></tbody></table></figure>



<p>Django shines in traditional web development with its comprehensive built-in features. The admin interface is particularly powerful, automatically generating a CRUD interface for your models. While Dash (through Flask) can achieve similar functionality, it requires additional setup and packages. </p>



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



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Feature</th><th>Django</th><th>Dash</th><th>Notes</th></tr></thead><tbody><tr><td>Interactive Graphs</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>Dash: deep Plotly integration</td></tr><tr><td>Real-time Updates</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>Dash: built-in reactivity</td></tr><tr><td>Callback System</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><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&#8217;s core feature</td></tr><tr><td>Component State</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>Dash: automatic state management</td></tr></tbody></table></figure>



<p>This is where Dash truly excels. Its reactive callback system allows you to build complex interactive visualizations without writing JavaScript. Components automatically update when their inputs change, making it easier and faster to build interactive web apps.</p>



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



<p>Django provides a more mature development environment with comprehensive testing tools and database migration systems. Dash, while having a smaller ecosystem, integrates seamlessly with data science tools like pandas and numpy. </p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th>Feature</th><th>Django</th><th>Dash</th><th>Notes</th></tr></thead><tbody><tr><td>Testing Framework</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></td></tr><tr><td>Database Migrations</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>Django: built-in </td></tr><tr><td>Community</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>Django has larger ecosystem</td></tr><tr><td>Data Science Tools</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>Better integration with pandas/numpy</td></tr></tbody></table></figure>



<p>Both frameworks can be deployed similarly, but Django offers more out-of-the-box deployment options and production-ready features. That&#8217;s because Django benefits from a more mature ecosystem and developer community.</p>



<h2 id='conclusion'  id="boomdevs_10" class="wp-block-heading" >Conclusion </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 any web application. <strong>It is more about how &#8220;built-in&#8221; certain features are</strong>.</p>



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



<ul class="wp-block-list">
<li><strong>Choose</strong> <strong>Django</strong> if you need a stable framework to build a traditional website or if you have JavaScript knowledge to handle the interactivity.</li>
</ul>



<ul class="wp-block-list">
<li><strong>Choose</strong> <strong>Dash</strong> if you need to build an interactive web app that embeds some data visualization and/or if you only know Python.</li>
</ul>



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



<p>I hope this article helped you better understand the differences between Django and Dash, just as it helped me!</p>
<p>L’article <a href="https://dash-resources.com/dash-plotly-vs-django/">Dash plotly vs. Django: what are the differences?</a> est apparu en premier sur <a href="https://dash-resources.com">dash-resources.com</a>.</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
