Skip to content

title: "Beyond the Basics: Complete Guide for Streamlit Buttons" description: "Dive into the world of Streamlit's st.button. Learn how to use it, style it, and leverage its event-driven capabilities with practical examples. Perfect for beginners and seasoned developers alike." date: 2024-05-20 language: en author: olaf

Beyond the Basics: Complete Guide for Streamlit Buttons

Streamlit makes it simple to turn Python scripts into interactive apps, and buttons are often the first interaction users expect. The st.button widget may look minimal, but it unlocks workflows ranging from quick toggles to complex multi-step actions. This refreshed guide summarizes the modern API, clarifies common misconceptions, and offers proven patterns for crafting reliable button-driven experiences.

Looking for an out-of-the-box visual analytics component for your Streamlit projects?

PyGWalker (opens in a new tab) embeds a Tableau-like exploration UI directly inside Streamlit apps. Watch how to explore data with PyGWalker (opens in a new tab) to see it in action, or read the integration guide.

Understanding st.button

Quick start

At its core, st.button renders a clickable element and returns True during the script run immediately following a click. The returned boolean is usually enough for simple flows:

import streamlit as st
 
if st.button("Say hello"):
    st.write("Hello, Streamlit!")

Streamlit reruns the entire script every time a widget changes. Because of that rerun model, ensure the work you trigger inside the if block is idempotent or guarded by additional state.

Key arguments you should know

st.button accepts several optional arguments that cover the majority of customization needs:

  • key: Unique identifier when you have multiple buttons with the same label.
  • help: Tooltip text shown on hover.
  • on_click: Callback executed when the button is pressed.
  • args / kwargs: Positional and keyword arguments forwarded to the callback.
  • type: Choose between "primary" and "secondary" styling presets to align with Streamlit's theme.
  • disabled: Prevent users from interacting with the button.
  • use_container_width: Stretch the button to the width of its parent container.
def refresh_data(verbose: bool = False, limit: int | None = None) -> None:
    st.session_state["last_result"] = load_data(limit=limit)
    if verbose:
        st.success("Data refreshed")
 
st.button(
    "Refresh data",
    key="refresh",
    help="Pull the latest records from the API",
    type="primary",
    use_container_width=True,
    on_click=refresh_data,
    args=(True,),
    kwargs={"limit": 500},
)

Handling interactions beyond the return value

Using callbacks and session state

Callbacks run after Streamlit collects widget changes, making them ideal for encapsulating logic and updating st.session_state:

import datetime as dt
import streamlit as st
 
if "last_clicked" not in st.session_state:
    st.session_state.last_clicked = None
 
 
def mark_timestamp() -> None:
    st.session_state.last_clicked = dt.datetime.now(dt.timezone.utc)
 
st.button("Record timestamp", on_click=mark_timestamp, type="primary")
 
st.write("Last click:", st.session_state.last_clicked or "Not yet")

Callbacks keep your top-level script declarative and avoid deep if nesting while preserving state between reruns.

Building toggle behaviour

Buttons do not stay pressed, but you can combine them with session state for toggle-like interactions:

import streamlit as st
 
st.session_state.setdefault("show_advanced", False)
 
if st.button("Toggle advanced options"):
    st.session_state.show_advanced = not st.session_state.show_advanced
 
if st.session_state.show_advanced:
    st.info("Advanced configuration goes here.")

For persistent controls (e.g., on/off switches) consider widgets such as st.checkbox or st.toggle for clearer semantics.

Layout techniques for buttons

Streamlit arranges elements in the order they appear, but you still have layout control through containers:

col1, col2 = st.columns(2)
with col1:
    st.button("Previous", key="prev", type="secondary")
with col2:
    st.button("Next", key="next", type="primary", use_container_width=True)
 
with st.sidebar:
    st.button("Reset filters", key="reset")

Use st.container() or st.expander() to group related actions, and st.columns() to align buttons on the same row without relying on raw HTML.

Styling options and CSS overrides

Streamlit intentionally limits direct styling to preserve consistency, but you still have choices:

  • Prefer the built-in type="primary" preset for emphasis and use_container_width=True for responsive layouts.
  • Use the global theme settings (.streamlit/config.toml) to update default colours across the app.
  • For bespoke styles, inject scoped CSS through st.markdown(..., unsafe_allow_html=True).
import streamlit as st
 
st.markdown(
    """
    <style>
    .custom-button button {
        background: linear-gradient(135deg, #005bbb, #00bcd4);
        color: white;
        border: 0;
        padding: 0.6rem 1.5rem;
        border-radius: 999px;
        transition: transform 0.1s ease-in-out;
    }
    .custom-button button:hover {
        transform: translateY(-1px);
    }
    </style>
    """,
    unsafe_allow_html=True,
)
 
with st.container():
    st.markdown('<div class="custom-button">', unsafe_allow_html=True)
    st.button("Gradient button", key="custom")
    st.markdown("</div>", unsafe_allow_html=True)

Keep CSS overrides focused; large inline blocks can become difficult to maintain and may break when Streamlit updates its internal class names.

Practical patterns

  • Progressive disclosure: pair a button with st.session_state to reveal additional inputs or explanatory text only when needed.
  • Data processing steps: disable buttons (disabled=True) until prerequisites such as file uploads or authentication tokens are available.
  • Download flows: use st.download_button when you specifically need file downloads; reserve st.button for triggering in-app logic.

Conclusion

Buttons anchor many Streamlit user journeys. By combining the boolean return value, callback API, and st.session_state, you can coordinate complex behaviours without leaving the reactive paradigm. Lean on layout primitives for alignment, escalate to CSS only when necessary, and consider alternative widgets when a persistent state indicator is clearer. Thoughtful button design keeps your Streamlit apps fast, intuitive, and dependable.

Frequently Asked Questions

How do you make a button in Streamlit?

Use st.button("Label"). The function returns True on the run triggered by the click, so place your logic inside the corresponding if block or pass a callback with on_click.

How do you position a button in Streamlit?

Wrap widgets inside layout primitives such as st.columns, st.container, st.sidebar, or st.expander. These containers control placement without resorting to raw HTML for alignment.

How do you make a radio button in Streamlit?

Use st.radio(label, options) to present mutually exclusive choices. The selected option is returned immediately and can be stored in st.session_state for downstream logic.

What are the disadvantages of Streamlit?

Streamlit's scripted rerun model simplifies development but can feel limiting when you need granular control over layout, long-running background tasks, or deeply customized styling. That said, the ecosystem evolves quickly—new widgets, theming options, and session state helpers continue to reduce these gaps.