Hey there!
I’m looking for a table of contents to include in my report. I looked in the streamlit documentation and in the forum and couldn’t find any reference to it. Does streamlit have this kind of widget?
Thanks
Hey there!
I’m looking for a table of contents to include in my report. I looked in the streamlit documentation and in the forum and couldn’t find any reference to it. Does streamlit have this kind of widget?
Thanks
Hi @Avi_Aminov -
There is not currently a table of contents widget. Is your app sufficiently large where this needs to be auto-generated?
Best,
Randy
Hello @Avi_Aminov,
There’s no widget to do that as it would require to catch every other widget rendering markdown text and generate a TOC from that. And to keep things clean and simple, widgets does not communicate this way with each other.
In my opinion, it’d be more appropriate to implement a function (or use a library) that generates a TOC from a markdown input, and display it like you’d display any other markdown text.
While I do think the TOC generation is a bit much, it would be nice to be able to scroll to a given header through the URL, as more and more websites provide this ability (like here is the section on install in Streamlit). For a report, I would be able to send an url pointing to the results section directly instead. You would also need this scroll if you provide a clickable TOC like in @Synode’s idea
@Avi_Aminov do you have other ideas for navigating through your large report ? How large / structured is it ?
There seems to be a very old issue for this if we want to bump it…also with all of the lazy loading and Python background computation going around, it may be a bit tricky to implement
In my opinion, it’d be more appropriate to implement a function (or use a library) that generates a TOC from a markdown input, and display it like you’d display any other markdown text.
Thanks for the reply. I understand how it makes sense to keep the elements separated. I can implement such a function, but how do I get the markdown input generated by streamlit to do so?
You could think about this in the reverse, creating a dictionary or list holding the values you want to have for both the TOC and headers. For the TOC, iterate over all the values and make a list, and for each header, pick the key that’s appropriate to display.
Makes the code a bit more abstracted I suppose, but by having the data in a single data object, they stay in sync.
I see two problems with this:
Alright, here’s an example. You can place your TOC in the sidebar or on the main page, anywhere you want. The only requirement is to use toc’s title()
, header()
and subheader()
methods to create your titles. Call toc.generate()
once you have displayed every title, at the very end of your app.
And just in case, make sure you don’t use user inputs to create your titles. I’m using unsafe_allow_html
, so if the end user can change titles somehow by anything he wants, he’d be able to execute code on your page.
import streamlit as st
class Toc:
def __init__(self):
self._items = []
self._placeholder = None
def title(self, text):
self._markdown(text, "h1")
def header(self, text):
self._markdown(text, "h2", " " * 2)
def subheader(self, text):
self._markdown(text, "h3", " " * 4)
def placeholder(self, sidebar=False):
self._placeholder = st.sidebar.empty() if sidebar else st.empty()
def generate(self):
if self._placeholder:
self._placeholder.markdown("\n".join(self._items), unsafe_allow_html=True)
def _markdown(self, text, level, space=""):
key = "".join(filter(str.isalnum, text)).lower()
st.markdown(f"<{level} id='{key}'>{text}</{level}>", unsafe_allow_html=True)
self._items.append(f"{space}* <a href='#{key}'>{text}</a>")
toc = Toc()
st.title("Table of contents")
toc.placeholder()
toc.title("Title")
for a in range(10):
st.write("Blabla...")
toc.header("Header 1")
for a in range(10):
st.write("Blabla...")
toc.header("Header 2")
for a in range(10):
st.write("Blabla...")
toc.subheader("Subheader 1")
for a in range(10):
st.write("Blabla...")
toc.subheader("Subheader 2")
for a in range(10):
st.write("Blabla...")
toc.generate()
Wow! This is super cool, thanks @okld !
Thanks for the code! I adapted your version of Toc() with an extra Header class here:
Hi @okld, thanks for your code! I was able to jump to Title but not header or subheader and couldn’t figure out why. Could you please help? Thank you!
Did you happen to figure out the solution to this? If yes, please do let me know
I also had this issue, solved it by replacing
key = "".join(filter(str.isalnum, text)).lower()
with
import re
key = re.sub('[^0-9a-zA-Z]+', '-', text).lower()
Hi everyone,
Thanks for sharing this. Do you know if there is a way to stop the anchor tags from showing at the end of url? Does it require jQuery or javascript?
Very nice! is it possible to adapt the toc code to work with headers or sub headers within an st.tab? So, for example, in the tabs below if I click on the header in the toc “An Owl” it would guide me towards and open the tab3. In the toc current state if you click on a header that is within a tab it does not take you anywhere. Any ideas on a workaround? Thank you.
tab1, tab2, tab3 = st.tabs(["Cat", "Dog", "Owl"])
with tab1:
st.header("A cat")
st.image("https://static.streamlit.io/examples/cat.jpg", width=200)
with tab2:
st.header("A dog")
st.image("https://static.streamlit.io/examples/dog.jpg", width=200)
with tab3:
st.header("An owl")
st.image("https://static.streamlit.io/examples/owl.jpg", width=200)
The real problem is that the TOC Widget does not work as stated here in the current verson.
This is caused by streamlit replacing any given ID added to a heading flag by the text of the heading. Your solution works. The original code only keeps alpha numeric chars, while your version keeps the dash, exactly like streamlit does.
The entire thing breaks as soon as you have two headings with the same name as streamlit will give them the same ID – no matter what you set as ID.
The workaround is to not use any html heading but only use <p>
and style them manually, e.g.:
style = 'font-size:1.5rem;font-weight:600;color:rgb(49, 51, 63);line-height 1.2;"'
st.markdown(f"<p id='{key}' style={style}>{text}</p>", unsafe_allow_html=True)
I am not an expert, but I would guess that tabs are JavaScript. Meaning the js will on click remove the elements on one tab from the dom (or make them invisible) and adding the other tabs elements to the dom. So really, you wont get anywhere with pure html functionality. You kind of need to open the tab via a callback and only then can you jump to the right place using html.
These cookies are necessary for the website to function and cannot be switched off. They are usually only set in response to actions made by you which amount to a request for services, such as setting your privacy preferences, logging in or filling in forms.
These cookies allow us to count visits and traffic sources so we can measure and improve the performance of our site. They help us understand how visitors move around the site and which pages are most frequently visited.
These cookies are used to record your choices and settings, maintain your preferences over time and recognize you when you return to our website. These cookies help us to personalize our content for you and remember your preferences.
These cookies may be deployed to our site by our advertising partners to build a profile of your interest and provide you with content that is relevant to you, including showing you relevant ads on other websites.