Multiple tabs in streamlit

Hi,

I am new to this - please bear with me if I have gravely missed something.

One feature I would love to see is multiple tabs so the app can host multiple windows. Is it available? I looked through the online documents as well as the community site, but could not find it.

Thanks!
-jc

3 Likes

Not at the moment. What you can do for now is to use radiobuttons like in the image below

There are some feature requests related to this like


It would be awesome if you casted a vote or added some comments in the issues on what you need.

4 Likes

thanks @Marc. I upvoted both requests.

1 Like

But @JayC. There is actually a hack (?) via Bokeh that I just discovered. See Bokeh can provide layouts, tabs, advanced tables and JS Callbacks.

2 Likes

There鈥檚 another potential hack while we鈥檙e waiting for Streamlit-native tabs if you don鈥檛 need the power of Bokeh. You can make use of query args and Bootstrap tabs rendered via st.markdown(). Here鈥檚 a little demo inside the issue requesting tabs in streamlit: Feature request: Tabs 路 Issue #233 路 streamlit/streamlit 路 GitHub

And here鈥檚 what that can look like:

5 Likes

Hi @benlindsay

thanks for share this code , i use it in my app.
I have a question,when i want to use each page in separate python file and then use all of this file in one file as main.py.
But i can鈥檛 do this in this way, can you help me more about this?

1 Like

Yeah, you can handle this with standard module importing logic. Here鈥檚 an example modified from my original example:

import streamlit as st

from pages import home, about, contact

st.markdown(
    '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">',
    unsafe_allow_html=True,
)
query_params = st.experimental_get_query_params()
tabs = ["Home", "About", "Contact"]
if "tab" in query_params:
    active_tab = query_params["tab"][0]
else:
    active_tab = "Home"

if active_tab not in tabs:
    st.experimental_set_query_params(tab="Home")
    active_tab = "Home"

li_items = "".join(
    f"""
    <li class="nav-item">
        <a class="nav-link{' active' if t==active_tab else ''}" href="/?tab={t}">{t}</a>
    </li>
    """
    for t in tabs
)
tabs_html = f"""
    <ul class="nav nav-tabs">
    {li_items}
    </ul>
"""

st.markdown(tabs_html, unsafe_allow_html=True)
st.markdown("<br>", unsafe_allow_html=True)

if active_tab == "Home":
    home.write()
elif active_tab == "About":
    about.write()
elif active_tab == "Contact":
    contact.write()
else:
    st.error("Something has gone terribly wrong.")

For this to work, you鈥檇 need to make a 鈥減ages鈥 folder with a home.py, about.py, and contact.py file, and each of those files should have a 鈥渨rite鈥 function that does that handles logic for that page. Hope that helps!

6 Likes

@benlindsay
Thanks for your solution, it is a new break.
Can we use the css file locally , change the background color when tab is hover or clicked?
It seems it not work with other bootstrap version like newest 5.0.2

You can include a local css file in a hacky way as shown in this post. I haven鈥檛 tried tinkering with CSS other than including bootstrap, so I鈥檓 not sure what the limitations are or what will clash with streamlit鈥檚 built-in CSS, but go ahead and tinker away!

Hi @benlindsay - love this hack to get tabs working. I鈥檝e been using this in my app and it works fine when hosted locally however doesn鈥檛 work when the app is deployed. Encountering 404 page not found. Do you have a workaround for this?

I haven鈥檛 tried streamlit sharing yet so I鈥檓 not sure why that doesn鈥檛 work. I鈥檒l have to try it out soon

1 Like

can you visit the url in deploy environment about https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css

If it鈥檚 still relevant, i鈥檝e made something of that sort, which also works faster then using the normal navigation.

But it uses flask (or you can indeed use it with anything else).

No worries :slight_smile:

okay I will check it out, thx

Just took a look at it and the way streamlit sharing urls work, going to a link with an href of 鈥/鈥 doesn鈥檛 take you to the current page like it does when running streamlit locally. You鈥檒l need to do something like

STREAMLIT_URL = os.getenv(鈥淪TREAMLIT_URL鈥, 鈥/鈥)

and make all your tab hrefs look like f"{STREAMLIT_URL}?foo=bar"

then set STREAMLIT_URL as a secret in your streamlit management page. I鈥檒l try to set up a streamlit sharing tabs demo some time

Hey, I would like to contribute to that discussion about tabs. While a completely understand all your dev reasons to not implement tabs right now, I and other community members, probably think it鈥檚 a great feature to have, mostly to create more organized apps for final users.

I have tried that approach using experimental_get_query_parameters, but my session cache was invalidated on each tab interaction (aka, losing previously checked options).
So I looked for a more simple approach, and voil谩, tabs using a radio component as a base for interactions.
UPDATE: I just realize that approach will 鈥渙verride鈥 any radio component. :disappointed_relieved:
I looked at some CSS solutions for that but, since we don鈥檛 have any reference to that component on HTML code, I couldn鈥檛 find a way to isolate it.
Hey dev almighties, would be great to have 鈥渒ey=鈥榥ame鈥欌 at some point in the HTML code of the component.

def tabs(default_tabs = [], default_active_tab=0):
        if not default_tabs:
            return None
        active_tab = st.radio("", default_tabs, index=default_active_tab, key='tabs')
        child = default_tabs.index(active_tab)+1
        st.markdown("""  
            <style type="text/css">
            div[role=radiogroup] > label > div:first-of-type, .stRadio > label {
               display: none;               
            }
            div[role=radiogroup] {
                flex-direction: unset
            }
            div[role=radiogroup] label {             
                border: 1px solid #999;
                background: #EEE;
                padding: 4px 12px;
                border-radius: 4px 4px 0 0;
                position: relative;
                top: 1px;
                }
            div[role=radiogroup] label:nth-child(""" + str(child) + """) {    
                background: #FFF !important;
                border-bottom: 1px solid transparent;
            }            
            </style>
        """,unsafe_allow_html=True)        
        return active_tab

active_tab = tabs(["Tab 1", "Tab 2", "Tab 3"])
st.write(active_tab)
4 Likes

Great, it is unbelievable you can did this without bootrap.css.

2 Likes

Thank you @flunardelli , I have used your tab-solution to display different contents

def _tabs(tabs_data = {}, default_active_tab=0):
        tab_titles = list(tabs_data.keys())
        if not tab_titles:
            return None
        active_tab = st.radio("", tab_titles, index=default_active_tab)
        child = tab_titles.index(active_tab)+1
        st.markdown("""  
            <style type="text/css">
            div[role=radiogroup] > label > div:first-of-type {
               display: none
            }
            div[role=radiogroup] {
                flex-direction: unset
            }
            div[role=radiogroup] label {             
                border: 1px solid #999;
                background: #EEE;
                padding: 4px 12px;
                border-radius: 4px 4px 0 0;
                position: relative;
                top: 1px;
                }
            div[role=radiogroup] label:nth-child(""" + str(child) + """) {    
                background: #FFF !important;
                border-bottom: 1px solid transparent;
            }            
            </style>
        """,unsafe_allow_html=True)        
        return tabs_data[active_tab]

def _show_video():
    st.title("Russia 鈥 Ukraine conflict / crisis Explained")
    st.video("https://www.youtube.com/watch?v=h2P9AmGcMdM")

def _fake_df():
    N = 50
    rand = pd.DataFrame()
    rand['a'] = np.arange(N)
    rand['b'] = np.random.rand(N)
    rand['c'] = np.random.rand(N)    
    return rand

def do_tabs():
    st.markdown("Tab example found at [Multiple tabs in streamlit](https://discuss.streamlit.io/t/multiple-tabs-in-streamlit/1100/19?u=wgong27514)")
    tab_content = _tabs({
            "Tab html": "<h2> Hello Streamlit, <br/> what a cool tool! </h2>",
            "Tab video": _show_video, 
            "Tab df": _fake_df()
        })
    if callable(tab_content):
        tab_content()
    elif type(tab_content) == str:
        st.markdown(tab_content, unsafe_allow_html=True)
    else:
        st.write(tab_content) 
3 Likes