The idea
A dashboard is mostly communication: a clear title, a few headline numbers, a sentence of context, a table for the details. Streamlit has a small set of elements for each job. Picking the right one makes a page scannable, so people find the number they came for in two seconds.
Headings and text
The owner opens the weekly report on their phone and should know straight away what it covers, when it was refreshed, and what the main points are. Streamlit has a small set of text elements for structuring a page like that:
import streamlit as st
st.title("Toko Kita: weekly report")
st.caption("Completed orders, 1 to 7 July 2024 · refreshed 08:00 WIB")
st.header("Sales")
st.subheader("By channel")
st.text("Plain, fixed-width text.")
st.markdown("Revenue is **up 12%**, driven by the *web* channel.")
| Element | Use for |
|---|---|
st.title | the page name, once, at the top |
st.header / st.subheader | sections and sub-sections |
st.markdown | sentences with bold, italics, lists, links |
st.caption | small grey context: source, period, refresh time |
st.text | fixed-width text, rarely needed |
st.divider() | a horizontal line between sections |
Markdown in one call
st.markdown understands the same Markdown you've seen in this site's lessons:
import streamlit as st
st.markdown("""
### This week
- **Revenue:** Rp 11.8M (completed)
- **Top product:** USB-C Hub
- Full data in the [shared drive](https://example.com)
""")
Triple-quoted strings let you write several lines naturally. st.write("...") also renders Markdown, which is why it worked for text in the first module.
Metrics: the headline numbers
st.metric shows a big number with an optional change underneath:
import streamlit as st
st.metric("Revenue", "Rp 11.8M", delta="12%")
st.metric("Cancelled orders", 10, delta=3, delta_color="inverse")
The delta is green when positive and red when negative. For numbers where up is bad (cancellations, costs, complaints), delta_color="inverse" flips the colours, so +3 cancellations shows red.
A row of metrics at the top is the classic dashboard header; the layout module shows how to put them side by side.
Status messages
Coloured boxes signal how the reader should feel about something:
import streamlit as st
st.success("All 26 completed orders were delivered.")
st.info("Data for June is still being finalised.")
st.warning("3 orders are pending for more than 7 days.")
st.error("The payment provider feed failed at 03:00.")
Use them sparingly. If everything is a warning, nothing is.
Showing data
At some point the owner will want to see the actual numbers behind the headline, like revenue per category. Streamlit has two ways to show a DataFrame, and they suit different situations:
import streamlit as st
import pandas as pd
sales = pd.read_csv("sales.csv")
by_category = (sales[sales["status"] == "completed"]
.groupby("category", as_index=False)["revenue"].sum()
.sort_values("revenue", ascending=False))
st.subheader("Interactive table")
st.dataframe(by_category, hide_index=True)
st.subheader("Static table")
st.table(by_category)
st.dataframeis interactive: users can sort columns, scroll and search. Use it for anything bigger than a handful of rows.hide_index=Truedrops the 0, 1, 2 index column nobody needs.st.tableis static: everything is shown at once. Use it for small summary tables.st.write(df)picksst.dataframefor you.
st.json displays dictionaries and API responses, collapsible and readable:
import streamlit as st
st.json({"order_id": "NG-105", "status": "pending", "items": [{"sku": "KT-POUR", "qty": 1}]})
st.write: the Swiss army knife
st.write looks at what you give it and picks a display: text becomes Markdown, a DataFrame becomes a table, a dict becomes JSON, a Plotly figure becomes a chart. It also takes several arguments: st.write("Total:", 42).
It's great while exploring. In finished apps, prefer the specific function (st.markdown, st.dataframe, st.metric): the code says exactly what appears, and you get their extra options.
Why this matters for clean code
A readable page has the same structure as readable code: a clear name (title), sections (headers), the important things first (metrics), details last (tables). Choosing the specific element over st.write is the UI version of choosing a precise name over x: the next person knows exactly what you meant.
Recap
st.titleonce;st.header/st.subheaderfor sections;st.captionfor context;st.divider()between sections.st.markdownrenders bold, italics, lists and links (triple quotes for several lines).st.metric(label, value, delta=...);delta_color="inverse"when up is bad.st.success,st.info,st.warning,st.errorsignal status; use sparingly.st.dataframe(interactive,hide_index=True) for data;st.tablefor small static tables;st.jsonfor dicts.st.writeis flexible; specific functions are clearer in finished apps.