Greyed out widgets

Streamlit Version 1.50.0
Python 3

The app in question is running on an SBC, but I am networked with it and running the app my Mac.

This is a more general “flow” question…

I am new to Streamlit, and the app I am writing keeps running into greyed out elements being displayed.

I have taken time to read the docs (fundamentals section, and concepts section ), plus I have been searching the forum to try and figure out what I am doing wrong.

I am using “stages” to try and show different things on the screen at different times.

In stage 0, I’m displaying only button.
In stage 1, I’m displaying an image, a form (with a form button) plus another separate button outside the form.

I am using session_state to manage the “stage”, and each button updates session state.

The program goes like this:

if st.session_state.stage == 0:
with st.container():
st.button(“Take Picture”, on_click=create_data, key=“take_pic”):

if st.session_state.stage == 1:
    with st.container():
         st.image(st.session_state.current_image) # Display Picture
         with st.form('item_form'):
              …form fields

              st.form_submit_button("Save to Database", on_click=save_to_db)

         st.button("Reset Item", on_click=reset, key="reset_item")

The on_click functions do certain tasks, like taking a picture and making a single LLM call to analyze the picture PLUS they all change the session_state.stage

My issue is that I keep getting greyed out widgets sticking around.

At first it was the form, but using a container fixed that. Now it is the buttons. They just will not go away - after pressed, they stick around greyed out. My first button is weird - the first time I press it, it does not grey out - works like a charm. The second time it is pressed, it sticks around greyed out below the other widgets.

Maybe I am missing something on how to correctly develop an app with Streamlit? I understand that the entire app gets rerun every time a widget is interacted with, and when I plan out the stages, I “feel” like I am thinking through it correctly, but any general thoughts or tips would be much appreciated.

Here is the main file in the repo.

edit: following the advice of @Raghu2 I added this to the bottom of my app:

for i in range(0, 2):
st.markdown(" ")

And now the stale elements are no more…

Feels hacky as get out, I wish I understood what was going on here. I have no idea how I would explain this behavior to a colleague.

Welcome to the Streamlit community, and thanks for your thoughtful question! :tada: You’re absolutely right that Streamlit reruns the entire script on every interaction, and “stale” (greyed out) widgets can appear when the widget tree changes between reruns—especially when using conditional rendering for stages. This happens because Streamlit matches widgets by their order and identity; if the number or order of widgets changes between reruns, old widgets may linger as greyed-out “stale” elements until the script finishes and the frontend catches up. Using containers (like st.container() or st.empty()) helps, but sometimes you need to explicitly clear or replace elements to avoid this ghosting effect.

A common, robust solution is to wrap each stage’s UI in a dedicated st.empty() container and call .empty() on it before rendering the next stage. This ensures that only the intended widgets are displayed, and stale ones are cleared immediately. The “hack” you mentioned (adding blank markdowns) works by forcing Streamlit to render enough elements to overwrite the stale ones, but using containers is more explicit and maintainable. For more details and code examples, see Streamlit Docs: Animate and update elements and Button behavior and examples.

Sources:

its just like we are flushing away the garbage data with empty spaces. I am not aware why these garbage data formed.

1 Like

For efficiency and visual stability, Streamlit doesn’t default to removing elements when a script reruns. If the page is mostly the same as it was previously, this is good: an element that exists on one run will still need to be there on the next run, so Streamlit saves times but just keeping it instead of redrawing it from scratch. However, if the elements on your page change dramatically, this isn’t as helpful, even more so if your page is slow to load and you get to see the inbetween state of the old stack of elements getting replaced one by one with the new stack of elements.

For highly dynamic pages and pages with slower processes, the specific layout of you page matters a bit more. You can use containers to group elements so that from the top level, any element that stays between reruns maintains the same nth position from the top of your app.

  • st.button
  • st.chart
  • st.chart
  • st.markdown
  • st.button
  • st.chart
  • st.markdown

Suppose your charts are slow to load and you have on the next run:

  • st.button
  • st.chart
  • st.markdown # replaces second st.chart
  • st.button # replaces st.markdown
  • st.chart # replaces st.button
  • st.markdown # replaces st.chart

If one chart disappears from one run to the next, all the following elements basically replace the previous one. In particular, if the chart that replaces the button is slow, you will see the stale instance from the first run while the new instance loads above it.

To fix this, use containers around the dynamic portions of your app to preserve element positions in the DOM tree.

  • st.button
  • st.container
    • st.chart
    • st.chart
  • st.markdown
  • st.button
  • st.chart
  • st.markdown

and

  • st.button
  • st.container
    • st.chart
  • st.markdown
  • st.button
  • st.chart
  • st.markdown
1 Like

Thanks for this @mathcatsand !