skills/streamlit/agent-skills/creating-streamlit-themes

creating-streamlit-themes

Installation
SKILL.md

Creating Streamlit themes

Build professional, brand-aligned themes using .streamlit/config.toml. This skill covers design principles and complete configuration for polished, cohesive themes.

Theme file setup

Theme options go in Streamlit's config.toml under the [theme] section:

Theme inheritance

Start from a built-in theme or external file:

[theme]
base = "light"                         # or "dark"
# base = "./my-base-theme.toml"        # Local file
# base = "https://example.com/theme.toml"  # Remote URL

When using base, you only need to override the values you want to change. Theme files referenced via base can only contain a single [theme] section—[theme.light] and [theme.dark] variants are not supported in external theme files.

Color configuration

Theme colors

[theme]
primaryColor = "#0969da"           # Buttons, links, active elements
backgroundColor = "#ffffff"        # Main content background
secondaryBackgroundColor = "#f6f8fa"  # Widget backgrounds, code blocks
textColor = "#1F2328"              # Body text

# Optional refinements
linkColor = "#0969da"              # Markdown links (defaults to primaryColor)
codeTextColor = "#1F2328"          # Inline code text
codeBackgroundColor = "#f6f8fa"    # Code block background
borderColor = "#d0d7de"            # Widget borders

Design principle: Choose a primaryColor dark enough to contrast with white text. Streamlit renders the text of primary buttons white against the primary color.

Color palette

Define semantic colors for status indicators, markdown text coloring, and sparklines:

[theme]
redColor = "#cf222e"
orangeColor = "#bf8700"
yellowColor = "#dbab09"
greenColor = "#1a7f37"
blueColor = "#0969da"
violetColor = "#8250df"
grayColor = "#57606a"

Each color supports background and text variants (auto-derived if not set):

[theme]
greenColor = "#1a7f37"
greenBackgroundColor = "#dafbe1"   # Light tint for badges
greenTextColor = "#116329"         # Darkened for readability

Chart colors

Define colors for Plotly, Altair, and Vega-Lite charts:

[theme]
# Categorical data (bars, pie slices, series)
chartCategoricalColors = ["#0969da", "#1a7f37", "#bf3989", "#8250df", "#cf222e", "#bf8700", "#57606a"]

# Sequential/gradient data (heatmaps) - exactly 10 colors required
chartSequentialColors = ["#f0f6fc", "#c8e1ff", "#79c0ff", "#58a6ff", "#388bfd", "#1f6feb", "#1158c7", "#0d419d", "#0a3069", "#04244a"]

Dataframe styling

[theme]
dataframeBorderColor = "#d0d7de"
dataframeHeaderBackgroundColor = "#f6f8fa"

Ensure textColor is readable against dataframeHeaderBackgroundColor—headers use the main text color.

Typography

Font families

Use built-in fonts, load from Google Fonts, or define custom fonts from font files (see below):

[theme]
# Built-in options
font = "sans-serif"  # or "serif" or "monospace"

# Google Fonts
font = "Inter:https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"

# Font with spaces in name
font = "'IBM Plex Sans':https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&display=swap"

Self-hosting custom fonts

Use [[theme.fontFaces]] tables to load fonts via Streamlit's static file serving. Font files must be placed in a static/ directory and served through the app—they cannot be arbitrary local file paths.

Before adding fonts to config.toml: Verify the font files exist in the static directory.

[[theme.fontFaces]]
family = "CustomFont"
url = "app/static/CustomFont-Regular.woff2"
weight = 400

[[theme.fontFaces]]
family = "CustomFont"
url = "app/static/CustomFont-Bold.woff2"
weight = 700

[theme]
font = "CustomFont"

Attributes: family (name), url (path to OTF/TTF/WOFF/WOFF2), weight (400, "200 800", or "bold"), style ("normal"/"italic"/"oblique"), unicodeRange (e.g., "U+0000-00FF").

Changes to fontFaces require a server restart.

Heading and code fonts

[theme]
headingFont = "Inter:https://fonts.googleapis.com/css2?family=Inter:wght@600;700&display=swap"
codeFont = "'JetBrains Mono':https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap"

Font sizing and weight

[theme]
baseFontSize = 14                  # Root size in pixels (default: 16)
baseFontWeight = 400               # Normal weight
codeFontSize = "0.875rem"          # Relative to base, or use "13px"
codeFontWeight = 400

# Heading hierarchy (h1 through h6), or use a single value for all
headingFontSizes = ["32px", "24px", "20px", "16px", "14px", "12px"]
headingFontWeights = [600, 600, 600, 500, 500, 500]

Link styling

[theme]
linkUnderline = false              # Remove underlines for cleaner look

Border and radius

