Switching pages within the new st.column_config.ButtonColumn

Hello all!

Firstly, I would just like to take the time to thank all the maintainers of Streamlit. It’s such an amazing product to use!

I recently started using the new ButtonColumn column_config that was released in v1.59. This is such an amazing addition.

Referring to this demo: https://button-column.streamlit.app/

I really like this idea of a menu/action that could allow you to switch pages.

    "action": st.column_config.ButtonColumn(
        "Column Header", 
        type="primary", 
        on_click=lambda: st.switch_page(),  *#  👈*
        key="my_key",      *# Required for clicks*
    ),
})

So the handler would essentially inspect the “my_key” to inspect how to switch a page. Maybe populate query parameters?

However, when doing this, I see this warning: “Calling st.rerun() within a callback is a no-op.”

It makes sense, but I’m wondering if it would still be possible to switch pages using this new fancy button technology? Is it even possible at all?

Cheers!

Welcome to the community and thanks for your kind words! :balloon: You’re right—ButtonColumn is a fantastic new feature. However, using st.switch_page (or st.rerun) inside a callback (like on_click for ButtonColumn) currently doesn’t work as expected. You’ll see the “Calling st.rerun() within a callback is a no-op” warning, and the page won’t switch—this is a known limitation due to how Streamlit handles callbacks and reruns in its execution model. The same limitation applies to st.switch_page in custom component callbacks or ButtonColumn on_click handlers, so switching pages this way isn’t supported right now.

The recommended approach is to use st.switch_page outside of callbacks, typically in direct response to a widget’s value (e.g., after a button press, not as a callback). For ButtonColumn, you can use the returned click state in st.session_state to trigger a page switch at the top level of your script, not inside the callback. For more, see the Streamlit ButtonColumn PR discussion and community forum threads.

Sources:

Thanks Mr Bot

Hi @Johan5 ,

The rerun will be required to switch the page, so currently I don’t see any workaround, unless maybe you do something like set the session state in the callback, and then later in the script outside of the callback use the session state value to trigger a switch_page.

We are exploring a feature that would allow reruns within callbacks, however, see the spec I wrote. here: [spec] Event-scoped fragment reruns by sfc-gh-lwilby-1 · Pull Request #15755 · streamlit/streamlit · GitHub . The feature is to support targeted fragment reruns based on events, though.

I could see including switch page potentially as well. Or, since we have a button for page switches as well, maybe the better call is to give the option of using that button in the column as well. Could you describe more of why you want the switch page menu inside of the dataframe? It’s a bit surprising to me and I’d like to understand more about it.

Hi @Laura3 , thank you so much for taking an interest!

I currently have a data structure with several one to many relationships. They perfectly nest in each other.

So you might see it as:

group 1/
├── project 1/
│   ├── sub project 1
│   ├── sub project 2
│   └── sub project n
├── project 2
└── project n
group 2
group n

I want to build an app that allows you to explore this data structure. So the “homepage” would start off with a dataframe of all those groups. Then, using the new fancy ButtonColumn, you can perform actions on these groups. “edit” will open a dialog to edit the current group, “delete” will delete, etc.

The one thing I couldn’t do until recently is to switch pages. I wanted to st.switch_page into, lets say, “group 1” and view all the projects inside that group (amongst other things).

While my callback function could handle all the dialogs, the switch page is a no-op. After further reading, I think I found a workaround. I created a NEW column that has links to the actual page.

How do I build the link? I used st.Page to extract the url_path property. I can then use urlencode to create query parameters. Like this: f"/{path}?{encoded_query}"

I then use the pd.Styler to style the column’s display name.

def create_link_styler(df: pd.DataFrame) -> Styler:
    link_to_name = dict(zip(df["link"], df["name"], strict=False))

    return df.style.format(
        {"link": lambda url: link_to_name.get(url, url)},
    )

Which means that I can represent the names as hyperlinks I can click on to switch pages. One annoying thing is still the fact that clicking on the link does a target = blank, instead of self. While I can hack my way around this by injecting javascript, it would have been nice to have the option in LinkColumn to set the target. I see there are a few PRs for that as well.

I see, you should take a look at this enhancement request as well: Add a tree view/select element · Issue #7947 · streamlit/streamlit · GitHub

I think this idea would fit well with what you want to build? If it does, please upvote and even comment. We use the signal to help prioritize work.