Button inside a dataframe

Hi! Everyone, Is there a way to insert a button (not checkbox) inside a dataframe. So that for each row we have a separate button and when clicked we can perform some action.

1 Like

Hello Vedant :wave:,

As far as I know, you canā€™t directly add a button inside a (native) dataframe ā€“ There may be a hack but Iā€™m not aware of it! :smile:

Some questions:

  • Why wouldnā€™t the checkbox be a viable option?
  • Have you also tried other options such as streamlit-aggrid?

Thanks,
Charly

Iā€™m facing the same issue, I have a table where one column needs to redirect to a ā€˜detailsā€™ page with that rowā€™s info. I want to use st.page_link to go to the details page since it preserves st.session_state and is generally easy to use, or add a st.button with a st.switch_page callback. A link column doesnā€™t solve my problem because I would need to point to ā€œhttp://localhost:port/ā€ during development and change the link for a deploy, and to my knowledge there is no way to get the current url in streamlit (that doesnā€™t use JS or an equally hacky solution). Honestly, a generic callback option when clicking a table cell would be the best and easiest (I think) solution. Iā€™ll try to use streamlit-aggrid but it would be better to have a native option for streamlit.

You can use a checkbox instead of a button. With 1.35, it has a feature to select a single row, column, etc.

Here is a sample demo.

image


After pressing the page link.

image


streamlit_app.py

import streamlit as st
import pandas as pd


data = {
    'Country': ['United States', 'Canada', 'Germany', 'France', 'Japan'],
    'Capital': ['Washington, D.C.', 'Ottawa', 'Berlin', 'Paris', 'Tokyo']
}


df = pd.DataFrame(data)
st.title('Countries and Their Capitals')

event = st.dataframe(
    df,
    on_select='rerun',
    selection_mode='single-row'
)

if len(event.selection['rows']):
    selected_row = event.selection['rows'][0]
    country = df.iloc[selected_row]['Country']
    capital = df.iloc[selected_row]['Capital']

    st.session_state['country_data'] = {'country': country, 'capital': capital}
    st.page_link('pages/detail.py', label=f'Goto {country} Page', icon='šŸ—ŗļø')

pages/detail.py

import streamlit as st


st.set_page_config(layout='centered')


st.subheader('Detail Page')
st.write(st.session_state['country_data'])
1 Like

Thanks for the code snippet, @ferdy! :hugs:

I actually used checkboxes (as in a boolean column in an editable table) to solve this issue before the selection feature was available, but I donā€™t think this is the best solution yet.

My main issue with checkboxes is that from a design pov they imply that more than one option can be selected, which is not the desired behavior. A radio button would be better, but then I would need another button to confirm the userā€™s selection, which is an unneeded button press. The best solution, in my opinion, still is a button row.

Iā€™ll look into the select row and column feature as soon as I have some time, maybe thereā€™s a way to achieve this behavior without using checkboxes.

1 Like

That belief is incorrect. Have a look on this discussion at ux stack.

One nice feature of this table is the ability to clear the selection.

The link you presented reinforces what I said. Checkboxes imply zero or more selections, but in my use case only one option should be selected, which triggers a redirect to another page. Radio buttons would be more appropriate since options are mutually exclusive, but it still feels strange clicking a radio button to go to another page.

You are incorrect again. You said

Would like to see a solution as wellā€¦ Aggrid is cool, but I want to use actual streamlit buttons, not HTML rendered ones

Same here - Iā€™m building an overview table, and each row should redirect to the (dynamically generated) details page of this rowā€™s entry.

My work-around is:

  • Configure the streamlit dataframe with selection_mode="single-row"
  • Define a callback that is executed every time the row selection changes
def single_row_selection_callback():
    # note: it is not possible to have st.switch_page in the callback
    st.session_state["mp_single_row_selection_callback"] = True


event = st.dataframe(
    data,
    on_select=single_row_selection_callback,
    selection_mode="single-row",
)

modules = event.selection.rows  

if st.session_state.get("mp_single_row_selection_callback", False) is True:
    st.session_state["mp_single_row_selection_callback"] = False
    st.session_state["mp_selected_uid"] = int(data.loc[modules[0]]["uid"]) if len(modules) > 0 else None
    if st.session_state["mp_selected_uid"] is not None:
        st.cache_data.clear()  # clear the cache and force the module details page to refresh
        st.switch_page("pages/5_module_details.py")

The very first column can be used to select an entry (a ā€œmoduleā€ in my case):


selecting one of the rows via the first column will cause the script to switch to a new page with details about the selected entry.

For now this is good enough, but it would be much nicer to be able to click on a cell, e.g. UID or Name and have the script switch to the corresponding details page.

Currently, thereā€™s an open issue requesting this feature. It is useful for when you need to execute functions relevant to a row.

Under the hood Streamlit uses the Glide data grid for the dataframes, it seems it supports buttons as well, so integration to Streamlit should be possible.

Iā€™d be willing to contribute, but adding this functionality seems quite involved as, looking at the other column types, many files need to be modified/added.

Iā€™ve been looking at NiceGUI, which seems to have more features and freedom compared to Streamlit, to transfer my app so I could get buttons-in-rows feature. However, with NiceGUI itā€™s literally hacking a custom language blob into Python script to make it happenā€¦ it looks the part, but unmaintainable and code is ugly.

For now the best solution for me, using Streamlit, remains using columns with st.button().