Tab switch old UI render below the original UI in faded form.

I have this weird issue in my Streamlit application where I have a sidebar with multiple tabs. On my home tab I am making a data call to azure during which I show a spinnerwidget and once the data is fetched I render the UI. On other tabs I have some locally stored data which I show the transition between them is smooth and as expected but as soon as I come back to my hometab he UI breaks and I see the previous tab UI just below my hometab UI in faded form.

Already tried making a container and emptying it solution. PS doesn’t seem to work.

Here are the relevant code snippets -

main.py

def main():

    user_data = load_users()



    # Try auto login from cookies

    username_cookie = cookies.get("username")

    password_cookie = cookies.get("password")



    if "authenticated" not in st.session_state:

        st.session_state.authenticated = False

        st.session_state.username = None



        if username_cookie and password_cookie:

            if authenticate(username_cookie, password_cookie, user_data):

                st.session_state.authenticated = True

                st.session_state.username = username_cookie



    if not st.session_state.get("authenticated", False):

        st.markdown("<h1 style='text-align: center;'>Login</h1>", unsafe_allow_html=True)

        col1, col2, col3 = st.columns([1, 2, 1])

        with col2:

            username = st.text_input("Username")

            password = st.text_input("Password", type="password")

            _, btn_col = st.columns([5, 1])

            with btn_col:

                login_btn = st.button("Login")

        st.markdown("<br><br>", unsafe_allow_html=True)



        if login_btn:

            if authenticate(username, password, user_data):

                cookies["username"] = username

                cookies["password"] = password

                st.session_state.authenticated = True

                st.session_state.username = username

                st.success("Login successful. Reloading...")

                st.rerun()

            else:

                st.error("Invalid username or password")

        return



    # Authenticated UI

    with st.sidebar:

        # menu_list = ["Home", "Transactions", "Masters", "Mappings", "Rate Chart", "Billing", "Payments", "Reports","Product Sale", "User Management"]

        menu_list = ["Home", "Transactions", "Masters","Rate Chart","Rate Mapper", "Billing", "Payments", "Reports","Product Sale", "User Management"]



        try:

            username = st.session_state.username

            user_data = load_users()

            access = user_data["users"][username]["access"]

            st.session_state.access = access

            if access == 'L1':

                menu_list = ["Home", "Masters", "Rate Chart","Billing", "User Management"]

        except:

            pass



        st.markdown("""

        <style>

        .logo {

            margin-top: -80px;

            border-radius: 4px;

            width:150px;

            margin-left:20px;

        }

        </style>

        """, unsafe_allow_html=True)



        # st.markdown(

        #     f"""<img class="logo" src="data:image/png;base64,{base64.b64encode(open("pavak_logo.png", "rb").read()).decode()}">""",

        #     unsafe_allow_html=True,

        # )



        selected = option_menu(

            "Milk ERP",

            menu_list,

            # icons=["house", "arrow-repeat", "gear-fill", "map-fill", "table", "receipt", "cash-coin", "bar-chart-line-fill", "cart-check-fill", "people-fill"],

            icons=["house", "arrow-repeat", "gear-fill","table","diagram-3-fill", "receipt",  "cash-coin", "bar-chart-line-fill", "cart-check-fill", "people-fill"],

            menu_icon="app-indicator",

            default_index=0,

            styles={

                "container": {

                    "padding": "5px",

                    "background-color": "#f1f5f9",

                    "border-radius": "10px",

                    "box-shadow": "0px 4px 10px rgba(0, 0, 0, 0.1)",

                },

                "icon": {"color": "#3b82f6", "font-size": "14px", "margin-right": "8px"},

                "nav-link": {

                    "font-size": "12px",

                    "font-weight": "500",

                    "color": "#334155",

                    "text-align": "left",

                    "margin": "6px 0",

                    "--hover-color": "#e2e8f0",

                    "padding": "8px 12px",

                    "border-radius": "6px",

                    "transition": "all 0.3s ease",

                },

                "nav-link-selected": {

                    "background-color": "#2563eb",

                    "color": "#ffffff",

                    "font-weight": "bold",

                    "border-radius": "6px",

                    "padding": "8px 12px",

                    "box-shadow": "0px 2px 6px rgba(37, 99, 235, 0.4)",

                    "transition": "all 0.3s ease",

                },

            },

        )

    

    content = st.container()

    content.empty()



    with content:

        try:

            role = user_data["users"][st.session_state.username]["role"]

            if selected == "Home":

                home()

            elif selected == "Transactions":

                transactions()

            elif selected == "Masters":

                masters()

            elif selected == "Mappings":

                mappings()

            elif selected == "Billing":

                billing()

            elif selected == "Payments":

                payments()

            elif selected == "Reports":

                reports()

            elif selected=="Product Sale":

                sale()

            elif selected == "User Management":

                user_management(role, user_data)

            elif selected == "Rate Chart":

                rate_chart()

            elif selected=="Rate Mapper":

                rate_mapper_main()



            if st.sidebar.button("Logout"):

                st.session_state.authenticated = False

                st.session_state.username = None

                cookies["username"] = ""

                cookies["password"] = ""

                st.rerun()



        except Exception as e:

            st.error(f"⚠️ Error: {str(e)}")

            with st.expander("See details"):

                st.code(traceback.format_exc(), language="python")

