I want to create consecutive expanders, where only one is open at the time. A button click on ‘next’ inside the expander should open the next expander. I want to avoid clicking twice for collapsing the expander. Is there a way to define ‘open/collapsed’ before defining the expander itself?
import streamlit as st
samples = 3
#intialise session states
for key in [f'next{i}' for i in range(0, samples)]:
if key not in st.session_state:
st.session_state[key] = False
for key in [f'is_expanded{i}' for i in range(0, samples)]:
if key not in st.session_state:
st.session_state[key] = False
for i in range(samples):
#define layout for
expander_container = st.container()
with expander_container:
in_expander = st.container()
#define next button
if st.session_state[f'next{i}']:
st.session_state[f'is_expanded{i}'] = False
st.session_state[f'is_expanded{i+1}'] = True
#content of expander
in_expander.write('Inside the container')
st.session_state[f'next{i}'] = in_expander.button('Next', key = f'next_button{i}')
#define expander
expander_container.expander(f'Sætning {i+1}', expanded=st.session_state[f'is_expanded{i}'])
Actual behavior:
This does not place the content of the expander inside the expander.
I made an example using script injection to accomplish this. There is occassionally a little lag in response since the javascript is loaded dynamically on demand. I’m sure this could be improved upon, but at least this illustrates a viable mechanism.
You could have a button in the expander content that says Next that just sets this expander’s expanded to False and sets the next to True. You’d want to do that in a callback, which means those expanded variables should probably be in session state. When you do that, the server will rerun the whole app on each Next, though. Which is worth keeping in mind.
Let me know how it goes and if you have any more questions
In my enthusiasm, I totally forgot about the expanded parameter. I would definitely use that instead. (It’s tabs that don’t have a Python attribute to mess with and requires the JavaScript.)
I’ll add another note here: I implemented it using the expanded keyword, but it doesn’t mix-and-match well with users manually expanding and contracting the elements. I tried passing (alternatingly) 0 and 1 instead of False and True to try and force Streamlit to recognize it as a new command and re-render but it seems the widget is caching the boolean value rather than the actual value passed to expanded, which means Streamlit doesn’t know to override its state it “got from the user.” This means I’d have to rely on something like st.empty to forcefully destroy and recreate the expanders…
(e.g. If expanded is False and I manually expand it, then if I try to programatically set it to False, nothing happens since Streamlit thinks it’s the same and is happily maintaining the state for the user. I had hoped by passing it 0, I could trick Streamlit into processing it as “new information” but it doesn’t seem to do this so I can’t get around it like that.)
Thanks @Shawn_Pereira . For me, the code is susceptible the issue I mentioned: it won’t mix and match well if a user manually expands/collapses things in addition to using the next buttons.
Suppose session state thinks an expander is closed, but a user clicks on it to expand it. This is done in browser and does not report back to session state. If you attempt to close it with session state, nothing will happen since you won’t actually be changing the parameters of the element.
With your code:
I see three ways around this:
with javascript
with manual destruction and recreation of the expander elements so they start as if new with each forced state
using experimental rerun to “cycle” the expander through first the opposite of what you want, then what you want so it registers a definite “change of state”
If you just want to ensure you are opening the next one, you can add that cycling behavior (note this will not force the state of all the expanders if the user has pulled them out of sync with what session state thinks, though it’d be an easy addition if you really did want to force the state of all of them).
import streamlit as st
if 'cycle' not in st.session_state:
st.session_state.cycle = -1
if "etgl" not in st.session_state:
st.session_state.etgl = [True, False, False]
with st.expander('Expander1', st.session_state.etgl[0]):
st.write("In expander 1")
if st.button("Next", key="b1"):
st.session_state.etgl = [False, False, False]
st.session_state.cycle = 1
st.experimental_rerun()
with st.expander('Expander2', st.session_state.etgl[1]):
st.write("In expander 2")
if st.button("Next", key="b2"):
st.session_state.etgl = [False, False, False]
st.session_state.cycle = 2
st.experimental_rerun()
with st.expander('Expander3', st.session_state.etgl[2]):
st.write("In expander 3")
if st.button("Next", key="b3"):
st.session_state.etgl = [False, False, False]
st.session_state.cycle = 0
st.experimental_rerun()
if st.session_state.cycle >= 0:
st.session_state.etgl[st.session_state.cycle] = True
st.session_state.cycle = -1
st.experimental_rerun()
Agree with you on the user not manually opening & closing the expanders, but I provided a response based on the discussion subject ‘Closing current expander and opening next by button-press’.
Yes, the solution does not make for perfect code, but works within the limitations of the expander widget. Hopefully, Streamlit will give us more parameters to manipulate when using the expander widget in future.
Not sure the reason for st.session_state.cycle, as the variable is being assigned a value but not being used. The code I provided cycles through the expanders as intended. Maybe, I am missing something…?
Cheers
oh btw, I tried to upload a recorded screencast, but it wouldnt accept the file (webm format). How did you upload yours?
Sorry for any confusion, I was just trying to illustrate what I mentioned in the post where I said I had implemented a solution using the expanded keyword but found it to have this limitation.
The purpose of cycling is to make it robust to the mentioned limitation (though I only made a guarantee that the next one would open since that is the only one that is cycled).
Suppose a user is on expander 1 and clicks next (session state has the 1 is closed and 2 is open now). The user wants to go back so they manually collapse expander 2 and open expander 1. In that situation, that next button in expander 1 won’t work if they try to click next without the cycling in place. The button click set everything to False but the cycling flag lets you get to the end, set the correct one to true and rerun, guaranteeing that the next container will be opened because the backend saw a change of state.
How much a limitation is in fact a limitation is of course a personal preference according to use case. I just meant to explain since I hadn’t posted my other solution to illustrate.
PS I use cloud convert to make gifs from screen records.
Thanks for stopping by! We use cookies to help us understand how you interact with our website.
By clicking “Accept all”, you consent to our use of cookies. For more information, please see our privacy policy.
Cookie settings
Strictly necessary cookies
These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms.
Performance cookies
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us understand how visitors move around the site and which pages are most frequently visited.
Functional cookies
These cookies are used to record your choices and settings, maintain your preferences over time and recognize you when you return to our website. These cookies help us to personalize our content for you and remember your preferences.
Targeting cookies
These cookies may be deployed to our site by our advertising partners to build a profile of your interest and provide you with content that is relevant to you, including showing you relevant ads on other websites.