Hi folks!
I wanted to preview a new way to define multi-page Streamlit apps and some new features coming to the side navigation. We’re aiming to release these updates within the next 2 months.
NOTE: this will be additive, the current MPA approach with pages/
will still work just fine
This is cross-posted on GitHub issue #8388.
Update: Check out the demo app & whl file!
Summary
Using the new API, when you do streamlit run streamlit_app.py
, the contents of streamlit_app.py
will automatically run before every page instead of defining the home page. Any common code can go here.
- We’re building a new API called
st.navigation()
withst.Page()
to programmatically define the available pages of your app. - This means the available and displayed pages can change in a given session / rerun! (see code example below)
- Besides defining your pages / nav, you can also add any common session setup code, authorization check, page_config, styles, etc only once in this file.
- We also plan to add native support for an app logo at the top left, text headings between page groups in the native navigation, and support for Material icons in addition to emojis
Here’s how a native side navigation might look using all the new features:
Here’s the simplest example of how this would look in your app code:
# streamlit_app.py
# Define all the available pages, and return the current page
current_page = st.navigation([
st.Page("hello.py", title="Hello World", icon=":material:Plane:"),
st.Page("north_star.py", title="North Star", icon=":material:Star:"),
# ...
])
# call run() to execute the current page
current_page.run()
Pages can be defined by path to a python file, or passing in a function.
def Page(
page: str | Callable,
*,
title: str = None,
icon: str = None, # emojis and material icons
default: bool = False, # Explicitly set a default page ("/") for the app
key: str = None, # set an identifier and /url-path for the page, otherwise inferred
)
New navigation UI
Here’s a fuller example with a logo and section headers.
st.logo("my_logo.png")
# Define all the available pages, and return the current page
current_page = st.navigation({
"Overview": [
st.Page("hello.py", title="Hello World", icon=":material:Plane:"),
st.Page("north_star.py", title="North Star", icon=":material:Star:"),
],
"Metrics": [
st.Page("core_metrics.py", title="Core Metrics", icon=":material:Hourglass:"),
# ...
],
})
# current_page is also a Page object you can .run()
current_page.run()
Logos
Calling st.logo(image)
adds an app logo in the top left of your app, floating above the navigation / sidebar.
Material icons
You’ll be able to use a wide range of Material icons for page navigation and other elements that support icon=
today. Our current plan is to support the Material icons built into @emotion. You can specify these via shortcode, such as icon=":material:Archive:"
. The final details of this might change a bit before release.
Navigation headers
By default, st.navigation
expects a list of Pages (List[st.Page]
). However you can also pass Dict[str: List[st.Page]]
. In this case, each dictionary key becomes a section header in the navigation with the listed pages below. E.g.
current_page = st.navigation({
"Overview": [ # "Overview" becomes a section header
st.Page("hello.py"),
st.Page("north_star.py"),
],
"Metrics": [ # "Metrics becomes a section header
st.Page("core_metrics.py"),
# ...
],
})
Dynamic navigation
The available pages are re-assessed on each rerun. So, for example, if you want to add some pages only if the user is authenticated, you can just append them to the list passed to st.navigation
based on some check. E.g.:
import streamlit as st
pages = [st.Page("home.py", title="Home", icon="🏠", default=True)]
if is_authenticated():
pages.append(st.Page("step_1.py", title="Step 1", icon="1️⃣"))
pages.append(st.Page("step_2.py", title="Step 2", icon="2️⃣"))
page = st.navigation(pages)
page.run()
You can also set position="hidden"
on st.navigation if you want to use the new API while defining your own navigation UI (such as via st.page_link
and st.switch_page
).
# streamlit_app.py
import streamlit as st
pages = [
st.Page("page1.py", title="Page 1", icon="📊"),
st.Page("page2.py", title="Page 2", icon="🌀"),
st.Page("page3.py", title="Page 3", icon="🧩"),
]
# Makes pages available, but position="hidden" means it doesn't draw the nav
# This is equivalent to setting config.toml: client.showSidebarNavigation = false
page = st.navigation([pages], position="hidden")
page.run()
# page1.py
st.write("Welcome to my app. Explore the sections below.")
col1, col2, col3 = st.columns(3)
col1.page_link("page1.py")
col2.page_link("page2.py")
col3.page_link("page3.py")
# page2.py
st.markdown(long_about_text)
if st.button("Back"):
st.switch_page("page1.py")
We’re still putting the final touches on this feature so the final API and UI might change slightly. But we’re excited about this and wanted to share, so you know what’s coming and in case you have early feedback! Thanks!!