The Component Workshop: Reflex's 100 Library Components
·
Course overview
Layout
·
Box
·
Flex
·
Stack
·
Grid
·
Container
·
Section
·
Center
·
Spacer
·
Separator
·
Card
·
Inset
·
Aspect Ratio
·
Fragment
Typography
·
Text
·
Heading
·
Link
·
Code
·
Markdown
·
Blockquote
·
Quote
·
Strong
·
Em
·
Kbd
Forms
·
Button
·
Input
·
Text Area
·
Checkbox
·
Switch
·
Radio Group
·
Select
·
Slider
·
Form
·
Upload
Data display
·
Badge
·
Avatar
·
Icon
·
Callout
·
List
·
Data List
·
Code Block
·
Progress
·
Spinner
·
Scroll Area
·
Moment
Disclosure
·
Tabs
·
Accordion
·
Segmented Control
Overlay
·
Tooltip
·
Popover
·
Hover Card
·
Dialog
·
Alert Dialog
·
Drawer
·
Dropdown Menu
·
Context Menu
·
Toast
Dynamic rendering
·
Cond
·
Match
·
Foreach
·
Auto Scroll
Media
·
Image
·
Audio
·
Video
Tables and data grids
·
Table
·
Data Table
·
Data Editor
Charts
·
Area Chart
·
Bar Chart
·
Line Chart
·
Composed Chart
·
Pie Chart
·
Radar Chart
·
Radial Bar Chart
·
Scatter Chart
·
Funnel Chart
·
Treemap
·
Sankey Chart
·
Error Bar
Chart parts
·
Axis
·
Cartesian Grid
·
Chart Tooltip
·
Legend
·
Label
·
Reference
·
Brush
Other chart libraries
·
Plotly
·
Pyplot
HTML elements
·
HTML
·
HTML Text
·
HTML Layout
·
HTML Forms
·
HTML Media
·
HTML Tables
·
SVG
Other tools
·
Theme
·
Skeleton
·
Clipboard
·
Html Embed
·
Script
·
Memo
Workshop order
Make the café's inventory sheet editable in place, like a spreadsheet, and recalculate the total stock value after every edit.
rx.data_editor is a data grid where you select cells and edit them, like a spreadsheet. It is rendered with Glide Data Grid, so it stays fast even with a great many rows and columns. For a read-only table, Data Table is enough; reach for this component when users need to change values.
columns is a list with one dict per column.
title: the column title.id: the column's id. If missing, it is the title in lowercase.width: column width (px).type: the cell type. Use "str" (default), "int", "float", "bool" or "image".group: draws one extra group title above the columns that share the same value.data is a list with one list per row. Each value must match its column's type.
rx.data_editor(
columns=[
{"title": "Code", "type": "str"},
{"title": "Value", "type": "int"},
{"title": "Activated", "type": "bool"},
],
data=[
["A", 1, True],
["B", 2, False],
["C", 3, False],
["D", 4, True],
["E", 5, True],
["F", 6, False],
],
)Click the grid to select cells, rows and columns. Double-click a cell to open the editor (this lab doesn't save values, so an edited value soon snaps back).
Try the props
row_markers
column_select
row_select
range_select
vertical_border
show_search
rx.data_editor(
columns=[
{"title": "Code", "type": "str"},
{"title": "Value", "type": "int"},
{"title": "Activated", "type": "bool"},
],
data=[["A", 1, True], ["B", 2, False], ["C", 3, False], ["D", 4, True]],
row_markers="number",
column_select="single",
row_select="multi",
range_select="rect",
vertical_border=True,
show_search=False,
height="220px",
)| Prop | Values | Use |
|---|---|---|
columns · data | list | Column settings, row data |
rows | int | Number of rows. Defaults to the length of data |
row_height · header_height · group_header_height | int (px) | Height of rows, column titles and group titles |
row_markers | "none" "number" "checkbox" "both" "checkbox-visible" "clickable-number" | Marker in front of each row |
row_marker_start_index · row_marker_width | int | First row number, marker column width |
freeze_columns | int | Number of columns pinned on the left during horizontal scrolling |
min_column_width · max_column_width · max_column_auto_width | int (px) | Limits on column width |
column_select · row_select | "none" "single" "multi" | Selecting columns and rows |
range_select | "none" "cell" "rect" "multi-cell" "multi-rect" | Selecting cell ranges |
*_selection_blending · row_selection_mode · span_range_behavior | string | Fine details, such as whether different kinds of selection can coexist |
smooth_scroll_x · smooth_scroll_y | True / False | Smooth scrolling |
vertical_border · draw_focus_ring · fixed_shadow_x · fixed_shadow_y | True / False | Vertical lines, focus ring, shadow on the pinned area |
fill_handle | True / False | Handle on the cell corner that fills values by dragging |
get_cells_for_selection | True / False | Allow copying the selected cells (default True) |
overscroll_x · overscroll_y · scroll_offset_x · scroll_offset_y | int | Extra scroll room past the end, initial scroll position |
prevent_diagonal_scrolling · show_search | True / False | Block diagonal scrolling, search box |
theme | rx.data_editor_theme(...) or dict | Colors and fonts |
grid_selection | selection dict | The current selection. If you use on_grid_selection_change, always wire it up too |
| Event | Receives | When |
|---|---|---|
on_cell_clicked · on_cell_activated · on_cell_context_menu | [col, row] | A cell is clicked, activated with Enter or a double-click, or right-clicked |
on_cell_edited | [col, row], new cell dict | A cell value is edited (the new value is in dict["data"]) |
on_finished_editing | new cell dict, move direction | The editor closes |
on_header_clicked · on_header_context_menu | [col, row] | A column title is clicked or right-clicked |
on_header_menu_click | column, position | The menu button on a column title is clicked |
on_group_header_clicked · on_group_header_context_menu · on_group_header_renamed | - | A group title is clicked, right-clicked or renamed |
on_item_hovered | [col, row] | The pointer moves over a cell |
on_grid_selection_change · on_selection_cleared | selection dict · - | The selection changes or is cleared |
on_delete | selection dict | Delete is pressed on the selected cells |
on_row_appended · on_column_resize · on_search_close | - · column, width · - | A row is appended, a column is resized, the search box closes |
A column with type="image" shows image URLs as small thumbnails. A value can be a single URL or a list of URLs holding several images. Increase row_height so the images are easy to see. Clicking an image cell opens a larger view.
rx.data_editor(
columns=[
{"title": "Preview", "type": "image", "width": 120},
{"title": "Name", "type": "str"},
],
data=[
["https://picsum.photos/id/237/160/100", "Puppy"],
["https://picsum.photos/id/1025/160/100", "Pug"],
[["https://picsum.photos/id/1084/160/100", "https://picsum.photos/id/1074/160/100"], "Two animals"],
],
row_height=90,
height="320px",
)on_cell_clicked passes the position of the clicked cell as [col, row] (both starting at 0). The column settings below include group: "Data", so the five columns other than Title get an extra group title, "Data", above them.
from typing import Any
class DataEditorClickState(rx.State):
clicked_data: str = "Cell clicked: "
cols: list[dict[str, Any]] = [
{"title": "Title", "type": "str"},
{"title": "Name", "type": "str", "group": "Data", "width": 220},
{"title": "Birth", "type": "str", "group": "Data", "width": 150},
{"title": "Human", "type": "bool", "group": "Data", "width": 80},
{"title": "House", "type": "str", "group": "Data"},
{"title": "Patronus", "type": "str", "group": "Data"},
]
data: list[list[Any]] = [
["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "Stag"],
["2", "Ronald Bilius Weasley", "1 March 1980", True, "Gryffindor", "Jack Russell terrier"],
["3", "Hermione Jean Granger", "19 September 1979", True, "Gryffindor", "Otter"],
["4", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "None"],
]
@rx.event
def click_cell(self, pos: tuple[int, int]):
col, row = pos
self.clicked_data = f"Cell clicked: column {col}, row {row}"
rx.vstack(
rx.heading(DataEditorClickState.clicked_data, size="3"),
rx.data_editor(
columns=DataEditorClickState.cols,
data=DataEditorClickState.data,
on_cell_clicked=DataEditorClickState.click_cell,
height="220px",
),
width="100%",
)Data Editor only lets cells be edited; it does not save the edited values by itself. The on_cell_edited handler receives the position and the new cell info, and has to update data in State for the change to stay on screen. The new value is in new_value["data"]. Double-click a cell to edit it, or click an Activated checkbox.
The official docs don't include an example of this event, but you can't build an editable grid without it.
Last edit: nothing yet
from typing import Any
class DataEditorEditState(rx.State):
cols: list[dict[str, Any]] = [
{"title": "Code", "type": "str"},
{"title": "Value", "type": "int"},
{"title": "Activated", "type": "bool"},
]
data: list[list[Any]] = [["A", 1, True], ["B", 2, False], ["C", 3, False]]
last_edit: str = "nothing yet"
@rx.event
def edit_cell(self, pos: tuple[int, int], new_value: dict[str, Any]):
col, row = pos
rows = [list(r) for r in self.data]
rows[row][col] = new_value.get("data")
self.data = rows
self.last_edit = f"row {row}, column {col} -> {new_value.get('data')}"
@rx.event
def add_row(self):
self.data = self.data + [["New", 0, False]]
rx.vstack(
rx.data_editor(
columns=DataEditorEditState.cols,
data=DataEditorEditState.data,
on_cell_edited=DataEditorEditState.edit_cell,
row_markers="number",
height="200px",
),
rx.hstack(
rx.button("Add row", size="1", on_click=DataEditorEditState.add_row),
rx.text("Last edit: ", DataEditorEditState.last_edit, size="2"),
align="center",
),
width="100%",
)Pass colors and fonts to theme, either as keywords to rx.data_editor_theme(...) (in the official docs, from reflex.components.datadisplay.dataeditor import DataEditorTheme) or as a plain dict. Keys work both in snake case, like accent_color, and in camel case, like accentColor. The example below adds row_height=50, smooth scrolling and single-column selection to a dark theme.
dataeditor_dark_theme = {
"accent_color": "#8c96ff",
"accent_light": "rgba(202, 206, 255, 0.253)",
"text_dark": "#ffffff",
"text_medium": "#b8b8b8",
"text_light": "#a0a0a0",
"text_bubble": "#ffffff",
"bg_icon_header": "#b8b8b8",
"fg_icon_header": "#000000",
"text_header": "#a1a1a1",
"text_header_selected": "#000000",
"bg_cell": "#16161b",
"bg_cell_medium": "#202027",
"bg_header": "#212121",
"bg_header_has_focus": "#474747",
"bg_header_hovered": "#404040",
"bg_bubble": "#212121",
"bg_bubble_selected": "#000000",
"bg_search_result": "#423c24",
"border_color": "rgba(225,225,225,0.2)",
"drilldown_border": "rgba(225,225,225,0.4)",
"link_color": "#4F5DFF",
"header_font_style": "bold 14px",
"base_font_style": "13px",
}
rx.data_editor(
columns=[
{"title": "Code", "type": "str", "width": 120},
{"title": "Value", "type": "int", "width": 120},
{"title": "Activated", "type": "bool", "width": 120},
],
data=[["A", 1, True], ["B", 2, False], ["C", 3, False], ["D", 4, True], ["E", 5, True]],
row_height=50,
smooth_scroll_x=True,
smooth_scroll_y=True,
column_select="single",
theme=rx.data_editor_theme(**dataeditor_dark_theme),
height="260px",
)Save the inventory sheet with on_cell_edited, and make the total stock value and the reorder count computed vars. Edit a quantity or unit price and the badges below change right away. The item column is pinned with freeze_columns=1.
from typing import Any
class DataEditorStockState(rx.State):
cols: list[dict[str, Any]] = [
{"title": "Item", "type": "str", "width": 160},
{"title": "Qty", "type": "int", "width": 80},
{"title": "Unit price", "type": "float", "width": 110},
{"title": "Reorder", "type": "bool", "width": 90},
]
data: list[list[Any]] = [
["Coffee beans", 12, 18.5, False],
["Paper cups", 300, 0.1, False],
["Oat milk", 4, 2.9, True],
]
@rx.event
def edit_cell(self, pos: tuple[int, int], new_value: dict[str, Any]):
col, row = pos
rows = [list(r) for r in self.data]
rows[row][col] = new_value.get("data")
self.data = rows
@rx.var
def total(self) -> str:
value = sum(float(r[1] or 0) * float(r[2] or 0) for r in self.data)
return f"${value:,.2f}"
@rx.var
def reorder_count(self) -> int:
return sum(1 for r in self.data if r[3])
rx.vstack(
rx.data_editor(
columns=DataEditorStockState.cols,
data=DataEditorStockState.data,
on_cell_edited=DataEditorStockState.edit_cell,
row_markers="number",
freeze_columns=1,
height="180px",
),
rx.hstack(
rx.badge("Stock value: ", DataEditorStockState.total, size="2"),
rx.badge("To reorder: ", DataEditorStockState.reorder_count, color_scheme="orange", size="2"),
),
width="100%",
)Workshop notes
columns is a list of {title, type, width, id, group} dicts; data is a list of rows. Values must match the column's type.
type: str · int · float · bool · image.
Edits only stick if on_cell_edited((col, row), new cell) updates data in State.
Selection range: column_select · row_select · range_select; row markers: row_markers.
theme=rx.data_editor_theme(...) or a dict; size is set with height and row_height.
Get news about new lessons
We'll email you when this course gets new lessons or a follow-up course opens.
R
ReflexKR
Learn Python and Reflex by doing. Every example was run and verified on Reflex 0.10.0.
ReflexKR is free, with no banner ads. If it helped you, you can support it with a coffee. ☕ Buy us a coffee
Affiliate disclosure© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Questions & answers
Ask anything about this lesson. Questions from every language version of this page appear together.