Issues with Background Colour for buttons

I have two buttons in the same row that i want to a different Background colour for each. Please i have tried all the Css selectors known to man Nothing seems to work

here is my code

side1, side2 = st.columns(2)
 but1 =side1.button('Clock In',use_container_width=True)
 but2 =side2.button('Clock Out',use_container_width=True )

Hereā€™s a modification of @Shawn_Pereiraā€™s solution to include background color.

import streamlit as st
import streamlit.components.v1 as components

def ChangeButtonColour(widget_label, font_color, background_color='transparent'):
    htmlstr = f"""
        <script>
            var elements = window.parent.document.querySelectorAll('button');
            for (var i = 0; i < elements.length; ++i) {{ 
                if (elements[i].innerText == '{widget_label}') {{ 
                    elements[i].style.color ='{font_color}';
                    elements[i].style.background = '{background_color}'
                }}
            }}
        </script>
        """
    components.html(f"{htmlstr}", height=0, width=0)

cols = st.columns(4)
cols[0].button('first button', key='b1')
cols[1].button('second button', key='b2')
cols[2].button('third button', key='b3')
cols[3].button('fourth button', key='b4')

ChangeButtonColour('second button', 'red', 'blue') # button txt to find, colour to assign
ChangeButtonColour('fourth button', '#c19af5', '#354b75') # button txt to find, colour to assign
4 Likes

this worked like a charm.
But is a little bit advance ā€¦Lucky for me i was able to work it upā€¦You are the best

Hi Mathcatsand,

Thanks for your code. Do you know how could I change this piece of code so I can change the buttonā€™s color based on the key value? My buttons share the same label name but comes with different key value, so I could not do it using the label names.

Thanks.

The widget key is not accessible on the front end, so you canā€™t do that directly. You would need to use the context of other items around your buttons, or possible nth-of-type selectors to pick out your buttonsā€¦which becomes very dependent on your exact code and situation.

Hereā€™s an example of using nth-of-type to ā€œgrabā€ different areas of the app to apply formatting.
https://mathcatsand-examples.streamlit.app/formatted_container

Feel free to create a new thread with your code if you need help applying the concept.

Mathcatsand! You are amazing. This is exactly what I was looking for. Thank you so much

One more favor if you dont mind. would you please tell me what to add to this function if I want to change the hovering color as well?

Hereā€™s a quick answer based on the same mechanism of using the label to color the buttons, It needs a little extra tweak if you want the button font color to remain the hover color after itā€™s clicked instead of just having the box shadow linger. Itā€™s set for dark mode, but there are lines at the top for you to switch out if you are in light mode or have a custom theme.

If you use the other mechanism I mentioned with nth-of-type selectors, you can get away with simpler CSS instead of JavaScript, but thatā€™s just trading one kind of mess for another.

import streamlit as st
import streamlit.components.v1 as components

#if you have specified a theme, you can get the border color with:
#border = st.get_option('theme.secondaryBackgroundColor')
#border = 'rgb(49,51,63,.2)' #light mode
border = 'rgb(250,250,250,.2)' #dark mode

def ChangeButtonColour(widget_label, font_color, hover_color, background_color='transparent'):
    htmlstr = f"""
        <script>
            var elements = window.parent.document.querySelectorAll('button');
            for (var i = 0; i < elements.length; ++i) {{ 
                if (elements[i].innerText == '{widget_label}') {{ 
                    elements[i].style.color ='{font_color}';
                    elements[i].style.background = '{background_color}';
                    elements[i].onmouseover = function() {{ 
                        this.style.color = '{hover_color}';
                        this.style.borderColor = '{hover_color}';
                    }};
                    elements[i].onmouseout = function() {{ 
                        this.style.color = '{font_color}';
                        this.style.borderColor = '{border}';
                    }};
                    elements[i].onfocus = function() {{
                        this.style.boxShadow = '{hover_color} 0px 0px 0px 0.2rem';
                        this.style.borderColor = '{hover_color}';
                        this.style.color = '{hover_color}';
                    }};
                    elements[i].onblur = function() {{
                        this.style.boxShadow = 'none';
                        this.style.borderColor = '{border}';
                        this.style.color = '{font_color}';
                    }};
                }}
            }}
        </script>
        """
    components.html(f"{htmlstr}", height=0, width=0)

