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.el.fieldsetrx.el.formrx.el.labelrx.el.legendrx.el.meterrx.el.optgrouprx.el.optionrx.el.outputrx.el.progressrx.el.selectrx.el.textareaOfficial docs
Workshop order
A café order form: drinks from a list grouped by type, the pickup method as radio buttons, requests as multi-line text, and a summary once the order is placed.
These are the form-related HTML elements: form, which collects inputs; the grouping box fieldset and its title legend; the pickers select, optgroup and option; the multi-line input textarea; and meter, progress and output, which display values.
They are lighter than the Radix form components (rx.select, rx.text_area) and use the behavior the browser provides out of the box.
rx.el.label(
"Size ",
rx.el.select(
rx.el.option("Small", value="s"),
rx.el.option("Medium", value="m"),
rx.el.option("Large", value="l"),
default_value="m",
border="1px solid gray",
padding="2px 6px",
),
)meter changes color depending on whether the value is in a good or a bad range. low and high are the range boundaries, and optimum decides which side is good.
Try the props
value
65
low
30
high
80
optimum
90
rx.el.div(
rx.el.meter(value=65, min=0, max=100, low=30, high=80, optimum=90, width="260px", height="20px"),
rx.el.progress(value=65, max=100, width="260px"),
display="flex",
flex_direction="column",
gap="10px",
)Try the props
placeholder
rows
resize
disabled
read_only
rx.el.textarea(
placeholder="Write a note",
rows="4",
resize="vertical",
disabled=False,
read_only=False,
border="1px solid gray",
border_radius="6px",
padding="6px 8px",
width="280px",
)| Prop | Values | Default | Use |
|---|---|---|---|
on_submit (form) | event handler | none (only prevents the default submit) | Receives a dict of values keyed by name |
reset_on_submit (form) | True / False | False | Clears the fields after submitting |
no_validate (form) | True / False | False | Turns off the browser's required and pattern checks |
html_for (label, output) | element id | none | The input to connect. Clicking the label moves to that field |
disabled (fieldset, select, option, optgroup) | True / False | False | Disables every input inside |
value / on_change (select, textarea) | string / event | none | Controlled input. default_value is uncontrolled |
multiple size (select) | bool / integer | False / none | Pick several, number of visible rows |
label (optgroup) | string | none | Group title |
rows cols max_length (textarea) | integer | none | Size and character limit |
auto_height enter_key_submit (textarea) | True / False | False | Grow with the text, submit with Enter |
value min max low high optimum (meter) | number | min=0 max=1 | A measurement within a range |
value max (progress) | number | max=1 | How far along a task is. Without value, shows an in-progress state |
When the submit button is clicked, on_submit passes the values of the inputs in the form as a dict keyed by name. A checkbox is included, as "on", only when it is checked.
Even without on_submit, rx.el.form prevents the browser's default submit (a page reload). With reset_on_submit=True, the fields are cleared after submitting.
class HtmlFormSignupState(rx.State):
submitted: dict[str, str] = {}
@rx.event
def handle_submit(self, form_data: dict):
self.submitted = {key: str(value) for key, value in form_data.items()}
rx.el.div(
rx.el.form(
rx.el.input(name="email", type="email", placeholder="Email", required=True, border="1px solid gray", padding="4px 8px"),
rx.el.select(
rx.el.option("Weekly", value="weekly"),
rx.el.option("Monthly", value="monthly"),
name="plan",
border="1px solid gray",
padding="4px",
),
rx.el.label(rx.el.input(type="checkbox", name="news"), " Send news"),
rx.el.button("Sign up", type="submit", padding="4px 12px", border="1px solid gray", border_radius="6px"),
on_submit=HtmlFormSignupState.handle_submit,
reset_on_submit=True,
display="flex",
gap="8px",
flex_wrap="wrap",
align_items="center",
),
rx.el.ul(
rx.foreach(HtmlFormSignupState.submitted, lambda kv: rx.el.li(rx.el.code(kv[0]), " = ", kv[1])),
margin_top="10px",
),
)fieldset groups related inputs inside a border, and legend is the group's title. Give fieldset disabled=True and every input inside is disabled.
label connects to an input either by wrapping it or by putting the input's id in html_for. Clicking a connected label moves the cursor into the input.
rx.el.div(
rx.el.fieldset(
rx.el.legend("Delivery", font_weight="bold"),
rx.el.label(rx.el.input(type="radio", name="ship", value="pickup", default_checked=True), " Pickup"),
rx.el.label(rx.el.input(type="radio", name="ship", value="courier"), " Courier"),
border="1px solid gray",
border_radius="6px",
padding="8px 12px",
display="flex",
gap="12px",
),
rx.el.fieldset(
rx.el.legend("Gift wrap (sold out)", font_weight="bold"),
rx.el.label(rx.el.input(type="checkbox"), " Wrap it"),
rx.el.label("Message ", rx.el.input(id="htmlforms_gift_msg", border="1px solid gray")),
disabled=True,
border="1px solid gray",
border_radius="6px",
padding="8px 12px",
display="flex",
gap="12px",
opacity="0.6",
),
rx.el.label("Coupon", html_for="htmlforms_coupon", margin_right="8px"),
rx.el.input(id="htmlforms_coupon", placeholder="Click the label", border="1px solid gray", padding="2px 6px"),
display="flex",
flex_direction="column",
gap="10px",
align_items="start",
)option is an item to pick, and optgroup groups items under a label title. Items marked disabled can't be picked.
With value and on_change, the State holds the chosen value. Add multiple=True and size and you pick several items from an open list with Ctrl or Shift.
You picked: soup
class HtmlFormSelectState(rx.State):
dish: str = "soup"
@rx.event
def change_dish(self, value: str):
self.dish = value
rx.el.div(
rx.el.select(
rx.el.optgroup(
rx.el.option("Soup", value="soup"),
rx.el.option("Salad", value="salad"),
label="Starters",
),
rx.el.optgroup(
rx.el.option("Pasta", value="pasta"),
rx.el.option("Steak (sold out)", value="steak", disabled=True),
label="Mains",
),
value=HtmlFormSelectState.dish,
on_change=HtmlFormSelectState.change_dish,
border="1px solid gray",
padding="4px",
),
rx.el.p("You picked: ", rx.el.strong(HtmlFormSelectState.dish)),
rx.el.select(
rx.el.option("Red"),
rx.el.option("Green"),
rx.el.option("Blue"),
rx.el.option("Black"),
multiple=True,
size=3,
border="1px solid gray",
width="120px",
),
display="flex",
flex_direction="column",
gap="8px",
align_items="start",
)Reflex's rx.el.textarea has two props that HTML doesn't. With enter_key_submit=True, Enter submits the form and Shift+Enter starts a new line.
With auto_height=True, the height grows as the text gets longer (use it together with min_height). enter_key_submit can't be combined with on_key_down.
class HtmlFormChatState(rx.State):
messages: list[str] = []
@rx.event
def send(self, form_data: dict):
text = str(form_data.get("message", "")).strip()
if text:
self.messages = [*self.messages, text][-4:]
rx.el.div(
rx.el.ul(
rx.foreach(HtmlFormChatState.messages, lambda m: rx.el.li(m, white_space="pre-wrap")),
list_style_type="disc",
padding_left="1.5em",
),
rx.el.form(
rx.el.textarea(
name="message",
placeholder="Type and press Enter",
enter_key_submit=True,
auto_height=True,
max_length=200,
min_height="40px",
width="100%",
border="1px solid gray",
border_radius="6px",
padding="6px 8px",
),
on_submit=HtmlFormChatState.send,
reset_on_submit=True,
),
max_width="360px",
)output is a place to show a calculated result; put the ids of the inputs used in the calculation in html_for, separated by spaces.
meter is a measurement with a fixed range, such as disk usage, and progress is how far along a task is. Without a value, progress shows an "in progress" state.
+ =
class HtmlFormOutputState(rx.State):
a: int = 2
b: int = 3
@rx.event
def change_a(self, value: float):
self.a = int(value)
@rx.event
def change_b(self, value: float):
self.b = int(value)
@rx.var
def total(self) -> int:
return self.a + self.b
rx.el.div(
rx.el.p(
rx.el.input(id="htmlforms_a", type="number", value=HtmlFormOutputState.a, on_change=HtmlFormOutputState.change_a, width="60px", border="1px solid gray"),
" + ",
rx.el.input(id="htmlforms_b", type="number", value=HtmlFormOutputState.b, on_change=HtmlFormOutputState.change_b, width="60px", border="1px solid gray"),
" = ",
rx.el.output(HtmlFormOutputState.total, html_for="htmlforms_a htmlforms_b", name="total", font_weight="bold"),
),
rx.el.label("Disk ", rx.el.meter(value=0.7, low=0.5, high=0.8, optimum=0.2, width="160px")),
rx.el.label("Loading ", rx.el.progress(width="160px")),
display="flex",
flex_direction="column",
gap="8px",
)Drinks are grouped with optgroup, the quantity is a number input, the pickup method is radio buttons inside a fieldset, and the requests are a textarea. On submit, the State writes a summary into output.
class HtmlFormOrderState(rx.State):
summary: str = ""
@rx.event
def place_order(self, form_data: dict):
qty = form_data.get("qty") or "1"
note = str(form_data.get("note", "")).strip() or "none"
self.summary = f"{qty} x {form_data.get('drink')} ({form_data.get('size')}), {form_data.get('pickup')}. Note: {note}"
rx.el.div(
rx.el.form(
rx.el.fieldset(
rx.el.legend("Your order", font_weight="bold"),
rx.el.select(
rx.el.optgroup(rx.el.option("Latte"), rx.el.option("Mocha"), label="Coffee"),
rx.el.optgroup(rx.el.option("Green tea"), rx.el.option("Chai"), label="Tea"),
name="drink",
border="1px solid gray",
padding="4px",
),
rx.el.select(rx.el.option("Small"), rx.el.option("Large"), name="size", border="1px solid gray", padding="4px"),
rx.el.input(name="qty", type="number", min=1, max=9, default_value=1, width="60px", border="1px solid gray", padding="4px"),
display="flex",
gap="8px",
flex_wrap="wrap",
border="1px solid gray",
border_radius="6px",
padding="8px 12px",
),
rx.el.fieldset(
rx.el.legend("Pickup", font_weight="bold"),
rx.el.label(rx.el.input(type="radio", name="pickup", value="for here", default_checked=True), " For here"),
rx.el.label(rx.el.input(type="radio", name="pickup", value="to go"), " To go"),
display="flex",
gap="12px",
border="1px solid gray",
border_radius="6px",
padding="8px 12px",
),
rx.el.textarea(name="note", placeholder="Notes for the barista", rows=2, border="1px solid gray", border_radius="6px", padding="6px 8px"),
rx.el.button("Order", type="submit", padding="6px 14px", border="1px solid gray", border_radius="6px", background=rx.color("indigo", 3)),
on_submit=HtmlFormOrderState.place_order,
display="flex",
flex_direction="column",
gap="10px",
),
rx.el.output(HtmlFormOrderState.summary, display="block", margin_top="10px", font_weight="600"),
max_width="420px",
)Workshop notes
on_submit of rx.el.form receives a {name: value} dict. Give every input a name.
Group with fieldset + legend, and disable them all at once with fieldset's disabled. Connect label to an id with html_for.
select uses option and optgroup and is controlled with value + on_change. multiple and size allow several picks.
enter_key_submit and auto_height on textarea are convenience props added by Reflex.
meter is a measurement whose color changes with low, high and optimum; progress shows progress; output shows a calculated result.
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.