lib/streamlit/.agents/skills/developing-with-streamlit/references/dashboards.md
Compose metrics, charts, and data into clean dashboard layouts.
Use border=True to create visual cards. Supported on st.container, st.metric, st.columns, and st.form:
# Container card
with st.container(border=True):
st.subheader("Sales Overview")
st.line_chart(sales_data)
# Metric card
st.metric("Revenue", "$1.2M", "+12%", border=True)
# Column cards
for col in st.columns(3, border=True):
with col:
st.metric("Users", "1.2k")
Add context to cards with headers or bold text:
# With subheader
with st.container(border=True):
st.subheader("Monthly Trends")
st.line_chart(data)
# With bold label
with st.container(border=True):
st.markdown("**Top Products**")
st.dataframe(top_products)
Use horizontal containers for responsive metric rows:
with st.container(horizontal=True):
st.metric("Revenue", "$1.2M", "-7%", border=True)
st.metric("Users", "762k", "+12%", border=True)
st.metric("Orders", "1.4k", "+5%", border=True)
Horizontal containers wrap on smaller screens. Prefer them over st.columns for metric rows.
Add trend context with chart_data:
weekly_values = [700, 720, 715, 740, 762, 755, 780]
st.metric(
"Active Users",
"780k",
"+3.2%",
border=True,
chart_data=weekly_values,
chart_type="line", # or "bar"
)
Sparklines show y-values only—use for evenly-spaced data like daily/weekly snapshots.
Combine cards into a dashboard:
# KPI row
with st.container(horizontal=True):
st.metric("Revenue", "$1.2M", "-7%", border=True, chart_data=rev_trend, chart_type="line")
st.metric("Users", "762k", "+12%", border=True, chart_data=user_trend, chart_type="line")
st.metric("Orders", "1.4k", "+5%", border=True, chart_data=order_trend, chart_type="bar")
# Charts row
col1, col2 = st.columns(2)
with col1:
with st.container(border=True):
st.subheader("Revenue by Region")
st.bar_chart(region_data, x="region", y="revenue")
with col2:
with st.container(border=True):
st.subheader("Monthly Trend")
st.line_chart(monthly_data, x="month", y="value")
# Data table
with st.container(border=True):
st.subheader("Recent Orders")
st.dataframe(orders_df, hide_index=True)
When a dashboard has multiple cards with independent, compute-intensive data loads (separate queries or API calls), combine @st.fragment(parallel=True) with st.skeleton. The fragments load concurrently, and each card shows a skeleton until its own data is ready—so the dashboard fills in card-by-card instead of blocking on the slowest query.
@st.cache_data(ttl="15m")
def load_revenue():
... # Slow query / API call
@st.cache_data(ttl="15m")
def load_orders():
... # Independent slow query / API call
@st.fragment(parallel=True)
def revenue_card():
with st.container(border=True):
st.subheader("Revenue by Region")
with st.skeleton(height=260):
data = load_revenue()
st.bar_chart(data, x="region", y="revenue")
@st.fragment(parallel=True)
def orders_card():
with st.container(border=True):
st.subheader("Recent Orders")
with st.skeleton(height=260):
data = load_orders()
st.dataframe(data, hide_index=True)
col1, col2 = st.columns(2)
with col1:
revenue_card()
with col2:
orders_card()
The st.skeleton context manager shows the placeholder while its block runs (after a short delay) and clears it once the content is rendered—anything written inside stays visible. Keep the card title outside the with block so it stays stable while the body loads, and cache the loaders (@st.cache_data) so cards render instantly on later reruns. See performance.md for more on parallel fragments and caching.
Put filters in the sidebar to maximize dashboard space:
with st.sidebar:
date_range = st.date_input("Date range", value=(start, end))
region = st.multiselect("Region", regions, default=regions)
# Main area is all dashboard content
Ready-to-use dashboard templates are available in assets/templates/apps/:
| Template | Features |
|---|---|
dashboard-metrics | @st.fragment(parallel=True) cards with st.skeleton, chart/table toggle, time-series charts, date filtering |
dashboard-companies | Company comparison with sparkline columns, filterable data tables, custom cache spinner |
dashboard-compute | @st.fragment(parallel=True) with st.skeleton for concurrent, independent updates, popover filters |
dashboard-feature-usage | Feature adoption tracking, trend analysis, conditional "Raw data" expander |
dashboard-seattle-weather | Weather data visualization |
dashboard-stock-peers | Stock peer comparison |
Each template uses synthetic data that can be replaced with real queries. See assets/templates/apps/README.md for setup instructions.
layouts.md — Columns, containers, tabs, dialogsdata-display.md — Charts, dataframes, column configurationperformance.md — Caching and fragments for heavy dashboards