cols = st.columns(4)
cols[0].button('first button', key='b1')
cols[1].button('second button', key='b2')
cols[2].button('third button', key='b3')
cols[3].button('fourth button', key='b4')

ChangeButtonColour('second button', 'red', 'blue') # button txt to find, colour to assign
ChangeButtonColour('fourth button', '#c19af5', '#401000', '#354b75') # button txt to find, colour to assign

Hey everyone, as this topic seems to be somewhat active and my question is somewhat fitting, I would like to follow up on the questions above :slight_smile: I am new to CSS styling and html so please excuse if my question very simple.

I want to individually style each button without creating columns (the buttons should be below one another). Meaning I have 3 sidebar buttons, and I want each of them to have a separate background image that is loaded via a web-link (e.g., the first being an image of the sun, second of earth and third of the moon).

So far I only managed to either style all buttons or none. Do you have any tips / helpful code snippets?

The code snippets above should work. The fact that the example put the buttons in columns is completely separate from the mechanism of apply custom colors, so just copy the example above and delete the part where the columns are created and buttons are put into the columns; add buttons to render where you want and you should be good with simple replacements of your labels and colors that you want.

Does anyone have any clarity about why Streamlit is like this? I work on Shiny, which lets you pass CSS styles or classes directly to UI components, and I was really surprised to learn that Streamlit users have to go through this whole window.parent.document.querySelectorAll process to do something that seems pretty fundamental to building a website. There must be a good reason for it, but Iā€™m at a loss to figure out what it is.

Hi! @Gordon_Shotwell1 relevant post: We don't need more parameters in widgets; we need more IDs - #2 by mathcatsand

In my understanding, there is not a lot of demand for functionalities that allow easier modifications of the UI. Therefore, the community has developed a lot of ā€˜hacks.ā€™ Streamlit was created as a tool for data scientists and not necessarily for front-end developers. With finite resources, the Streamlit team focuses on core functionalities.

2 Likes

Thatā€™s really interesting. I wonder if demand is somewhat related to the framework decisions. For example in Shiny weā€™ve always had full support for UI customization, and itā€™s very common even among folks who donā€™t have any CSS background.

Hereā€™s how you would do this in Shiny, and I donā€™t think it would be that hard to implement similar functionality in Streamlit unless thereā€™s some internal reason it wouldnā€™t work.

from shiny import *

app_ui = ui.page_fluid(
    ui.input_action_button(
        "red",
        "Red Button",
        style="background: red",
    ),
    ui.input_action_button("gray", "Gray Button"),
)

app = App(app_ui, None)

Hello, based on the previous pieces of code, I made this code in the form of a class, so that it can be called. You can put whatever you want inside the button, although I havenā€™t tested mixing it with CSS styles. You can improve it if you like, but now you can modify the content without needing to create a column. This is the code.

custom.py file:

import streamlit.components.v1 as components
import streamlit as st
import json 

class Button_Material():
    def __init__(self):
        self.key_dict = []
        st.write('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"/>', unsafe_allow_html=True)
    
    def generate_material(self,content,key):
        button_dict = {'key':key, 'content':content}
        self.key_dict.append(button_dict)
        return {'label':key,'key':key}
    
    def show(self):
        icon_config = f"""
                <script>
                    var elements = window.parent.document.querySelectorAll('button');
                    let attrib = {json.dumps(self.key_dict)};
                    for (var i = 0; i < elements.length; ++i) {{
                        for (var j = 0; j < attrib.length; ++j){{
                            if (elements[i].innerText == attrib[j].key) {{
                                elements[i].firstChild.innerHTML = attrib[j].content;
                            }}
                        }}
                    }}
                </script>
                """
        components.html(f"{icon_config}", height=0, width=0)

test.py file:

import streamlit as st
import custom

bt_custom = custom.Button_Material()

#Test
st.button(**bt_custom.generate_material('<img src="https://images.hdqwalls.com/download/flower-mod-pic-240x320.jpg">','button_test_1'))
st.button(**bt_custom.generate_material('<p><i class="fa-brands fa-youtube"></i>&nbsp;&nbsp;hola</p>','button_test_2'))
st.button('test')

bt_custom.show()

The idea is to first create a button with the same label and key, and then change all the affected buttons at the end.

1 Like