What Is a Streamlit DataFrame? How to Use st.dataframe and st.data_editor
Updated on
A Streamlit dataframe is the main way to show tabular data inside a Streamlit app.
What a Streamlit dataframe means
When people say "Streamlit dataframe," they usually mean one of two things:
st.dataframefor displaying data interactivelyst.data_editorfor letting users edit that data in the app
In plain terms, Streamlit takes a table-like object such as a pandas DataFrame, a Polars DataFrame, or another dataframe-like structure and turns it into a browser-friendly table. That table can be readable, scrollable, selectable, and sometimes editable depending on which API you use.
Under the hood, Streamlit's interactive dataframe UI is built on Glide Data Grid (opens in a new tab), which is one reason it behaves more like a rich data grid than a plain HTML table.
Why Streamlit dataframes matter
For many Streamlit apps, the dataframe is not just one component among many. It is the product.
That is especially true when you are building:
- internal analysis tools
- lightweight review workflows
- data cleaning utilities
- monitoring dashboards
- upload-and-preview apps
If the table feels clumsy, the whole app usually feels clumsy.
What st.dataframe and st.data_editor are actually for
The key decision is not "how do I show a table?" but "what kind of interaction should the table support?"
Use st.dataframe when the table is mainly for viewing, scanning, selecting, and exploring.
Use st.data_editor when the user should be able to change values, add rows, or remove rows.
After reading this guide, you should be able to:
- choose the right table API for the job
- configure columns more clearly
- handle styling and editing without brittle hacks
- avoid common performance and UX mistakes with large tables
Quick Answer
If the table should be read-only, start with st.dataframe.
If the table should be editable, start with st.data_editor.
That single distinction solves most beginner confusion around Streamlit tables.
Quick Start
This first example shows the simplest read-only table.
import pandas as pd
import streamlit as st
df = pd.DataFrame(
{
"product": ["A", "B", "C"],
"revenue": [1250, 980, 1540],
}
)
st.dataframe(df, width="stretch")This second example shows the editable version of the same idea.
import pandas as pd
import streamlit as st
df = pd.DataFrame(
{
"product": ["A", "B", "C"],
"approved": [True, False, True],
}
)
edited_df = st.data_editor(df, width="stretch")
st.write(edited_df)The difference is simple but important: st.dataframe displays data, while st.data_editor returns edited data back to your script.
What st.dataframe is good at
st.dataframe is best when users need to inspect data without changing it.
That usually means:
- browsing uploaded data
- checking filtered results
- comparing records
- selecting rows or cells for follow-up actions
- reviewing metrics in a table before charting them
Modern st.dataframe also supports selection behavior through on_select and selection_mode, which makes it useful for lightweight table-driven workflows.
What st.data_editor is good at
st.data_editor is best when the table is part of the input flow.
That usually means:
- fixing labels
- marking approvals
- editing planning tables
- creating or deleting rows in a small operational dataset
- letting users adjust parameters in a grid-like form
This is why st.data_editor often feels less like "a chart companion" and more like "a spreadsheet step inside the app."
Core arguments you should know
st.dataframe
| Argument | What it does | When to care |
|---|---|---|
width | Controls table width | Use width="stretch" for full-width layouts |
height | Controls table height | Useful for large tables |
hide_index | Hides index columns | Good for cleaner presentation |
column_order | Reorders or limits columns | Useful for tighter views |
column_config | Formats and customizes columns | Important for production-like tables |
on_select | Enables selection-driven reruns or callbacks | Good for row-based workflows |
selection_mode | Chooses row, column, or cell selection | Useful for interaction design |
row_height | Changes row height | Helpful for dense or multi-line content |
placeholder | Sets text for missing values | Useful for cleaner display |
st.data_editor
| Argument | What it does | When to care |
|---|---|---|
width / height | Controls editor size | Important in dense layouts |
hide_index | Hides index columns | Good for simpler editing views |
column_config | Formats and constrains columns | Important for editing UX |
num_rows | Controls whether rows are fixed, addable, or deletable | Critical when users should manage rows |
disabled | Locks all or some columns | Useful when only some fields should change |
on_change | Runs a callback when edits happen | Useful for stateful workflows |
row_height | Changes row height | Helpful for custom cell presentation |
placeholder | Sets text for missing values | Useful for sparse data |
Configure columns before you reach for CSS
For most table cleanup, column_config is the first tool to use.
This example shows how to rename and format columns without changing the underlying dataframe.
import pandas as pd
import streamlit as st
df = pd.DataFrame(
{
"item": ["A", "B", "C"],
"price": [1.5, 2.0, 3.25],
"margin": [0.12, 0.18, 0.27],
}
)
st.dataframe(
df,
width="stretch",
column_config={
"price": st.column_config.NumberColumn("Price", format="$%.2f"),
"margin": st.column_config.NumberColumn("Margin", format="%.0f%%"),
},
)This is usually better than custom CSS because it is clearer, safer, and aligned with the Streamlit API.
Style data carefully
If you want visual emphasis, a pandas Styler can still help.
This example highlights the maximum value in each numeric column.
import pandas as pd
import streamlit as st
df = pd.DataFrame(
{
"region": ["APAC", "EMEA", "LATAM"],
"revenue": [120, 180, 90],
"profit": [25, 41, 18],
}
)
st.dataframe(df.style.highlight_max(axis=0), width="stretch")The practical limit is important here: Streamlit supports useful styling such as custom cell values, colors, and font weights, but not every exotic pandas styling feature maps cleanly into the interactive table.
Use row selection for lightweight workflows
st.dataframe can now be more than a passive display widget. If you enable selection, it can behave more like an input.
This example shows row selection driving a follow-up detail view.
import pandas as pd
import streamlit as st
df = pd.DataFrame(
{
"ticket_id": [101, 102, 103],
"status": ["Open", "In Review", "Closed"],
"owner": ["Ava", "Ben", "Chen"],
}
)
selection = st.dataframe(
df,
on_select="rerun",
selection_mode="single-row",
width="stretch",
)
rows = selection["selection"]["rows"]
if rows:
selected_row = df.iloc[rows[0]]
st.write("Selected ticket:", selected_row.to_dict())This is often enough for simple review tools without building a heavier custom UI.
Use st.data_editor when editing is the product
The next example shows a grid where some columns are editable and some are intentionally locked.
import pandas as pd
import streamlit as st
df = pd.DataFrame(
{
"task": ["Check schema", "Approve upload", "Publish report"],
"priority": [1, 2, 3],
"done": [False, False, True],
}
)
edited_df = st.data_editor(
df,
width="stretch",
disabled=["task"],
column_config={
"priority": st.column_config.NumberColumn("Priority", min_value=1, max_value=5),
"done": st.column_config.CheckboxColumn("Done"),
},
)
st.write(edited_df)This works well because the user knows exactly what is editable and what is not.
Let users add or delete rows only when the workflow needs it
If the table is acting like a lightweight spreadsheet, num_rows is the setting that matters most.
import pandas as pd
import streamlit as st
df = pd.DataFrame(
[
{"name": "Alice", "quota": 10},
{"name": "Ben", "quota": 12},
]
)
edited_df = st.data_editor(
df,
num_rows="dynamic",
width="stretch",
)Use this carefully. Dynamic rows are useful for planning sheets and admin tools, but they can also make validation and downstream logic more complex.
Large tables: what actually helps
Large tables are where good intentions often turn into poor app experience.
The most practical fixes are usually:
- show only the columns users need first
- filter before rendering
- cap height instead of dumping a giant scrolling page
- cache data loading with
st.cache_data - use selection and drill-down views instead of trying to show everything at once
This example keeps the table focused by slicing the data before display.
import pandas as pd
import streamlit as st
df = pd.read_csv("large_dataset.csv")
page_size = 100
page = st.number_input("Page", min_value=1, value=1)
start = (page - 1) * page_size
end = start + page_size
st.dataframe(df.iloc[start:end], height=400, width="stretch")For user experience, this is often better than trying to prove the app can render everything at once.
st.dataframe vs st.table vs st.data_editor
| Need | Better choice |
|---|---|
| Interactive read-only table | st.dataframe |
| Static small table | st.table |
| Editable grid | st.data_editor |
This comparison is more useful than memorizing every argument. Start with the interaction model you need.
A more exploratory alternative: PyGWalker
st.dataframe is one way to render a dataframe inside Streamlit.
If you want a more exploratory interface, PyGWalker is another useful path. Instead of only showing the dataframe as a grid, PyGWalker can render it as a richer interactive analysis surface inside Streamlit.
That is useful when the reader wants more than inspection and light editing.
pygwalker.table(...)gives you a richer table view with column profiling in the headerpygwalker.walk(...)gives you a drag-and-drop, Tableau-like exploration interface for the dataframe
If your app is moving from "show the table" to "let the user explore the table visually," this is often the more natural upgrade path.
See:
Common mistakes
1. Using st.data_editor when nobody should edit anything
If the table is read-only, st.dataframe is usually clearer and simpler.
2. Fighting table formatting with CSS
Most formatting problems are better solved with column_config, hide_index, column_order, and sensible widths.
3. Showing too much data at once
Even if the app can render a huge table, that does not mean it is the best interface.
4. Mixing incompatible types in one editable column
st.data_editor can become hard or impossible to edit when a single column mixes types.
5. Forgetting that editing changes the returned data
st.data_editor returns the edited result. If you ignore that return value, the UI may look editable while your app logic still uses stale data.
Troubleshooting
Why is my table not full width?
Use width="stretch". The old use_container_width=True pattern is deprecated.
Why won't one of my st.data_editor columns edit correctly?
Check for mixed types, unsupported types, or a disabled configuration that is locking the column.
Can I style everything with pandas Styler?
No. Streamlit supports useful styling, but not every advanced Styler feature maps cleanly to the interactive table.
How do I let users pick rows from a dataframe?
Use on_select with a selection mode such as "single-row" or "multi-row" on st.dataframe.
Should I use a dataframe as both the full database and the full UI?
Usually no. It is often better to show a focused slice for browsing and keep broader logic in cached loaders, filters, or backend queries.
Related Guides
- Streamlit Caching
- Streamlit Session State
- Streamlit Upload File
- Streamlit Selectbox
- Streamlit Columns
Frequently Asked Questions
What does st.dataframe do in simple terms?
It displays tabular data as an interactive table inside a Streamlit app.
What is the difference between st.dataframe and st.data_editor?
st.dataframe is mainly for viewing and selecting data. st.data_editor is for editing data and returning the edited result.
How do I make a Streamlit dataframe full width?
Use width="stretch" on st.dataframe or st.data_editor.
How do I format Streamlit dataframe columns?
Use column_config for labels, formats, widths, visibility, and editing rules.
Can users select rows in a Streamlit dataframe?
Yes. Enable selection with on_select and choose a selection_mode such as single-row or multi-row.