Is it possible to create a sticky header?

Is it possible to create a sticky header on streamlit apps?
I have seen a few examples or other posts but with vague solutions or just not working ones.

In general what I want is to leave a section of my streamlit app sticky on top so that when I scroll down, it stays on screen.

1 Like

Someone created a navbar component. Would that help you at all?

1 Like

Here is the solution i ended up using to create a sticky header.

It is possible to create a div with a specific class-name inside the streamlit container you want to use as your header. Then using the new css has() pseudo class you can target the parent of the div you created and make it sticky.

import streamlit as st
header = st.container()
header.title("Here is a sticky header")
header.write("""<div class='fixed-header'/>""", unsafe_allow_html=True)

### Custom CSS for the sticky header
st.markdown(
    """
<style>
    div[data-testid="stVerticalBlock"] div:has(div.fixed-header) {
        position: sticky;
        top: 2.875rem;
        background-color: white;
        z-index: 999;
    }
    .fixed-header {
        border-bottom: 1px solid black;
    }
</style>
    """,
    unsafe_allow_html=True
)

This solution works for us as our app is for in-house use only thus we can guarantee everyone’s browsers is at the latest version and thus supports the has() pseudo class

3 Likes

Thanks a lot @zedfly, you just saved a bunch of my time.

I reworked a code a bit to make it more reusable. Maybe it can be useful for some of you future-visitors:

from typing import Literal

import streamlit as st

MARGINS = {
    "top": "2.875rem",
    "bottom": "0",
}

STICKY_CONTAINER_HTML = """
<style>
div[data-testid="stVerticalBlock"] div:has(div.fixed-header-{i}) {{
    position: sticky;
    {position}: {margin};
    background-color: white;
    z-index: 999;
}}
</style>
<div class='fixed-header-{i}'/>
""".strip()

# Not to apply the same style to multiple containers
count = 0


def sticky_container(
    *,
    height: int | None = None,
    border: bool | None = None,
    mode: Literal["top", "bottom"] = "top",
    margin: str | None = None,
):
    if margin is None:
        margin = MARGINS[mode]

    global count
    html_code = STICKY_CONTAINER_HTML.format(position=mode, margin=margin, i=count)
    count += 1

    container = st.container(height=height, border=border)
    container.markdown(html_code, unsafe_allow_html=True)
    return container


if __name__ == "__main__":
    st.title("Sticky Container")
    st.write("This is a demonstration of a sticky container.")
    for i in range(30):
        st.write(f"Line {i}")
    with sticky_container(mode="top", border=True):
        st.write("This is a sticky container at the top.")
        st.write("This is a sticky container at the top.")
    for i in range(30):
        st.write(f"Line {i}")
    with sticky_container(mode="bottom", border=True):
        st.write("This is a sticky container at the bottom.")
        st.write("This is a sticky container at the bottom.")
    for i in range(30):
        st.write(f"Line {i}")

I reworked a code a bit in order to support both sticky/fixed positioning. Also, now it changes width as well as all other elements. And it updates background color if color-theme changes

Here is a full code

Usage:

with fixed_container(mode="fixed", position="bottom", border=True):
    st.write("This is a fixed container.")
    st.write("This is a fixed container.")
    st.write("This is a fixed container.")

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.