lib/streamlit/.agents/skills/developing-with-streamlit/references/design.md
Small touches that make apps feel polished.
Visual design works hand-in-hand with other references:
selection-widgets.md — Choosing the right widget (segmented control, pills, toggle)data-display.md — Column config, sparklines, bordered metricslayouts.md — Containers, alignment, dashboard cardstheme.md — Colors, fonts, and borders via config.tomlDo not use custom CSS to polish an app unless the user explicitly asks for it. Set colors, fonts, and borders in .streamlit/config.toml instead. Native theming applies consistently across every element and survives Streamlit upgrades; CSS selectors target internal class names that can change.
# BAD: Injected CSS for a brand color
st.html("<style>.stButton button { background: #0969da; }</style>")
# GOOD: Configure theme tokens in .streamlit/config.toml
[theme]
primaryColor = "#0969da"
Read theme.md for the full option set, ready-made theme configs, and the narrow escape hatch for when the user explicitly asks for CSS.
Set browser tab title, icon, and layout. Place this at the top of your script to avoid visual blinking:
st.set_page_config(
page_title="My Dashboard",
page_icon=":material/analytics:",
layout="wide", # Use "wide" for dashboards with lots of data
)
Layout options:
layout="centered" (default) → Best for most apps, content is constrained to a readable widthlayout="wide" → Full-width, good for dashboards and data-heavy appsAdd a logo to the sidebar/header:
st.logo("logo.png")
Use Material Symbols for a cleaner, more professional look. Streamlit uses the Material Symbols icon set (not the older Material Icons).
# GOOD: Material Symbols
st.markdown(":material/settings:")
st.markdown(":material/calendar_today:")
st.markdown(":material/dashboard:")
st.markdown(":material/person:")
# SPARINGLY: Emojis for special occasions
st.markdown("Celebration! 🎉")
Format: :material/icon_name:
Find icons: https://fonts.google.com/icons
Full list of icons available in Streamlit: material_icon_names.py
Popular icons by category:
| Category | Icons |
|---|---|
| Navigation | home, arrow_back, menu, settings, search |
| Actions | send, play_arrow, refresh, download, upload, save, delete, edit |
| Status | check_circle, error, warning, info, pending |
| Data | table_chart, bar_chart, analytics, query_stats, database |
| Content | chat, code, description, article, folder |
| UI | visibility, build, tune, filter_list |
For standalone badges:
st.badge("Active", icon=":material/check:", color="green")
st.badge("Pending", icon=":material/schedule:", color="orange")
st.badge("Deprecated", color="red")
For inline badges in text:
st.markdown("""
:green-badge[Active] :orange-badge[Pending] :red-badge[Deprecated] :blue-badge[New]
""")
Avoid the old verbose syntax:
# OLD (still works but cluttered)
st.markdown(":orange-background[:orange[Pending]]")
Dividers (st.divider() or ---) look heavy. Just remove them—Streamlit's default spacing is usually enough.
# BAD
st.header("Section 1")
st.write("Content")
st.divider() # Too heavy
st.header("Section 2")
# GOOD
st.header("Section 1")
st.write("Content")
st.header("Section 2")
If you genuinely need spacing:
st.space("small") # Small gap
st.space("medium") # Medium gap
st.space("large") # Large gap
st.space(50) # Custom pixels for fine-tuning
Don't systematically replace dividers with st.space()—it can look weird too.
Use sentence casing for titles and labels. Title Case Feels Shouty.
# GOOD
st.title("Upload your data")
st.selectbox("Select a region", options)
st.button("Save changes")
# BAD
st.title("Upload Your Data")
st.selectbox("Select A Region", options)
st.info() is too heavy for simple informational text.
# GOOD: Lighter
st.caption("Data last updated 5 minutes ago")
# BAD: Too heavy
st.info("Data last updated 5 minutes ago")
When to use what:
st.caption → Simple info, metadata, timestampsst.info → Important instructionsst.warning → Caution, potential issuesst.error → Errors that block progressst.success → Confirmation of actionst.toast → Lightweight confirmation that auto-dismissesUse text_alignment for text elements:
st.title("Centered title", text_alignment="center")
st.write("Right aligned", text_alignment="right")
st.caption("Justified text", text_alignment="justify")
Options: "left" (default), "center", "right", "justify"
Note: horizontal_alignment on containers positions elements but also sets their text_alignment. If you need different text alignment within a horizontally-aligned container, override text_alignment on the text element itself.
Material Symbols can make callouts and expanders look nicer:
st.info("Processing complete", icon=":material/check_circle:")
st.warning("Rate limit approaching", icon=":material/warning:")
st.error("Connection failed", icon=":material/error:")
st.success("Saved!", icon=":material/thumb_up:")
with st.expander("Settings", icon=":material/settings:"):
st.write("Configure your preferences")
Other elements like st.button and st.tabs also support icons—worth considering when it adds clarity.