Navigate Multipage App with Buttons instead of Sidebar

Hi,

I’m new to Streamlit and am building a Multipage App as described here Multipage apps. Rather than using the sidebar to navigate through the pages, I’d like to create a walk-through experience using a “Next Page” button at the bottom of each page. Unfortunately, I haven’t been able to figure out how to do this, other than with a very hacky JS approach that forces a page reload. What is the proper way to do this using the new Streamlit Multipage App functionality?

A minimal example of what I want to do:

multipage_app.py

import streamlit as st

st.header("Main Page")

st.button("Button I want to click to go to next page")

next_page.py (in pages folder)

import streamlit as st

st.header("Next Page")

Thanks a lot for your help!

3 Likes

Hey @cbattle!

We don’t have a built-in way to do this right now from Python. But you can use just a normal link via markdown or HTML. E.g. insert this into multipage_app.py:

st.markdown('<a href="/next_page" target="_self">Next page</a>', unsafe_allow_html=True)

This should add a link that navigates to the page in next_page.py. Note that I put in target="_self" here so that the link opens in the same tab. By default, links in Streamlit apps open in a new tab.

Now, this isn’t a button yet! But you can use some custom CSS to style a link like a button. Have a look through this thread. It talks about download buttons (which is not what you want) but the CSS part should be exactly the same for any kind of link.

I’ll also add your request to our internal feedback tracker for multipage apps, so we can think about a better solution for this in the future!

3 Likes

I have the same doubt.
Wondering if it’s possible use the native streamlit multipages not just the st.markdown:

  • The st.markdown will open a new website em users interface. I do not want that.

Maybe it’s possible use CSS to hide the st.sidebar and use “on_click” button to call a function that change the page clicked?
@andfanilo @randyzwitch

Hey jrieke, thanks for your help! Your solution works as advertised, which I appreciate, but the page reloads which kills the session state I was trying to share between the pages. Is there any way to access the page’s st.sidebar state and then set that directly on a button click?

Ah got it! Good point. Hm then I think there’s currently no way to do this. Or at least no easy hack. Maybe @blackary knows some advanced hacks? :wink:

@cbattle This is a pretty hacky solution, but should work to change the page

def switch_page(page_name: str):
    from streamlit import _RerunData, _RerunException
    from streamlit.source_util import get_pages

    def standardize_name(name: str) -> str:
        return name.lower().replace("_", " ")
    
    page_name = standardize_name(page_name)

    pages = get_pages("streamlit_app.py")  # OR whatever your main page is called

    for page_hash, config in pages.items():
        if standardize_name(config["page_name"]) == page_name:
            raise _RerunException(
                _RerunData(
                    page_script_hash=page_hash,
                    page_name=page_name,
                )
            )

    page_names = [standardize_name(config["page_name"]) for config in pages.values()]

    raise ValueError(f"Could not find page {page_name}. Must be one of {page_names}")```
16 Likes

@cbattle There’s also a Streamlit component called streamlit_book that inherently has Next/Prev buttons for navigating multiple pages.

Here are some links to this:

1 Like

FYI, this solution has been added to a new package called streamlit-extras GitHub - arnaudmiribel/streamlit-extras: Discover, try, install and share Streamlit re-usable bits we call "extras"!

from streamlit_extras.switch_page_button import switch_page

switch_page("New page name")
9 Likes

Ah this is very cool, thanks!

Thanks was searching for this
Great solution

1 Like

Hi.

My new page should take me to another Python script. So when I mention the filename in switch_page() parameter, I am getting an error.

Do I need to make a “Pages” directory first and put all my scripts(which I want to show as Next page) there?

I believe switch_page() does require the script to be a “page” in your app, so the easiest method is yes, put it in the pages folder. There is also an st-pages package that @blackary created that allows you to arrange your pages in other ways than just having things in the pages folder, but I haven’t tried combining the two functions to confirm if they play well together.

where does my other scripts(to be used on next page) needs to be saved ? I have the main page as intro.py, now I have another script “sess.py” in the same directory right now. When I click on the function switch_pages(“sess.py”), I get a Value error. I also tried creating a folder “pages” and saving my “sess.py” file there and then again calling the switch_pages() function , Still the same error.

How do we need to use the switch_pages() function ?

didnt work that way. Still same ValueError

The vanilla way of having multiple pages is to put a pages folder in the same directory as your main app file. If this is done correctly, you will see it show up in the sidebar navigation.

Check out the example code for using switch_pages. Once you place the next page in a pages folder, you call it by the filename without the .py extension.

https://extras.streamlit.app/Switch%20page%20function

Yea that I know, but I am trying to redirect the app to the next page only after some successful validation on the home page.

For ex, I would like to ask the user to enter their year of birth in the home page. If the year of birth is less than 2004, then only take the user to the next page , otherwise pop up a message.

You can add a validation check on your secondary page that sends them back to the main page if the necessary data isn’t present. The switch_page function ultimately needs a page (as recognized by Streamlit) to switch to; it needs a page name, not a path.

Can u please send an example on how to to use switch_page( ) ?

I have two python scripts in the same path. And I am passing the script name inside the switch_page ( ) parameter. It is still not working.

The switch_page() function is being called inside “intro.py” script and the page which is being called as a parameter to switch_page ( ) is “trial.py” in the following way

if st.button(“Click me”):
switch_page(“trial.py”)

I get the default ValueError which is raised by switch page.

Put trial.py into a folder named pages. You should have:

Working Directory
|___pages
|   |___trial.py
|___intro.py

Within intro.py you call the function with:

switch_page('trial')

For example:

import streamlit as st
from streamlit_extras.switch_page_button import switch_page

if st.button('Trial'):
    switch_page('trial')

Do not put a path (or filename with extension) into the switch_page function; you need the “name of the page” as recognized by Streamlit.

Now if you want to prevent unintentional navigation, you could:

  1. Hide the navigation menu with CSS and force the use of the switch page buttons
  2. Add a conditional as the first line of your second page to check if the necessary data is stored, switching back to your primary page if not.
  3. Use the st-pages module to change how Streamlit recognizes pages. This package would allow you to create a second page without it being in a pages folder and would also allow you to hide pages in navigation as desired. (I’ve linked that package above in my first reply.)

For example, if you keep a folder structure of:

Working Directory
|___intro.py
|___trial.py

and call streamlit run intro.py to launch your app. Within intro.py, you would have:

import streamlit as st
from streamlit_extras.switch_page_button import switch_page
from st_pages import Page, show_pages, hide_pages

show_pages([
    Page("intro.py","Intro"),
    Page("trial.py","Trial")
])

hide_pages(['Trial'])

if st.button('Trial'):
    switch_page('Trial')

If you want to keep “Trial” hidden from the navigation menu on other pages, you would just have:

import streamlit as st
from st_pages import hide_pages

hide_pages(['Trial'])
2 Likes

there is still a Sidebar if we go this way