st.button, st.link_button, st.download_button y st.page_link

Básico
Streamlit
Streamlit
Actualizado: 26/04/2026

st.button: botón de acción

st.button es el widget de interacción más básico de Streamlit. Devuelve True solo en la ejecución del script en la que el usuario hizo clic y False en todas las demás re-ejecuciones. Esta naturaleza efímera es consecuencia directa del modelo de ejecución top-down de Streamlit, donde cada interacción provoca una nueva ejecución completa del script:

import streamlit as st

# Botón básico
if st.button("Generar informe"):
    st.success("Informe generado correctamente.")

# Botón primario (resaltado con el color principal del tema)
if st.button("Entrenar modelo", type="primary"):
    with st.spinner("Entrenando..."):
        # ... lógica de entrenamiento
        st.success("Modelo entrenado con AUC = 0.94")

# Botón desactivado condicionalmente
datos_cargados = "df" in st.session_state
if st.button("Analizar", disabled=not datos_cargados):
    st.write("Analizando datos...")

# Botón con icono emoji
if st.button("Exportar", icon="📥"):
    st.info("Preparando exportación...")

# Botón que ocupa todo el ancho
if st.button("Limpiar todos los filtros", use_container_width=True):
    st.rerun()

Importante: st.button no "guarda" su estado. Si el usuario pulsa el botón, se procesa la acción, pero en el siguiente rerun (por ejemplo, al interactuar con otro widget), st.button vuelve a devolver False. Para acciones que deban persistir, usa st.session_state.

flowchart TD
    A[Tipos de boton Streamlit] --> B[st.button acción]
    A --> C[st.download_button bytes]
    A --> D[st.link_button URL externa]
    A --> E[st.page_link navegación app]
    B --> F{"Click usuario?"}
    F -->|Si| G[Devuelve True esa rerun]
    F -->|No| H[Devuelve False]
    G --> I[Bloque if ejecuta acción]
    I --> J[Necesita session_state para persistir]
    C --> K[Usuario descarga archivo en bytes]
    D --> L[Abre URL en nueva pestana]
    E --> M[Navega entre páginas multipagina]

st.button con session_state para persistencia

import streamlit as st

if "analisis_ejecutado" not in st.session_state:
    st.session_state.analisis_ejecutado = False

if st.button("Ejecutar análisis completo"):
    st.session_state.analisis_ejecutado = True

if st.session_state.analisis_ejecutado:
    st.success("El análisis está disponible.")
    st.line_chart([1, 3, 5, 4, 7, 8])

st.download_button: ofrecer archivos para descarga

st.download_button permite al usuario descargar un archivo generado dinámicamente en Python. El archivo se prepara en memoria (como bytes o cadena de texto) y se envía al navegador del usuario cuando hace clic en el botón, sin necesidad de guardarlo previamente en el servidor:

import streamlit as st
import pandas as pd
import io

df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]})

# Descargar CSV
csv = df.to_csv(index=False).encode("utf-8")
st.download_button(
    label="Descargar CSV",
    data=csv,
    file_name="datos_exportados.csv",
    mime="text/csv",
    icon="📄"
)

# Descargar Excel (requiere openpyxl)
buffer = io.BytesIO()
df.to_excel(buffer, index=False, engine="openpyxl")
st.download_button(
    label="Descargar Excel",
    data=buffer.getvalue(),
    file_name="datos_exportados.xlsx",
    mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)

# Descargar archivo de texto
reporte = "Informe de análisis\n\nFecha: 2026-03-30\nRegistros: 1.250\nPrecisión: 94,3%"
st.download_button(
    label="Descargar informe TXT",
    data=reporte.encode("utf-8"),
    file_name="informe.txt",
    mime="text/plain"
)

st.download_button funciona como cualquier otro widget, no como un formulario. El archivo se genera en Python y se envía al navegador para su descarga inmediata.

st.link_button: botón enlace a URL externa

import streamlit as st

st.link_button(
    label="Ver documentación oficial",
    url="https://docs.streamlit.io",
    type="secondary",
    icon="📚"
)

st.link_button(
    label="Reportar un error",
    url="https://github.com/streamlit/streamlit/issues",
    use_container_width=True
)

st.page_link: navegación entre páginas de la app

import streamlit as st

# Navegación interna entre páginas de la app multipágina
st.page_link("pages/analisis.py", label="Ir al análisis completo", icon="📊")
st.page_link("pages/configuracion.py", label="Configurar parámetros", icon="⚙️")

# También funciona con URLs externas como st.link_button
st.page_link("https://share.streamlit.io", label="Ver apps de ejemplo")

Botones en formularios

Dentro de un st.form, el botón de envío se crea con st.form_submit_button:

with st.form("mi_formulario"):
    nombre = st.text_input("Nombre")
    email = st.text_input("Email")
    enviado = st.form_submit_button(
        "Registrar",
        type="primary",
        use_container_width=True
    )
    if enviado:
        st.success(f"Registrado: {nombre} ({email})")

Ejemplo integrado: pipeline de ML con botones

import streamlit as st
import pandas as pd
import numpy as np

st.title("Pipeline de Machine Learning")

# Botones de fases del pipeline
col1, col2, col3 = st.columns(3)

