Applying custom CSS to manually created containers

Summary

I am trying to add custom outline to containers used in my app. I am familiar with the technique of applying custom CSS styles through markdown and have no issue with that. However, I am struggling to only apply styles to containers created with st.container() without affecting all the other containers that Streamlit generates on its own (e.g. when using tabs). Sadly, manually created containers have no “data-testid” attribute, unlike metrics and some other widgets. That would’ve helped resolve the issue.

Basically, my question narrows down to this:
How can I add a CSS outline to the container I am creating manually without affecting other widgets?
Let this be an example application:

st.write('text outside the container')
with st.container():
       st.write('text inside the container')

This be a CSS style code:

[data-testid="some_container_tag"] {
    outline: 2px solid red;
    border-radius: 2px;
} 

And this be an expected result:
image

2 Likes

Hi @Chay,

This is pretty convoluted, but here’s one method that seems to work:

import streamlit as st

st.write("text outside the container")
with st.container():
    st.write("text inside the container")

st.write("More text outside the container")


st.markdown(
    """
<style>
    div[data-testid="stVerticalBlock"] div[style*="flex-direction: column;"] div[data-testid="stVerticalBlock"] {
        border: 1px solid red;
    }
</style>
""",
    unsafe_allow_html=True,
)
2 Likes

Your idea is magnificent! Although, I would make some minor changes to the structure of the css selector so that it does not affect st.columns() grid :wink:

Turns out that the st.columns() method generates a similar hierarchy of divs. Luckily, there is a difference! Unlike those that compose a container, these divs are not direct children. So, a specific descendant-child order would make our thingy more precise :upside_down_face: This allows us to play with containers without affecting other widgets (as far as I can tell).

Little example here:

    with st.container():
        st.text('interesting content')
        st.text('in a potentially ')
        st.text('very stylish container')

    col1, col2, col3 = st.columns(3)
    col1.write('cool column box 1')
    col2.write('cool column box 2')
    col3.write('cool column box 3')
/* Style columns */
[data-testid="column"] {
    box-shadow: rgb(0 0 0 / 20%) 0px 2px 1px -1px, rgb(0 0 0 / 14%) 0px 1px 1px 0px, rgb(0 0 0 / 12%) 0px 1px 3px 0px;
    border-radius: 15px;
    padding: 5% 5% 5% 10%;
} 

/* Style containers */
[data-testid="stVerticalBlock"] > [style*="flex-direction: column;"] > [data-testid="stVerticalBlock"] {
    border: 20px groove red;
}

6 Likes

That’s excellent!

to who it may concern:
if you want to style a specific element you can create a css element with an id and then style it using the css “has” property. It works directly in edge and chrome, in firefox (v 109) you have to enable the css has selector in the config first.

    st.write('''<style>
    [data-testid="stHorizontalBlock"]:has(div.PortMarker) [data-testid="stMarkdownContainer"] p { 
        margin: 0px 0px 0.2rem; 
        color: #ff0000;
    }        
    </style>''', unsafe_allow_html=True)


    with st.container():
        INcol1, INcol2 = st.columns(2) 
        with INcol1:
                st.write('Test 1')
                st.write("""<div class='PortMarker'/>""", unsafe_allow_html=True)
        with INcol2:
                st.write('Test 2')
2 Likes

Hi, I have come up with this script. It may help to differ containers that are placed inside each other.

plh = st.container()
script = """<div id = 'chat_outer'></div>"""
st.markdown(script, unsafe_allow_html=True)

with plh:
    script = """<div id = 'chat_inner'></div>"""
    st.markdown(script, unsafe_allow_html=True)
    st.text("Random inner text")

st.text("Random outer text")

## applying style
chat_plh_style = """<style>
div[data-testid='stVerticalBlock']:has(div#chat_inner):not(:has(div#chat_outer)) {background-color: #E4F2EC};
</style>
"""

st.markdown(chat_plh_style, unsafe_allow_html=True)    

This is application of this method from my pet project. Right chat allows to add messages in a way that they will be shown in reversed order:

2 Likes

Ah this is great. Here’s a helper function I created based on it

def create_container_with_color(id, color="#E4F2EC"):
    # todo: instead of color you can send in any css
    plh = st.container()
    html_code = """<div id = 'my_div_outer'></div>"""
    st.markdown(html_code, unsafe_allow_html=True)

   
    with plh:
        inner_html_code = """<div id = 'my_div_inner_%s'></div>""" % id
        plh.markdown(inner_html_code, unsafe_allow_html=True)

    ## applying style
    chat_plh_style = """
        <style>
            div[data-testid='stVerticalBlock']:has(div#my_div_inner_%s):not(:has(div#my_div_outer)) {
                background-color: %s;
                border-radius: 20px;
                padding: 10px;
            };
        </style>
        """
    chat_plh_style = chat_plh_style % (id, color)

    st.markdown(chat_plh_style, unsafe_allow_html=True)
    return plh

You can actually make that quite convenient using the new :has CSS pseudo-class using a simple <span> as a CSS tag:

st.write('text outside the container')
with st.container(border=True):
  st.write('text inside container with red border')
  st.write('<span class="red-frame"/>', unsafe_allow_html=True)
with st.container(border=True):
  st.write('text inside container')

st.write("""
  <style>
    div[data-testid="stVerticalBlockBorderWrapper"]:has(
      >div>div>div[data-testid="element-container"] 
      .red-frame
    ) {
      outline: 2px solid red;
      border-radius: 2px; 
    }
  </style>
  """, unsafe_allow_html=True)

Of course, using a separate st.write() for the tag takes up an empty line of space, so in practice, it’s probably better to place the tag inside the payload.

grafik

1 Like