[theme]
baseRadius = "8px"                 # All components (none/small/medium/large/full/px/rem)
buttonRadius = "8px"               # Buttons specifically (defaults to baseRadius)
showWidgetBorder = true            # Show borders on unfocused widgets
showSidebarBorder = true           # Show divider between sidebar and content

Radius keywords: "none" (0), "small" (4px), "medium" (8px), "large" (12px), "full" (pill shape).

Sidebar customization

Style the sidebar independently:

[theme.sidebar]
backgroundColor = "#f6f8fa"
secondaryBackgroundColor = "#eaeef2"
codeBackgroundColor = "#eaeef2"
textColor = "#1F2328"
borderColor = "#d0d7de"
primaryColor = "#0969da"           # Active elements in sidebar

Light and dark modes

Define separate themes for each mode:

[theme.light]
primaryColor = "#0969da"
backgroundColor = "#ffffff"
secondaryBackgroundColor = "#f6f8fa"
textColor = "#1F2328"

[theme.dark]
primaryColor = "#58a6ff"
backgroundColor = "#0d1117"
secondaryBackgroundColor = "#161b22"
textColor = "#e6edf3"

[theme.light.sidebar]
backgroundColor = "#f6f8fa"

[theme.dark.sidebar]
backgroundColor = "#010409"

Users can switch between modes in the app settings menu only if both [theme.light] and [theme.dark] are defined. A custom theme with just [theme] locks the app to a single mode.

Detecting current theme

Use st.context.theme.base to adapt your app to the active theme. Useful for:

  • Adjusting specific chart colors for better contrast
  • Swapping logos or images (e.g., dark logo on light, light logo on dark)
  • Styling third-party components that don't auto-adapt
  • Applying conditional CSS or custom styling
if st.context.theme.base == "dark":
    # Do something for dark mode

Design principles

Color contrast