with col1:
    if st.button("1. Cargar datos", use_container_width=True, icon="📂"):
        st.session_state.df = pd.DataFrame(np.random.randn(100, 3), columns=["A", "B", "C"])
        st.success("100 muestras cargadas.")

with col2:
    datos_ok = "df" in st.session_state
    if st.button("2. Entrenar modelo", disabled=not datos_ok, use_container_width=True, icon="🧠"):
        st.session_state.modelo_entrenado = True
        st.success("Modelo entrenado: AUC = 0.92")

with col3:
    modelo_ok = st.session_state.get("modelo_entrenado", False)
    if st.button("3. Exportar resultados", disabled=not modelo_ok, use_container_width=True, icon="💾"):
        datos_export = "Modelo: RandomForest\nAUC: 0.92\nFecha: 2026-03-30"
        st.download_button(
            "Descargar reporte",
            data=datos_export,
            file_name="reporte_modelo.txt",
            mime="text/plain"
        )

Este patrón con botones desactivados según el estado del pipeline es muy habitual en aplicaciones Streamlit de ciencia de datos para guiar al usuario a través de pasos secuenciales.

La diferencia clave entre st.button y st.form_submit_button es que el segundo solo funciona dentro de un bloque st.form y agrupa múltiples widgets para un solo envío. Para acciones independientes o flujos simples, st.button es la opción adecuada.

Contexto: los botones y el modelo de ejecución de Streamlit

Para entender por qué los botones de Streamlit se comportan de forma diferente a los de otros frameworks, conviene recordar el modelo de ejecución de la biblioteca. Cada vez que un usuario interactúa con cualquier widget de la página, Streamlit vuelve a ejecutar el script completo de arriba a abajo. No hay un servidor que "escuche" eventos, ni un callback global que mantenga el estado entre ejecuciones. Este enfoque simplifica enormemente el modelo mental (escribes Python lineal) pero obliga a entender una peculiaridad crítica: los botones solo devuelven True durante la ejecución inmediatamente posterior al clic.

En la práctica, esto significa que si pulsas un botón que carga un DataFrame y acto seguido interactúas con un st.selectbox, el bloque if st.button(...) ya no se ejecutará porque el botón ha vuelto a False. La forma canónica de "persistir" el efecto de un botón es guardar el resultado en st.session_state como se muestra en el ejemplo anterior.

Tabla de parámetros comunes

| Parámetro | Disponible en | Descripción | |-----------|---------------|-------------| | label | todos | Texto visible del botón | | type | button, link_button, download_button | "primary", "secondary" o "tertiary" | | disabled | button, download_button | Desactiva el botón visual y funcionalmente | | icon | todos | Emoji o icono Material Symbols (:material/save:) | | use_container_width | todos | Ajusta el ancho al contenedor padre | | help | todos | Tooltip al pasar el ratón sobre el botón | | key | todos | Identificador único para distinguir botones en st.session_state | | on_click | button, download_button | Callback que se ejecuta antes del rerun | | args, kwargs | button, download_button | Argumentos posicionales y nombrados pasados al callback |

Errores comunes al trabajar con botones

El botón "se desactiva" solo. Si dentro del if st.button(...) haces una acción que modifica otro widget (por ejemplo, cambias un valor en st.session_state) y luego lees ese estado fuera del bloque, verás que el resultado desaparece en el siguiente rerun. La solución es guardar la marca de acción en session_state y condicionar el renderizado a esa clave, no al valor del botón.

Anidar botones. Un error típico es anidar if st.button("A"): dentro de otro if st.button("B"):. Esto no funciona porque ambos botones pueden devolver True solo en ejecuciones distintas: es imposible que los dos sean True al mismo tiempo.

Descargas que no se disparan. Si usas st.download_button dentro de un if st.button(...), la descarga solo aparecerá tras un doble clic porque el botón de descarga se renderiza solo cuando el primero devuelve True. La solución es mostrar st.download_button siempre que los datos estén disponibles, sin envolverlo en otro botón.

Colisiones de key. Si creas varios botones con la misma etiqueta en distintas partes del script, Streamlit lanzará un error de clave duplicada. Pasa un parámetro key explícito para diferenciarlos.

Mejores prácticas

  • Usa type="primary" solo para la acción principal de cada sección, no para todos los botones: el impacto visual se diluye si todo es primario.
  • Para pipelines secuenciales, combina disabled con banderas en session_state para guiar al usuario a través de los pasos correctos.
  • Los iconos mejoran la escaneabilidad: Streamlit acepta emojis Unicode y también la sintaxis ":material/save:" para iconos de Material Symbols.
  • Evita textos largos en botones: lo ideal son 1-3 palabras en imperativo ("Descargar", "Generar informe", "Reiniciar").
  • Para descargas pesadas (CSV de cientos de MB, ZIPs de modelos entrenados), considera cachear la generación del archivo con @st.cache_data para no regenerarlo en cada rerun.
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Streamlit es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Streamlit

Explora más contenido relacionado con Streamlit y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Usar st.button para ejecutar acciones y controlar el flujo con su valor booleano. Configurar st.download_button para ofrecer archivos para descarga al usuario. Navegar a URLs externas con st.link_button sin salir del flujo de la app. Crear enlaces de navegación entre páginas con st.page_link. Aplicar los parámetros type, icon, disabled y use_container_width en botones.