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
rx.auto_scrollOfficial docs
Workshop order
Build a customer support chat window. It should follow new messages down to the bottom, but not drag you down while you've scrolled up to read earlier messages.
rx.auto_scroll is a box (a div) that scrolls to the bottom by itself when new content is added. Use it for content that keeps piling up at the bottom, like chats, logs and feeds, so the newest item is always visible. You don't have to write any scroll-position code yourself.
class AutoScrollBasicState(rx.State):
messages: list[str] = ["Initial message"]
count: int = 1
@rx.event
def add_message(self):
self.count += 1
self.messages = (self.messages + [f"New message #{self.count}"])[-50:]
rx.vstack(
rx.auto_scroll(
rx.foreach(
AutoScrollBasicState.messages,
lambda message: rx.box(
message,
padding="0.5em",
border_bottom=f"1px solid {rx.color('gray', 4)}",
width="100%",
),
),
height="200px",
width="300px",
border=f"1px solid {rx.color('gray', 6)}",
border_radius="8px",
),
rx.button("Add Message", on_click=AutoScrollBasicState.add_message),
width="300px",
align_items="center",
)Every click on Add Message adds a message, and once the box starts to overflow, it scrolls down by itself so the new message is visible.
rx.auto_scroll is based on rx.el.div, so it accepts all div props and styles. Since it's a scroll box, height matters most. The box below holds twenty lines, yet the last line (Line 20) is visible from the start. Shrink the height to make it overflow and the bottom stays in view.
Try the props
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10
Line 11
Line 12
Line 13
Line 14
Line 15
Line 16
Line 17
Line 18
Line 19
Line 20
height
padding
radius
rx.auto_scroll(
*[rx.text("Line " + str(i), size="2") for i in range(1, 21)],
height="160px",
padding="8px",
border_radius="8px",
border=f"1px solid {rx.color('gray', 6)}",
width="260px",
)| Prop | Values | Default | Use |
|---|---|---|---|
height | CSS length | none | Height of the scroll box. Without it, the box grows with its content and never scrolls |
width | CSS length | none | Width |
overflow | CSS value | "auto" | How overflowing content is handled. Scrolls by default |
padding, border, border_radius | CSS value | none | Inner spacing, border, corners |
id | string | automatic | If you don't give one, a unique id is added automatically |
| other div props | title, role, on_click … | Same as rx.el.div |
When content is added, it scrolls to the bottom in only two cases:
So while the user has scrolled up to read earlier content, it doesn't force them down. Scroll the log box below up and press Add 10: the position stays put. Scroll back to the bottom and press it again, and it follows.
import random
class AutoScrollLogState(rx.State):
lines: list[str] = []
count: int = 0
@rx.event
def add(self, n: int):
levels = ["INFO", "INFO", "INFO", "WARN", "ERROR"]
new = []
for _ in range(n):
self.count += 1
new.append(f"{self.count:04d} {random.choice(levels)} request handled")
self.lines = (self.lines + new)[-100:]
@rx.event
def clear(self):
self.lines = []
self.count = 0
rx.vstack(
rx.auto_scroll(
rx.foreach(AutoScrollLogState.lines, lambda line: rx.text(line, size="1", font_family="var(--code-font-family)")),
height="160px",
width="100%",
padding="8px 10px",
background=rx.color("gray", 2),
border=f"1px solid {rx.color('gray', 6)}",
border_radius="8px",
),
rx.hstack(
rx.button("Add 1", on_click=AutoScrollLogState.add(1), size="1"),
rx.button("Add 10", on_click=AutoScrollLogState.add(10), size="1", variant="soft"),
rx.button("Clear", on_click=AutoScrollLogState.clear, size="1", variant="soft", color_scheme="gray"),
),
width="100%",
max_width="420px",
)Giving rx.auto_scroll display="flex" and flex_direction="column" stacks the speech bubbles vertically, and rx.cond switches alignment and color depending on the sender. Sending a message adds both your message and the reply, and the window follows them down. If you've scrolled up to read, it stays where it is.
class AutoScrollChatState(rx.State):
messages: list[dict[str, str]] = [{"who": "bot", "text": "Hi! Ask me about your order."}]
@rx.event
def send(self, form_data: dict):
text = form_data.get("text", "").strip()[:200]
if not text:
return
reply = {"who": "bot", "text": f"Got it: {text}"}
self.messages = (self.messages + [{"who": "me", "text": text}, reply])[-40:]
rx.vstack(
rx.auto_scroll(
rx.foreach(
AutoScrollChatState.messages,
lambda m: rx.flex(
rx.box(
m["text"],
padding="6px 10px",
border_radius="12px",
max_width="80%",
background=rx.cond(m["who"] == "me", rx.color("indigo", 9), rx.color("gray", 4)),
color=rx.cond(m["who"] == "me", "white", rx.color("gray", 12)),
),
justify=rx.cond(m["who"] == "me", "end", "start"),
width="100%",
),
),
display="flex",
flex_direction="column",
gap="6px",
height="240px",
width="100%",
padding="10px",
border=f"1px solid {rx.color('gray', 6)}",
border_radius="10px",
),
rx.form(
rx.hstack(
rx.input(name="text", placeholder="Type a message", flex="1", auto_complete=False),
rx.button(rx.icon("send", size=16), type="submit", aria_label="Send"),
width="100%",
),
on_submit=AutoScrollChatState.send,
reset_on_submit=True,
width="100%",
),
width="100%",
max_width="360px",
)Workshop notes
rx.auto_scroll(...) is a div that follows new content down to the bottom.
Always give it a height. overflow="auto" is set by default.
It scrolls down only when you are near the bottom (50px) or when a scrollbar newly appears.
It doesn't drag down users who have scrolled up to read. Ideal for chats, logs and feeds.
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.