St.tabs Auto-Redirects to First Tab

Today, I upgraded Streamlit from version 1.40.2 to 1.41.0. While running my code, I encountered an unexpected behavior:
I created a st.tabs object with three tabs, each containing several interactive components (st.checkbox, st.toggle, etc.). After starting the application, the page correctly displayed the first tab. However, the first time I interacted with a component in the second or third tab, the application unexpectedly redirected to the first tab. Subsequent interactions within the second or third tabs did not cause this redirection.
I initially suspected interference from other components or code. Even after simplifying the second and third tabs to contain only a single interactive component each, the first interaction with a component in those tabs still caused a redirection to the first tab. Finally, rolling back Streamlit to version 1.40.2 resolved the issue.

1 Like

I cannot reproduce this. Works as expected for me.

1 Like

You can use st.pills instead of st.tab. it help a lot as st.tab is just a design trick. With st.pills. you will be able to control more accurately what is display.

1 Like

Thank you very much for your suggestion! I also sincerely apologize for not expressing my gratitude for your reply in a timely manner! I tried using st.pills and st.tabs , but I noticed that if I perform some widget operations in one option of st.pills (e.g., Option A), and then switch to another option and return to Option A, the widgets in Option A are reinitialized and do not retain their previous state. This issue does not occur with st.tabs .

1 Like

Hello
you will need to save the state of the widgets of option A and option B in the session state in order to retain their state for each rereun :

Let me help you create a solution for replacing Streamlit tabs with pills while maintaining widget state.

import streamlit as st
from typing import List, Optional, Callable
import functools

class PillState:
“”“Manages state for pills to replicate tab behavior”“”
def init(self, key: str):
self.key = key
if key not in st.session_state:
st.session_state[key] = 0

def get_active_pill(self) -> int:
    return st.session_state[self.key]

def set_active_pill(self, index: int):
    st.session_state[self.key] = index

def pills(tabs: List[str], key: Optional[str] = None) → int:
“”"
Create pills that behave like tabs, maintaining widget state

Args:
    tabs: List of tab/pill labels
    key: Optional unique key for the pills group
    
Returns:
    Index of currently selected pill
"""
if key is None:
    key = f"pills_{id(tabs)}"
    
state = PillState(key)

# Create horizontal container for pills
cols = st.columns(len(tabs))

for i, (tab, col) in enumerate(zip(tabs, cols)):
    # Custom styling for active/inactive states
    is_active = state.get_active_pill() == i
    bg_color = "primary" if is_active else "secondary"
    
    # Create clickable pill button
    if col.button(
        tab,
        key=f"{key}_{i}",
        type=bg_color,
        use_container_width=True
    ):
        state.set_active_pill(i)

return state.get_active_pill()

Example usage with widget state persistence

def main():
st.title(“Pills Demo with State Management”)

# Create pills
tabs = ["Tab 1", "Tab 2", "Tab 3"]
active_tab = pills(tabs, key="main_pills")

# Content for each tab with persistent widgets
if active_tab == 0:
    st.write("### Tab 1 Content")
    st.number_input("Enter a number", key="num_input_1")
    st.text_input("Enter text", key="text_input_1")
    
elif active_tab == 1:
    st.write("### Tab 2 Content")
    st.slider("Select value", 0, 100, key="slider_2")
    st.checkbox("Check me", key="checkbox_2")
    
else:
    st.write("### Tab 3 Content")
    st.radio("Choose option", ["A", "B", "C"], key="radio_3")
    st.selectbox("Select item", ["Item 1", "Item 2"], key="select_3")

if name == “main”:
main()

This solution provides a seamless way to replace Streamlit’s st.tabs with pills while maintaining widget state. Here’s how it works:

  1. The PillState class manages the active pill index in Streamlit’s session state
  2. The pills function creates horizontally arranged buttons styled as pills
  3. Each pill uses Streamlit’s button component with conditional styling based on active state
  4. Widget state is preserved by using consistent keys for all components
1 Like

Interacting with input elements outside of form context causes the whole application to re-load from top to bottom. This is an expected behaviour from Streamlit’s core framework.

A quick solution here is to add these elements inside a form. st.form - Streamlit Docs
This will prevent the whole application from re-loading, which is the cause of your issue - navigating to first tab.

1 Like