St.button in one line

hi, I have a question.
I want to make 3 or more buttons in one line in streamlit.
I know we can use st.columns; but the problem with this solution is when I want to show another part of step.
in fact in the following steps I had to use columns again and it doesn’t work column in column.
so I need another idea for putting buttons in one line and for the next step use all of the workspaces of streamlit.
my goal is to put the buttons in one line such as an application and when the user clicks on it, the solution was shown in all of the streamlit workspace not only in the space of that column.
is it possible?
thanks for your help.

1 Like

Sounds like you’re creating a menu of sorts. Try streamlit-option-menu.

P.S. There will be a new feature announced today that you might be able to use. Look out for the announcement.

3 Likes

my goal is to put the buttons in one line such as an application and when the user clicks on it, the solution was shown in all of the streamlit workspace not only in the space of that column.
is it possible?

I think that this can solve your problem:

import streamlit as st

col1, col2, col3 = st.columns(3)

with col1:
    button1 = st.button('Button 1')

with col2:
    button2 = st.button('Button 2')

with col3:
    button3 = st.button('Button 3')

if button1:
    # Do something...

if button2:
    # Do something...

if button3:
    # Do something...

The 3 buttons are in the same line and when you press one of them the solution will be shown in the entire workspace, not only in the space defined by the button’s column.

2 Likes

thanks for your help
it works!!

It worked perfectly.
Thanks @Tiago_Coutinho

Hi All,

If you’re like me, it might bother you that the columns equally space, making layout not visually appealing because the buttons appear far apart.

After a lot of CSS digging, I found a pretty simple approach that doesn’t require any other library installs. After creating the columns as show above, it is possible (at least for the current version of streamlit) to apply the following CSS to make columns only as wide as the button. I included this at the top of my file:

st.markdown("""
            <style>
                div[data-testid="column"] {
                    width: fit-content !important;
                    flex: unset;
                }
                div[data-testid="column"] * {
                    width: fit-content !important;
                }
            </style>
            """, unsafe_allow_html=True)

And then (for my use case):

col1, col2, col3 = st.columns([1,1,1])
    with col1:
        st.button(...)
    with col2:
        st.button(...)
    with col3:
        st.download_button(...)
7 Likes

This CSS works better for me:

button_list = ['one', 'two ', 'three', 'four', '12321312321', 'sfsdafadsfadsfasdfafd', 'adsfad asfads']

st.markdown("""
    <style>
        div[data-testid="column"] {
            flex: 0 3 max-content !important;  
            min-width: min-content;                   
            justify-content: start !important;
            margin: -0.3em !important;        
            padding: 0;          
            # background-color: gray;
        }
        div[data-testid="column"] * {
            width: fit-content !important;
            padding: 0.015em;
            margin: -0.035em;
            border-radius: 16px;
            # background-color: red;     
        }
        div[data-testid="column"] button {
            height: 1.9em;
            align-items: center;
            justify-content: center;
            padding: 0 0.3em 0 0.3em;
        }
        div[data-testid="column"] button * {
            margin: -0.1em;
            padding: 0;
        }
            
    </style>
    """, unsafe_allow_html=True)

plh = plh = st.empty()
for text, col in list(map(list, zip(button_list, plh.columns(len(button_list))))):
    with col:
        col.button(label=text, key=text, on_click=None, args={text})

This solution is great. However, it will affect all columns on the page. If anyone has a solution to this, please let me know :slight_smile:

I managed to make the style specific to a single container by adding a special span with a class that can be used for css selector (using :has()).

Here’s the style I came up with:

<style class="hide-element">
    /* Hides the style container and removes the extra spacing */
    .element-container:has(.hide-element) {
        display: none;
    }
    /*
        The selector for >.element-container is necessary to avoid selecting the whole
        body of the streamlit app, which is also a stVerticalBlock.
    */
    div[data-testid="stVerticalBlock"]:has(> .element-container .horizontal-marker) {
        display: flex;
        flex-direction: row !important;
        flex-wrap: wrap;
        gap: 0.5rem;
        align-items: baseline;
    }
    /* Buttons and their parent container all have a width of 704px, which we need to override */
    div[data-testid="stVerticalBlock"]:has(> .element-container .horizontal-marker) div {
        width: max-content !important;
    }
    /* Just an example of how you would style buttons, if desired */
    /*
    div[data-testid="stVerticalBlock"]:has(> .element-container .horizontal-marker) button {
        border-color: red;
    }
    */
</style>

You can then use it like this:


@contextmanager
def st_horizontal():
    st.markdown(HORIZONTAL_STYLE, unsafe_allow_html=True)
    with st.container():
        st.markdown('<span class="hide-element horizontal-marker"></span>', unsafe_allow_html=True)
        yield

with st_horizontal():
    st.write("Confirm?")
    st.button("✅ Yes")
    st.button("❌ No")

And looks like this:
image

See the full gist if helpful: A container to have elements next to each other in streamlit by manipulating the styles. · GitHub

Let me know if that solves your problem fully!

6 Likes

Thanks a lot! This works perfectly :slight_smile:

How to extend this code for st.color_picker?

Amazing! Everything I have hoped for. Wish I have come to this link two weeks ago.

Very helpful. Thank you.

Thanks for an excellent solution.

I notice if I I use st.toggle() in the with block, the Off state look like this
image
instead of this
image
.
And the On state looks like this
image
instead of this
image
.

Is there an easy fix? (This is not a show stopper.)

Good catch. For this kind of problem and similar, it means that my css is targeting too many things I didn’t expect, so one needs to open the inspector, see which styles apply to the toggle but shouldn’t (here the width: max-content !important;) and make the rule more specific. I’ve updated the gist to take this into account.

@skemaikin This also fixes the issue for the color picker!

@cozyfractal I solved my problem like this:

    /* color picker */
    div[data-testid="stVerticalBlock"]:has(> .element-container .horizontal-marker) > div > div > div > div[data-testid="stColorPickerBlock"]:before {
        content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAXElEQVRYR+3SsQ0AIAwEMWD/nYOYwQ3F0Z8Umd8zs35+5+fj3m0dqD+UYIIqoH0bTFAFtG+DCaqA9m0wQRXQvg0mqALat8EEVUD7NpigCmjfBhNUAe3bYIIqoP0FMpcDTW7gDVoAAAAASUVORK5CYII=);
        vertical-align: middle;
    }
    div[data-testid="stVerticalBlock"]:has(> .element-container .horizontal-marker) > div > div[data-testid="stColorPicker"] {
        flex-direction: revert; 
        align-items: center; 
    }
    div[data-testid="stVerticalBlock"]:has(> .element-container .horizontal-marker) > div > div[data-testid="stColorPicker"] > label {
        margin-bottom: 0px;
        padding-right: 8px;
    }     

Excellent. It works!

The IT policy at work won’t let me access your gist, but I was able to get in from my home network. Just tried out the codes. Works as advertised. Thanks. Really appreciate your help.

It’s a pleasure to help! Thanks for asking such clear way which made it easy for me to go back into this problem and know the scale of debugging I was getting myself into. It’s easy to ignore a notification, but you made it almost as easy to give an answer!

An alternate solution is to use one of the new widgets: