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ā€™s another potential hack while weā€™re waiting for Streamlit-native tabs if you donā€™t need the power of Bokeh. You can make use of query args and Bootstrap tabs rendered via st.markdown(). Hereā€™s a little demo inside the issue requesting tabs in streamlit: Feature request: Tabs Ā· Issue #233 Ā· streamlit/streamlit Ā· GitHub

And hereā€™s 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ā€™t 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ā€™s 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ā€™d need to make a ā€œpagesā€ folder with a home.py, about.py, and contact.py file, and each of those files should have a ā€œwriteā€ 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ā€™t tried tinkering with CSS other than including bootstrap, so Iā€™m not sure what the limitations are or what will clash with streamlitā€™s built-in CSS, but go ahead and tinker away!

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

I havenā€™t tried streamlit sharing yet so Iā€™m not sure why that doesnā€™t work. Iā€™ll 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ā€™s still relevant, iā€™ve 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ā€™t take you to the current page like it does when running streamlit locally. Youā€™ll need to do something like

STREAMLIT_URL = os.getenv(ā€œSTREAMLIT_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ā€™ll 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ā€™s 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 ā€œoverrideā€ any radio component. :disappointed_relieved:
I looked at some CSS solutions for that but, since we donā€™t have any reference to that component on HTML code, I couldnā€™t find a way to isolate it.
Hey dev almighties, would be great to have ā€œkey=ā€˜nameā€™ā€ 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