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 anduse_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; reservest.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.