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
Build a newsletter sign-up field. It takes one line, an email address, and must refuse to submit when the format is wrong.
rx.input is a single-line text field. Use it for short values such as a search term, a name, an email address or a password. When you need several lines, use Text Area.
rx.input(placeholder="Search the docs")Change type and the same field becomes a password field, a date picker or a color picker. The other props control how it looks.
Try the props
placeholder
type
variant
size
color
radius
disabled
read_only
rx.input(placeholder="Search the docs", type="text", variant="surface", size="2", color_scheme="indigo", radius="medium", disabled=False, read_only=False)| Prop | Values | Default | Use |
|---|---|---|---|
placeholder | string | none | Dimmed hint shown while the field is empty |
type | "text" "password" "email" "number" "date" … | "text" | Kind of input, same as HTML <input type> |
value / default_value | string, number | none | Controlled value / value set only once at the start |
variant | "classic" "surface" "soft" | "surface" | Look |
size | "1" – "3" | "2" | Height and font size |
color_scheme, radius | Radix color name / "none" – "full" | theme value | Accent color and corners |
name, required | string / True | none | Name used on form submit; block submit while empty |
max_length, min_length, pattern | number / regex | none | Length and format checked by the browser |
min, max, step | number | none | Range and step for type="number", "date" and so on |
disabled, read_only, auto_focus | True / False | False | Disable, read-only, focus as soon as the page opens |
auto_complete | True / False | browser value | Browser autocomplete. The official docs also list strings like "off", but 0.10 only accepts a bool |
on_change | event | When the value changes. Passes the new value (a string) | |
on_blur, on_focus | event | When focus is lost / gained. on_blur passes the value too | |
on_key_down, on_key_up | event | When a key is pressed / released. Passes the key name |
on_blur fires once when focus leaves the field. Click outside or Tab away and the current value goes to the State. It fits when you don't need to call the server on every keystroke.
class InputBlurState(rx.State):
text: str = "Hello World!"
@rx.event
def set_text(self, value: str):
self.text = value
rx.vstack(
rx.heading(InputBlurState.text, size="5"),
rx.input(placeholder="Type, then click outside", on_blur=InputBlurState.set_text),
)Give value a State value and update it with on_change, and the field always matches the State. This is called a controlled input. When the server changes the value, the field follows.
When rx.input gets both value and on_change, it wraps itself in a debounced input (DebounceInput). While you type, the text shows up right away, and it is sent to the server once you pause for a moment (300 ms by default). That is why typing never stutters even though the State holds the value.
class InputControlledState(rx.State):
text: str = "Hello World!"
@rx.event
def set_text(self, value: str):
self.text = value
rx.vstack(
rx.heading(InputControlledState.text, size="5"),
rx.input(
placeholder="Type here",
value=InputControlledState.text,
on_change=InputControlledState.set_text,
),
)Using only on_change or on_blur, without value, gives an uncontrolled input. Set the initial value with default_value; after that the browser holds the value.
Put rx.input.slot in as a child and an icon or a short piece of text appears inside the field. With side="right" it sits on the right. A slot also takes its own color_scheme.
@example.com
rx.vstack(
rx.input(
rx.input.slot(rx.icon("search", size=16)),
placeholder="Search the docs",
),
rx.input(
rx.input.slot(rx.icon("at-sign", size=16)),
rx.input.slot(rx.text("@example.com", size="1"), side="right"),
placeholder="username",
),
)type takes the same values as HTML <input type>: "text" (the default), "password", "email", "number", "date", "time", "url", "color" and more, and the browser shows a matching input widget. The full list is in MDN's input documentation.
rx.vstack(
rx.input(placeholder="Username", type="text"),
rx.input(placeholder="Password", type="password"),
rx.input(placeholder="Email", type="email"),
rx.input(type="number", min=0, max=10, step=1, default_value="3"),
rx.input(type="date"),
rx.input(type="color", default_value="#3e63dd", width="80px"),
)An input inside a form needs a name to end up in the submitted data as name: value. With required=True the browser blocks submitting while the field is empty. The browser also checks pattern and max_length before submitting. The second field below only accepts exactly three digits.
Results:
{}class InputFormState(rx.State):
form_data: dict = {}
@rx.event
def handle_submit(self, form_data: dict):
self.form_data = form_data
rx.card(
rx.vstack(
rx.form.root(
rx.hstack(
rx.input(name="username", placeholder="Username", required=True),
rx.input(name="code", placeholder="3 digits", pattern="[0-9]{3}", max_length=3),
rx.button("Submit", type="submit"),
),
on_submit=InputFormState.handle_submit,
reset_on_submit=True,
),
rx.divider(),
rx.hstack(rx.text("Results:", weight="bold"), rx.badge(InputFormState.form_data.to_string())),
),
)Forms themselves are covered in detail on the Form page.
rx.set_value("id", value) sets the value of the input with that id directly. Use it to clear or fill a field without creating a State variable.
rx.hstack(
rx.input(id="input_erase_demo", default_value="Erase me"),
rx.button("Erase", on_click=rx.set_value("input_erase_demo", "")),
rx.button("Fill", on_click=rx.set_value("input_erase_demo", "Hello"), variant="soft"),
)on_key_down passes the key name ("Enter", "a" …) every time a key is pressed. If you want Enter to trigger something, it is usually simpler to put the input in a form.
Last key: -
class InputKeyState(rx.State):
last_key: str = "-"
@rx.event
def on_key(self, key: str):
self.last_key = key
rx.vstack(
rx.input(placeholder="Press any key", on_key_down=InputKeyState.on_key),
rx.text("Last key: ", rx.code(InputKeyState.last_key)),
)type="email" and required=True let the browser check the format first, and the form handles both Enter and the button. Only the three most recent addresses are shown.
class InputNewsletterState(rx.State):
emails: list[str] = []
@rx.event
def subscribe(self, form_data: dict):
email = form_data.get("email", "").strip()
if email and email not in self.emails:
self.emails = (self.emails + [email])[-3:]
rx.vstack(
rx.form(
rx.hstack(
rx.input(
rx.input.slot(rx.icon("mail", size=16)),
name="email",
type="email",
placeholder="you@example.com",
required=True,
flex="1",
),
rx.button("Subscribe", type="submit"),
width="100%",
),
on_submit=InputNewsletterState.subscribe,
reset_on_submit=True,
width="100%",
),
rx.foreach(InputNewsletterState.emails, lambda e: rx.badge(e, variant="soft")),
width="100%",
max_width="420px",
)Workshop notes
Create it with rx.input(placeholder=...) and pick the kind of input with type.
on_blur fires once when you leave; value + on_change makes a controlled input (debounced automatically).
Icons go in rx.input.slot; side="right" puts them on the right.
In a form, name becomes the key, and the browser checks required, pattern and max_length.
To change the value without State, use rx.set_value("id", value).
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.