Freeze column headers on st.dataframe + st.data_editor

Hi guys!

Does anyone know if we can freeze column headers when calling st.dataframe/st.data_editor? I have a very large dataframe that users need to work with and can’t get the headers to stick when scrolling down. Unfortunately I cannot use any streamlit components as the environment prohibits it.

Hey there, thanks for your question and welcome to the community! :blush: With Streamlit’s built-in st.dataframe and st.data_editor, column headers are automatically “sticky”—they remain visible (frozen) at the top as you scroll vertically through large tables. This is a default feature and doesn’t require any extra configuration or third-party components. If you’re not seeing this behavior, double-check that you’re using st.dataframe or st.data_editor and not st.table, as st.table does not support sticky headers by default.

If you need to freeze columns (not just headers), that’s not natively supported in st.dataframe or st.data_editor—you’d need a third-party component like AgGrid, but you mentioned that’s not possible in your environment. For sticky headers, though, Streamlit’s default interactive tables have you covered! According to the Streamlit documentation and community discussions, just use st.dataframe or st.data_editor and your headers will stay put as you scroll.

Sources:

Thanks but I set the height to “content” as I have pagination and a page size selectbox. Scrolling down is currently hiding the headers from view.

You will need something to bound the height of the dataframe to get the sticky headers. The headers are sticky within the dataframe element. However, if you make the element itself taller, the header only remains sticky at the top of the element itself. You’ll need to conditionally set a fixed height when the number of displayed rows exceeds what your expected screen height is. That way, the whole dataframe element fills the view and is scrollable instead of scrolling on the parent page. (If you want to get fancy, you can use a custom component to get the view height and use it in your app’s logic.)

1 Like

Here’s a little example of fetching the view height so you can dynamically constrain the dataframe to fit in the window:

import pandas as pd
import streamlit as st
from numpy.random import default_rng as rng

JS = """
export default function({ setStateValue }) {
    let height = window.innerHeight;
    let width = window.innerWidth;
    setStateValue("height", height);
    setStateValue("width", width);


    function getSize() {
        height = window.innerHeight;
        width = window.innerWidth;
        setStateValue("height", height);
        setStateValue("width", width);
    }

    // Debounced resize handler for performance
    let resizeTimeout;
    function handleResize() {
        clearTimeout(resizeTimeout);
        resizeTimeout = setTimeout(getSize, 16); // ~60fps
    }

    window.addEventListener("resize", handleResize);

    return () => {
        clearTimeout(resizeTimeout);
        window.removeEventListener("resize", handleResize);
  };
}

"""

get_size = st.components.v2.component(
    "window size",
    js = JS
)

df = pd.DataFrame(
    rng(0).standard_normal((50, 20)), columns=("col %d" % i for i in range(20))
)

size = get_size()
df_full_height = len(df)*35+37 # 37px header row + 35px per row
vh_80 = int(.8*(size.height - 60)) # 80% vh after subtracting header
df_height = min(df_full_height, vh_80) 
st.dataframe(df, height=df_height)

Much appreciated :+1: :+1: thanks!

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