Ensure WCAG AA compliance (4.5:1 ratio for text):

  • Light themes: Dark text (#1F2328) on light backgrounds (#ffffff)
  • Dark themes: Light text (#e6edf3) on dark backgrounds (#0d1117)
  • Primary colors must contrast with white button text

Color harmony

Build cohesive palettes using these approaches:

Monochromatic: Single hue with varying lightness (e.g., shadcn's zinc grays)

primaryColor = "#18181B"
textColor = "#09090B"
borderColor = "#E4E4E7"
grayColor = "#71717A"

Brand accent: Neutral base with one brand color (e.g., Stripe's purple)

primaryColor = "#635bff"           # Brand purple
backgroundColor = "#ffffff"
textColor = "#425466"              # Neutral gray

Complementary: Brand primary with supporting accent colors

primaryColor = "#29B5E8"           # Brand blue (Snowflake)
textColor = "#11567F"              # Darker blue for text
greenColor = "#36B37E"             # Success states
redColor = "#DE350B"               # Error states

Typography guidelines

  • Body text: 14-16px, weight 400
  • Headings: Decreasing scale from h1 (28-40px) to h6 (12-14px)
  • Code: Monospace font, slightly smaller than body (0.85-0.875rem)
  • Font pairing: Use the same font for body and headings for consistency, or pair complementary fonts (e.g., serif headings with sans-serif body). Code should always use a distinct monospace font.

Visual hierarchy

Create depth with background layers:

Main content:        #ffffff (lightest)
Secondary elements:  #f6f8fa (slightly darker)
Sidebar:             #f6f8fa or contrasting brand color
Code blocks:         #f6f8fa (matches secondary or distinct)

Example: Snowflake brand theme

Clean, professional theme with brand blue accents:

[theme]
primaryColor = "#29B5E8"
backgroundColor = "#ffffff"
secondaryBackgroundColor = "#f4f9fc"
codeBackgroundColor = "#e8f4f8"
textColor = "#11567F"
linkColor = "#29B5E8"
borderColor = "#d0e8f2"
showWidgetBorder = true
showSidebarBorder = true
baseRadius = "8px"
buttonRadius = "8px"

font = "'Inter':https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
codeFont = "'JetBrains Mono':https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap"
codeFontSize = "13px"
codeTextColor = "#11567F"
baseFontSize = 14
baseFontWeight = 400
headingFontSizes = ["32px", "24px", "20px", "16px", "14px", "12px"]
headingFontWeights = [600, 600, 600, 500, 500, 500]
linkUnderline = false

chartCategoricalColors = ["#29B5E8", "#11567F", "#71C8E5", "#174D6A", "#A5DDF2", "#0E4D6B", "#52B8D9"]

blueColor = "#29B5E8"
greenColor = "#36B37E"
yellowColor = "#FFAB00"
redColor = "#DE350B"
violetColor = "#6554C0"

dataframeBorderColor = "#d0e8f2"
dataframeHeaderBackgroundColor = "#e8f4f8"

[theme.sidebar]
backgroundColor = "#11567F"
secondaryBackgroundColor = "#174D6A"
codeBackgroundColor = "#0E4D6B"
textColor = "#ffffff"
borderColor = "#1E6D94"

Example: VS Code dark theme

Developer-focused dark theme with syntax-inspired colors:

[theme]
base = "dark"
primaryColor = "#0078d4"
backgroundColor = "#1e1e1e"
secondaryBackgroundColor = "#252526"
codeBackgroundColor = "#1e1e1e"
textColor = "#cccccc"
linkColor = "#3794ff"
borderColor = "#3c3c3c"
showWidgetBorder = true
showSidebarBorder = true
baseRadius = "4px"
buttonRadius = "4px"

font = "'Segoe UI', 'Open Sans':https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&display=swap"
codeFont = "'Fira Code':https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap"
codeFontSize = "13px"
codeTextColor = "#d4d4d4"
baseFontSize = 14
baseFontWeight = 400
headingFontSizes = ["28px", "22px", "18px", "16px", "14px", "12px"]
headingFontWeights = [600, 600, 600, 600, 600, 600]
linkUnderline = false

chartCategoricalColors = ["#0078d4", "#4ec9b0", "#dcdcaa", "#ce9178", "#c586c0", "#569cd6", "#6a9955"]

blueColor = "#569cd6"
greenColor = "#6a9955"
yellowColor = "#dcdcaa"
orangeColor = "#ce9178"
violetColor = "#c586c0"

[theme.sidebar]
backgroundColor = "#252526"
secondaryBackgroundColor = "#333333"
codeBackgroundColor = "#1e1e1e"
borderColor = "#3c3c3c"

Common mistakes

Primary color too light

# BAD: White text on yellow is unreadable
primaryColor = "#FFEB3B"

# GOOD: Use a darker shade
primaryColor = "#F59E0B"

Insufficient contrast

# BAD: Light gray text on white
textColor = "#CCCCCC"
backgroundColor = "#FFFFFF"

# GOOD: Dark text on light background
textColor = "#1F2328"
backgroundColor = "#FFFFFF"

Mismatched backgrounds

# BAD: Secondary lighter than primary
backgroundColor = "#f6f8fa"
secondaryBackgroundColor = "#ffffff"

# GOOD: Secondary should be darker/distinct
backgroundColor = "#ffffff"
secondaryBackgroundColor = "#f6f8fa"

Forgetting sidebar contrast

When using a dark sidebar with a light main section, adjust all sidebar colors—not just textColor:

# BAD: Only changed backgroundColor
[theme.sidebar]
backgroundColor = "#11567F"

# GOOD: Adjust all colors for dark sidebar
[theme.sidebar]
backgroundColor = "#11567F"
secondaryBackgroundColor = "#174D6A"
textColor = "#ffffff"
borderColor = "#1E6D94"
...

IMPORTANT: No custom CSS unless explicitly requested

DO NOT use custom CSS or HTML for theming. This includes:

  • st.markdown(..., unsafe_allow_html=True) with <style> or inline styles
  • st.html() with <style> blocks
  • Any HTML/CSS for colors, backgrounds, fonts, or visual styling

Only use CSS if the user explicitly asks for it (e.g., "add custom CSS", "use st.html for styling"). For brand colors, theming, and visual identity—always use config.toml.

Native theming is cleaner, more maintainable, and won't break with Streamlit updates.

If the user explicitly asks for CSS, use key= to create targetable classes:

st.button("Submit", key="submit")
# Generates: .st-key-submit

st.html("""<style>.st-key-submit button { width: 100%; }</style>""")

Never use CSS for theming (colors, backgrounds, fonts) unless explicitly asked. Use config.toml instead.

Development workflow

Most theme options update live after saving config.toml and rerunning. Font-related options (fontFaces) require a server restart.

Test your theme with: buttons (primary contrast), forms (borders, focus), dataframes (headers), code blocks, charts, and sidebar.

Theme templates

Ready-to-use themes with bundled fonts are available in templates/themes/:

Theme Base Primary Color Fonts
snowflake Light #29B5E8 (cyan) Inter, JetBrains Mono
dracula Dark #BD93F9 (purple) Fira Sans, JetBrains Mono
nord Dark #88C0D0 (frost blue) Inter, JetBrains Mono
stripe Light #635BFF (indigo) Inter, Source Code Pro
solarized-light Light #268BD2 (blue) Source Sans 3, Source Code Pro
spotify Dark #1DB954 (green) Inter, Fira Code
github Light #0969DA (blue) Inter, JetBrains Mono
minimal Dark #6366f1 (indigo) Inter, JetBrains Mono

Each theme uses Google Fonts for easy setup. See templates/themes/README.md.

Related skills

References

Weekly Installs
3
GitHub Stars
178
First Seen
1 day ago