home.py

def home():
    # st.set_page_config(page_title="Milk ERP Dashboard", layout="wide")
    main_tab1, main_tab2 = st.tabs(["🏢 MCC Data (00002022)", "👨‍🌾 Farmer Data"])

    with main_tab1:

        loading_placeholder = st.empty()

        with loading_placeholder:
            col1, col2, col3 = st.columns([6, 6, 2])
            with col2:
                st.markdown("<div style='height:35vh'></div>", unsafe_allow_html=True)
                with st.spinner("Loading MCC Data..."):
                    mcc_df, mcc_sync = fetch_data_from_azure(MCC_CODE)

        loading_placeholder.empty()

        render_dashboard_content(mcc_df, mcc_sync, MCC_CODE)


    with main_tab2:
        codes = get_farmer_codes()
        if codes:
            selected_farmer_codes = st.multiselect("Select Center/Farmer Codes", options=codes, default=codes, key="farmer_selector")
            
            if selected_farmer_codes:
                f_df, f_sync = fetch_data_from_azure(selected_farmer_codes)
                vlc_tab, farmer_tab = st.tabs(["🏢 VLC-wise Analysis", "👨‍🌾 Farmer-wise Analysis"])
                with vlc_tab:
                    render_vlc_wise_analysis(f_df, f_sync, ", ".join(selected_farmer_codes))
                with farmer_tab:
                    render_farmer_wise_analysis(f_df, f_sync, ", ".join(selected_farmer_codes))
            else:
                st.warning("Please select at least one center code.")
        else:
            st.warning("No other farmer codes found in the storage.")

Welcome to the Streamlit community and thanks for sharing your detailed code and description! :tada: This “ghosting” or “faded previous tab” issue is a known quirk in Streamlit when switching between tabs, especially after a rerun triggered by widgets like spinners or data loads. The problem is that Streamlit sometimes doesn’t fully clear the previous tab’s content, causing remnants to appear below the new content—even when using containers and .empty().

Root Cause:
This happens because Streamlit’s tab rendering is purely visual and not programmatic; all tab contents are always rendered and sent to the frontend, and conditional rendering or lazy loading isn’t natively supported. When you trigger a rerun (e.g., after a spinner or data fetch), Streamlit can get confused about which tab’s content should be visible, especially if widgets or containers are created conditionally or after the tab definition. This can result in the previous tab’s UI lingering in a faded state below the current tab’s content. Using .empty() on containers doesn’t always resolve this, as the tab’s internal state isn’t reset on rerun. This is a well-documented issue in the community and is not fully addressed by the current Streamlit API (see here, see here).

Workarounds:

  • Consistent Tab Definition: Always define your tabs at the very top of your page, before any conditional logic or widget creation. This helps Streamlit keep track of tab state more reliably.
  • Use st.container(height=…): If your content heights vary, explicitly set the height of containers to prevent layout jumps and ghosting (see solution).
  • Alternative Navigation: For more robust navigation, consider using st.navigation and st.Page for multipage apps, or use a st.radio or st.selectbox as a tab replacement, which allows for programmatic control and avoids this issue (see here).
  • Fragment Decorator: Wrapping tab content in an @st.fragment-decorated function can sometimes help Streamlit manage reruns and tab state more cleanly (see here).

If you’d like a step-by-step breakdown or code refactor to implement these workarounds, let me know! And if anyone in the community has found a bulletproof fix, please jump in and share your insights. :man_superhero::woman_superhero:

Sources:

Yes please provide the step-by-step breakdown or code refactor to implement these workarounds,