Day 4 of 5
⏱ ~55 minutes
Build a Data Dashboard — Day 4

Multi-Page Layout and Session State

Single-page dashboards get cluttered fast. Today you'll structure your app into multiple pages using Streamlit's native multi-page system, build a proper sidebar, and use session state to share data between pages.

Streamlit's Multi-Page System

Streamlit has a built-in multi-page system. Create a pages/ folder next to your main script and add Python files. Each file becomes a page automatically.

file structure
my_dashboard/
├── app.py              ← entry point (home page)
├── pages/
│   ├── 1_Sales.py      ← automatically becomes "Sales" page
│   ├── 2_Products.py   ← becomes "Products" page
│   └── 3_Settings.py   ← becomes "Settings" page
├── .streamlit/
│   └── secrets.toml
└── sales.db

The number prefix controls the sort order. Underscores become spaces in the sidebar nav. Streamlit creates the sidebar navigation automatically.

python — app.py (home page)
import streamlit as st

st.set_page_config(
    page_title="Sales Dashboard",
    page_icon="📊",
    layout="wide",
    initial_sidebar_state="expanded"
)

st.title("Sales Dashboard")
st.write("Welcome. Use the sidebar to navigate.")

# Show summary cards on the home page
col1, col2, col3, col4 = st.columns(4)
col1.metric("Total Revenue", "$2.4M", "+12%")
col2.metric("Deals Closed", "847", "+23")
col3.metric("Avg Order", "$2,836", "-3%")
col4.metric("Active Regions", "4", "0")

Building the Sidebar

Use st.sidebar to add content to the sidebar that appears on every page — like filters that apply globally.

python — pages/1_Sales.py
import streamlit as st
import pandas as pd
import plotly.express as px
from sqlalchemy import create_engine

engine = create_engine("sqlite:///sales.db")

# Sidebar filters — shows up in the sidebar
with st.sidebar:
    st.header("Filters")
    regions = pd.read_sql("SELECT DISTINCT region FROM sales", engine)["region"].tolist()
    selected_regions = st.multiselect("Regions", regions, default=regions)

    date_range = st.date_input(
        "Date Range",
        value=("2024-01-01", "2024-03-31")
    )

# Load and filter data
@st.cache_data(ttl=300)
def load_data():
    return pd.read_sql("SELECT * FROM sales", engine, parse_dates=["date"])

df = load_data()
df = df[df["region"].isin(selected_regions)]
if len(date_range) == 2:
    df = df[(df["date"] >= str(date_range[0])) & (df["date"] <= str(date_range[1]))]

# Page content
st.title("Sales Analysis")
st.caption(f"{len(df):,} records | {selected_regions}")

fig = px.bar(
    df.groupby("region")["revenue"].sum().reset_index(),
    x="region", y="revenue", title="Revenue by Region"
)
st.plotly_chart(fig, use_container_width=True)

Session State — Sharing Data Between Pages

Each page reruns independently. If you want a filter set on one page to persist when the user navigates, use st.session_state.

python — session state basics
# Initialize state with a default
if "selected_region" not in st.session_state:
    st.session_state.selected_region = "All"

# Widget that writes to session state
region = st.selectbox(
    "Region",
    ["All", "North", "South", "East", "West"],
    key="selected_region"  # automatically reads/writes session state
)

# Read state anywhere (including other pages)
st.write(f"Current region: {st.session_state.selected_region}")

# Manually update state
if st.button("Reset to All Regions"):
    st.session_state.selected_region = "All"
    st.rerun()  # force a rerun to reflect the change

Page Configuration and Wide Layout

python
# Must be the FIRST Streamlit call on each page
st.set_page_config(
    page_title="Products — Sales Dashboard",
    page_icon="📦",
    layout="wide"   # "centered" is the default
)

# Customize sidebar
st.sidebar.image("logo.png", width=120)
st.sidebar.markdown("---")
st.sidebar.caption("Data refreshes every 5 minutes")
ℹ️
layout="wide" uses the full browser width. Use it for dashboards with multiple charts side by side. Use "centered" for text-heavy pages.

Editable Data Tables

Streamlit 1.23+ supports editable tables with st.data_editor(). Users can edit cells and you get the modified dataframe back.

python
st.subheader("Sales Records")

# Read-only table
st.dataframe(df, use_container_width=True)

# Editable table
edited_df = st.data_editor(
    df[["date", "region", "product", "revenue"]],
    use_container_width=True,
    num_rows="dynamic"  # allows adding/deleting rows
)

if st.button("Save Changes"):
    # edited_df contains the modified version
    st.success(f"Saved {len(edited_df)} rows")
📝 Day 4 Exercise
Build a 3-Page Dashboard App
  1. Create the folder structure: app.py + pages/1_Sales.py + pages/2_Products.py.
  2. Set up st.set_page_config(layout="wide") on all three files.
  3. Add sidebar filters (region + date range) to the Sales page.
  4. Store the selected region in st.session_state so it persists when navigating.
  5. On the Products page, show a bar chart of revenue by product, using the stored region filter.
  6. Run with streamlit run app.py and verify the sidebar nav appears.

Day 4 Summary

  • Add pages by creating a pages/ folder. File names become navigation links automatically.
  • Use st.sidebar for filters that apply to the current page.
  • Use st.session_state with a widget key= to persist values across page navigation.
  • layout="wide" in set_page_config uses the full browser width.
Challenge

Add a Settings page where users can toggle between light and dark chart themes. Store the theme preference in st.session_state and apply it to every Plotly chart in the app via a shared helper function.

Finished this lesson?