streamlit dashboard tutorial content is everywhere, but the single concept that actually confuses newcomers — Streamlit's top-to-bottom rerun execution model — rarely gets the dedicated explanation it needs. This streamlit python guide covers that model directly, plus the specific, commonly-hit pitfalls (caching, st.set_page_config ordering) that separate a genuinely helpful guide from a shallow widget list.
A Dashboard Without Touching HTML, CSS, or JavaScript
What Streamlit actually removes from the process
With Streamlit, there's no need to navigate a web app's front-end framework jungle, or wrestle with HTML, CSS, and JavaScript to showcase data and insights — the entire dashboard is written in plain Python, top to bottom.
Why Streamlit specifically, versus a custom web app
For most data dashboard use cases, Streamlit is genuinely the right choice — its caching system, wide range of built-in widgets, and free Community Cloud deployment option together make it the fastest realistic path from a Python script to a shareable, interactive web application, without needing separate front-end expertise at all.
What this article covers
The rerun execution model (the genuine foundation everything else builds on), core widgets and layout, integrating pandas and Plotly directly, and the specific pitfalls that trip up nearly every Streamlit beginner at least once.
The Core Mental Model: Streamlit Reruns Your Script Top-to-Bottom
The single concept worth understanding first
This is worth understanding thoroughly before writing a single widget: Streamlit reruns the entire script, top to bottom, on every single interaction — clicking a button, moving a slider, changing a dropdown. This sounds inefficient at first, but it's actually genuinely liberating: you write plain, top-down, imperative Python — no callbacks, no event handlers, no manually-managed state machines, which is exactly the tangled complexity traditional web frameworks require you to manage directly.
import streamlit as st
st.title("My Dashboard")
name = st.text_input("What's your name?")
st.write(f"Hello, {name}!")Every time the text input changes, this entire script reruns from the top — st.title() executes again, st.text_input() executes again (now returning the new value), and st.write() executes again with the updated greeting. There's no explicit "on change" handler anywhere — the rerun-everything model is the event handling mechanism.
Why this matters practically for structuring an app
Because a slider, checkbox, or dropdown change triggers the entire script to run again from the very top, any genuinely expensive computation — loading a large CSV, running a machine learning model, querying a database — needs explicit caching. Without it, that expensive operation would re-run on every single interaction, even ones completely unrelated to that specific computation, making the app feel sluggish and unresponsive.
@st.cache_data: the fix
import pandas as pd
import streamlit as st
@st.cache_data
def load_data():
return pd.read_csv("large_dataset.csv")
df = load_data()Decorating a data-loading (or otherwise expensive) function with @st.cache_data tells Streamlit to only actually re-execute that function when its inputs genuinely change — on every other rerun, Streamlit reuses the previously computed, cached result instantly, rather than reloading the entire CSV from disk again. This single decorator is what keeps a Streamlit app responsive despite the rerun-everything model underlying the entire framework.
Building the Basic Layout: Title, Sidebar, and Filters
The skeleton of any dashboard
import streamlit as st
st.set_page_config(page_title="Sales Dashboard", layout="wide")
st.title("Sales Dashboard")
st.write("An overview of regional sales performance.")st.set_page_config() sets page-level configuration — the browser tab's title, and the overall page layout ("wide" uses the full browser width, rather than a narrower, centered default). st.title() and st.write() handle basic text and headers — the essential skeleton every dashboard starts from.
Sidebar filters
import pandas as pd
df = pd.DataFrame({
"species": ["Setosa", "Versicolor", "Virginica"] * 10,
"petal_length": [1.4, 4.5, 5.9] * 10,
})
selected_species = st.sidebar.multiselect(
"Select species", optidf["species"].unique(), default=df["species"].unique()
)
filtered_df = df[df["species"].isin(selected_species)]st.sidebar holds filter widgets — st.selectbox(), st.multiselect(), st.slider(), and similar — separately from the main content area, keeping filters visually distinct from the actual data and charts they control. Applying the selected filter values directly to a pandas DataFrame, exactly as shown here (df[df["species"].isin(selected_species)]), ties directly back to the boolean filtering covered in the earlier pandas data selection article — Streamlit widgets simply provide the interactive front-end; the actual filtering logic underneath is ordinary pandas you already know.
Arranging elements side by side with columns
col1, col2 = st.columns(2)
with col1:
st.metric("Total Sales", "$45,231")
with col2:
st.metric("Average Order", "$127")st.columns() arranges elements side by side, rather than Streamlit's vertical, stacked default — genuinely useful both for placing something like a logo image next to a title, and for laying out multiple charts or metrics in a row rather than one long, scrolling column.
Displaying Data and Interactive Charts
Showing tabular data
st.dataframe(filtered_df)
st.dataframe(filtered_df.describe())st.dataframe() shows tabular data directly, automatically interactive — built-in column sorting and scrolling, with zero extra code required beyond passing in the DataFrame itself. Passing df.describe() directly gives a quick, instantly interactive statistics summary — tying back to the earlier pandas data cleaning article's use of .describe() as a first diagnostic step, now displayed directly within the dashboard itself.
Integrating Plotly
import plotly.express as px
fig = px.histogram(filtered_df, x="petal_length", color="species")
st.plotly_chart(fig, use_cTrue)This integrates exactly the same way covered in the previous Plotly article — build the figure with px.histogram() (or any other Plotly Express or Graph Objects function) exactly as you would in a standalone script or Jupyter notebook — except fig.show() is replaced with st.plotly_chart(fig, use_cTrue), which renders the interactive Plotly chart directly within the Streamlit dashboard itself, rather than opening it separately in its own browser tab or notebook cell.
A practical combined example
col1, col2 = st.columns(2)
show_trend = st.checkbox("Show trend line", value=False)
with col1:
fig1 = px.histogram(filtered_df, x="petal_length", color="species")
st.plotly_chart(fig1, use_cTrue)
with col2:
fig2 = px.scatter(filtered_df, x="petal_length", y="petal_length",
trendline="ols" if show_trend else None)
st.plotly_chart(fig2, use_cTrue)Placing two related Plotly charts side by side in st.columns(2), with a checkbox widget controlling one chart's appearance in real time, demonstrates the full rerun model from Section 2 concretely in action: toggling the checkbox reruns the entire script, show_trend picks up the new value, and fig2 is rebuilt with (or without) a trend line — all automatically, with no manual "update the chart" logic written anywhere.
Common Pitfalls and Scaling Beyond a Single Page
The most frequently hit beginner mistake
import streamlit as st
st.write("Hello!") # any Streamlit command here...
st.set_page_config(page_title="My App") # ...before this raises an errorStreamlitAPIException: set_page_config() can only be called once per app,
and must be called as the first Streamlit command in your script.This is worth flagging explicitly, since it's genuinely the most common early mistake: st.set_page_config() must be the very first Streamlit command in the entire script — before st.write(), before referencing st.sidebar, before anything else Streamlit-related. Calling anything else first, even something as innocuous-looking as a single st.write(), raises a StreamlitAPIException immediately.
Best practices for polished dashboard visuals
Always set use_cTrue on charts, so they scale responsively to the available space, rather than rendering at a fixed size that might look cramped or oddly proportioned depending on the viewer's actual screen width. Keep a consistent color palette across multiple charts, using the same color_discrete_sequence parameter passed to each Plotly Express call — genuinely important for a dashboard's overall visual coherence, since inconsistent, randomly-assigned colors across different charts make it harder for a viewer to track a specific category visually as they move between panels. Add meaningful axis labels so charts are self-explanatory without needing a separate caption underneath explaining what they show.
Scaling to multiple pages
Streamlit's built-in multi-page architecture uses a genuinely simple convention: any .py file placed in a pages/ directory, sitting next to the entry-point script, automatically appears as a separate page in the sidebar navigation — no manual routing configuration, no separate page-registration step required.
my_dashboard/
├── main.py
└── pages/
├── 1_Overview.py
└── 2_Detailed_Analysis.pyStreamlit automatically discovers both files inside pages/ and adds them as navigable pages, using the filename itself (minus the numeric prefix, which controls ordering) as the page's display name in the sidebar.
A closing note on when to graduate to Dash instead
Streamlit's rerun-everything model, covered throughout this article, is genuinely what makes it so fast to build with — but it's also precisely what becomes limiting once an application's needs grow more complex: genuinely fine-grained control over exactly which parts of a page update (rather than the entire script re-executing), more complex multi-user state management, or highly custom, non-standard layouts that Streamlit's straightforward vertical/columns model doesn't comfortably express. Dash (built on top of Plotly, exactly as covered in the previous Plotly article) offers considerably more granular control over callbacks and layout, at the cost of noticeably more code and a steeper learning curve than Streamlit's straightforward, script-rerun simplicity. For the overwhelming majority of internal data dashboards and quick, shareable analysis tools, Streamlit remains the faster, simpler starting point — reach for Dash specifically once you've genuinely outgrown what Streamlit's model comfortably supports, not preemptively.