
Reflex Dev
- 105 installs
- 2 repo stars
- Updated August 4, 2026
- silvainfm/claude-skills
reflex-dev is a Claude agent skill that guides full-stack web development with Reflex—a Python framework compiling to React and FastAPI—for developers building state-driven UIs without JavaScript.
About
reflex-dev is a silvainfm/claude-skills guide for building full-stack web applications with Reflex, a Python framework compiling to a React frontend and FastAPI backend over WebSockets. The skill covers rx.State classes with JSON-serializable variables, @rx.var computed fields, 60+ built-in components (vstack, hstack, data_table, recharts), and event handlers as the only state mutation path—including async handlers for API and database calls. It documents project structure (rxconfig.py, assets/, .web/ generated output), routing with rx.page and rx.redirect, form handling, file uploads, DuckDB/Polars integration, and responsive styling props. Development commands include reflex init, reflex run on localhost:3000, reflex export, and reflex db migrate. Best practices emphasize substate organization, reusable component functions, async I/O in handlers, and strict type hints. Reach for reflex-dev when creating, modifying, or debugging Reflex apps instead of writing raw React or FastAPI boilerplate from scratch.
- Documents Reflex architecture: React frontend, FastAPI backend, WebSocket state sync
- Covers 60+ built-in Reflex components and rx.State event handler patterns
- Includes form handling, file upload, DuckDB/Polars DB integration examples
- Dev workflow: reflex init, reflex run (localhost:3000), reflex export, db migrate
- SKILL.md spans state management, routing, styling, and best-practice checklists
Reflex Dev by the numbers
- 105 all-time installs (skills.sh)
- Ranked #102 of 290 Python skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/silvainfm/claude-skills --skill reflex-devAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 105 |
|---|---|
| repo stars | ★ 2 |
| Last updated | August 4, 2026 |
| Repository | silvainfm/claude-skills ↗ |
How do you build a full-stack web app in Python with Reflex?
Apply reflex-dev patterns to build Reflex rx.State apps with 60+ components, FastAPI backends, and WebSocket-driven event handlers in Python.
Who is it for?
Python developers building Reflex full-stack apps who need state management, component, routing, and database integration patterns in one skill.
Skip if: Teams building React/Next.js frontends directly in TypeScript or SPAs that do not use the Reflex Python compilation model.
When should I use this skill?
User creates, modifies, or debugs Reflex apps covering state management, event handlers, components, routing, styling, or data integration.
What you get
Reflex app with rx.State classes, routed pages, styled components, and working event handlers compiled to React plus FastAPI.
- Reflex app source files
- routed pages
- rx.State event handlers
By the numbers
- Documents 60+ built-in Reflex UI components
- Dev server runs at http://localhost:3000 via reflex run
Files
Reflex Development
Overview
Reflex is a full-stack Python framework for building web applications without writing JavaScript. Apps compile to a React frontend and FastAPI backend, with state management and event handlers running entirely in Python.
Architecture:
- Frontend: Compiled to React (JavaScript) for UI rendering
- Backend: FastAPI server running Python event handlers
- Communication: WebSockets for real-time state updates
- State: Server-side Python state synchronized to frontend
Core Concepts
State Management
State is a Python class that holds all mutable data and event handlers. All state variables must be JSON-serializable.
import reflex as rx
class AppState(rx.State):
# State variables (any JSON-serializable type)
count: int = 0
items: list[str] = []
user_name: str = ""
# Event handlers - the ONLY way to modify state
def increment(self):
self.count += 1
def add_item(self, item: str):
self.items.append(item)
# Computed vars (derived state)
@rx.var
def item_count(self) -> int:
return len(self.items)Key Rules:
- State vars MUST be JSON-serializable (int, str, list, dict, bool, float)
- Only event handlers can modify state
- Use
@rx.vardecorator for computed/derived values - State is per-user session (isolated between users)
Components
Components are UI building blocks. Reflex provides 60+ built-in components.
import reflex as rx
def header() -> rx.Component:
return rx.heading("My App", size="lg")
def counter_component(state: AppState) -> rx.Component:
return rx.vstack(
rx.text(f"Count: {state.count}"),
rx.button("Increment", on_click=state.increment),
spacing="4"
)Common Components:
- Layout:
rx.vstack,rx.hstack,rx.box,rx.container - Text:
rx.heading,rx.text,rx.code - Input:
rx.input,rx.text_area,rx.select,rx.checkbox - Interactive:
rx.button,rx.link,rx.icon_button - Data:
rx.table,rx.data_table,rx.list - Charts:
rx.recharts.line_chart,rx.recharts.bar_chart, etc.
Event Handlers
Event handlers respond to user interactions and are the ONLY way to modify state.
class FormState(rx.State):
form_data: dict[str, str] = {}
# Simple event handler
def handle_submit(self):
print(f"Submitted: {self.form_data}")
# Event handler with argument
def update_field(self, field: str, value: str):
self.form_data[field] = value
# Async event handler (for API calls, DB queries)
async def fetch_data(self):
# Can use any Python library
import httpx
async with httpx.AsyncClient() as client:
response = await client.get("https://api.example.com/data")
self.data = response.json()Event Triggers (connect components to handlers):
on_click: Button clickson_change: Input field changeson_submit: Form submissionson_mount: Component first renderson_blur,on_focus: Input focus events
Project Structure
Standard Reflex app structure:
my_app/
├── my_app/
│ ├── __init__.py # Empty
│ └── my_app.py # Main app file (State + pages)
├── assets/ # Static files (images, fonts, etc.)
├── .web/ # Auto-generated frontend (don't edit)
├── rxconfig.py # Reflex configuration
└── requirements.txt # Python dependenciesMain App File Pattern
import reflex as rx
# 1. Define State
class State(rx.State):
count: int = 0
def increment(self):
self.count += 1
# 2. Define Pages
def index() -> rx.Component:
return rx.container(
rx.heading("Welcome"),
rx.button("Click", on_click=State.increment),
rx.text(f"Count: {State.count}")
)
def about() -> rx.Component:
return rx.container(
rx.heading("About"),
rx.link("Home", href="/")
)
# 3. Create App and Add Routes
app = rx.App()
app.add_page(index, route="/")
app.add_page(about, route="/about")Common Patterns
Form Handling
class FormState(rx.State):
name: str = ""
email: str = ""
def handle_submit(self, form_data: dict):
self.name = form_data.get("name", "")
self.email = form_data.get("email", "")
def form_page():
return rx.form(
rx.vstack(
rx.input(name="name", placeholder="Name"),
rx.input(name="email", placeholder="Email"),
rx.button("Submit", type="submit"),
),
on_submit=FormState.handle_submit,
)Data Tables
class DataState(rx.State):
data: list[dict] = [
{"id": 1, "name": "Alice", "age": 25},
{"id": 2, "name": "Bob", "age": 30},
]
def data_table_page():
return rx.data_table(
data=DataState.data,
columns=["id", "name", "age"],
sort=True,
search=True,
pagination=True,
)File Upload
class UploadState(rx.State):
async def handle_upload(self, files: list[rx.UploadFile]):
for file in files:
upload_data = await file.read()
# Process file data
outfile = f"./uploads/{file.filename}"
with open(outfile, "wb") as f:
f.write(upload_data)
def upload_page():
return rx.vstack(
rx.upload(
rx.button("Select Files"),
id="upload1",
),
rx.button(
"Upload",
on_click=UploadState.handle_upload(rx.upload_files(upload_id="upload1"))
),
)Database Integration (with DuckDB)
import duckdb
import polars as pl
class DBState(rx.State):
records: list[dict] = []
async def load_data(self):
# Use existing database connection
conn = duckdb.connect("data/mydb.duckdb")
df = conn.execute("SELECT * FROM mytable").pl()
self.records = df.to_dicts()
conn.close()
async def insert_record(self, data: dict):
conn = duckdb.connect("data/mydb.duckdb")
conn.execute(
"INSERT INTO mytable (name, value) VALUES (?, ?)",
[data["name"], data["value"]]
)
conn.close()
await self.load_data() # RefreshStyling & Layout
Inline Styling
rx.box(
rx.text("Styled text"),
bg="#1a5f9e",
color="white",
padding="4",
border_radius="md",
)Responsive Layout
rx.container(
rx.responsive_grid(
rx.box("Item 1", bg="blue"),
rx.box("Item 2", bg="green"),
rx.box("Item 3", bg="red"),
columns=[1, 2, 3], # 1 col mobile, 2 tablet, 3 desktop
spacing="4",
),
max_width="1200px",
)Common Style Props
- Layout:
width,height,padding,margin,display - Colors:
bg(background),color(text) - Typography:
font_size,font_weight,text_align - Borders:
border,border_radius,border_color - Spacing:
spacing(for stacks),gap
Routing
Multiple Pages
app = rx.App()
# Route with parameters
@rx.page(route="/user/[id]")
def user_page() -> rx.Component:
return rx.text(f"User ID: {State.router.page.params.get('id')}")
# Simple routes
app.add_page(index, route="/")
app.add_page(about, route="/about")Navigation
# Links
rx.link("Go to About", href="/about")
# Programmatic navigation
def go_home(self):
return rx.redirect("/")Development Workflow
Initialize New App
pip install reflex
reflex initRun Development Server
reflex runApp runs on http://localhost:3000 with auto-reload.
Common Commands
reflex run # Start dev server
reflex export # Build production bundle
reflex db init # Initialize database (if using Reflex DB)
reflex db migrate # Run migrationsBest Practices
1. State Organization: Split large states into substates
class AuthState(rx.State):
user: str = ""
class DataState(rx.State):
items: list = []2. Component Reusability: Create reusable component functions
def card(title: str, content: str) -> rx.Component:
return rx.box(
rx.heading(title, size="md"),
rx.text(content),
padding="4",
border="1px solid #ddd",
)3. Event Handler Performance: Use async for I/O operations
async def fetch_data(self):
# Async I/O won't block other users
self.data = await some_api_call()4. Type Hints: Always type-hint state vars and event handlers
count: int = 0
items: list[str] = []
def update_count(self, value: int) -> None:
self.count = valueReferences
Documentation
- Official Docs: https://reflex.dev/docs/getting-started/introduction/
- Component Library: https://reflex.dev/docs/library
- Tutorials: https://reflex.dev/docs/getting-started/tutorial/
Example Apps
See examples/ directory for complete working examples:
- Simple counter app
- Data table with CRUD operations
- Form with validation
- File upload and processing
Common Patterns Reference
See references/patterns.md for detailed examples of:
- Authentication flows
- Real-time updates
- Complex form validation
- Multi-step workflows
- Data visualization with charts
"""
Simple Counter App - Demonstrates basic Reflex concepts
Shows: State management, event handlers, components, and styling
"""
import reflex as rx
class CounterState(rx.State):
"""State for the counter application."""
count: int = 0
step: int = 1
def increment(self):
"""Increment counter by step value."""
self.count += self.step
def decrement(self):
"""Decrement counter by step value."""
self.count -= self.step
def reset(self):
"""Reset counter to zero."""
self.count = 0
def set_step(self, value: str):
"""Update the step value from input."""
try:
self.step = int(value) if value else 1
except ValueError:
self.step = 1
def index() -> rx.Component:
"""Main page of the counter app."""
return rx.container(
rx.vstack(
rx.heading("Counter App", size="2xl", mb="4"),
# Display current count
rx.box(
rx.text(
CounterState.count,
font_size="6xl",
font_weight="bold",
color="blue.600",
),
bg="gray.100",
padding="8",
border_radius="lg",
text_align="center",
mb="6",
),
# Control buttons
rx.hstack(
rx.button(
"- Decrement",
on_click=CounterState.decrement,
color_scheme="red",
size="lg",
),
rx.button(
"Reset",
on_click=CounterState.reset,
color_scheme="gray",
size="lg",
),
rx.button(
"+ Increment",
on_click=CounterState.increment,
color_scheme="green",
size="lg",
),
spacing="4",
mb="6",
),
# Step size control
rx.box(
rx.text("Step Size:", font_weight="bold", mb="2"),
rx.hstack(
rx.input(
value=CounterState.step.to_string(),
on_change=CounterState.set_step,
type="number",
width="100px",
),
rx.text(f"(counting by {CounterState.step})"),
spacing="2",
),
padding="4",
border="1px solid #e2e8f0",
border_radius="md",
),
spacing="6",
align="center",
padding_y="20",
),
max_width="600px",
center_content=True,
)
# Create and configure the app
app = rx.App()
app.add_page(index)
"""
Data Table with CRUD Operations
Demonstrates: Data tables, forms, CRUD operations, state management with lists
"""
import reflex as rx
from datetime import datetime
class DataTableState(rx.State):
"""State for managing a data table with CRUD operations."""
# Data storage
items: list[dict[str, str]] = [
{"id": "1", "name": "Alice Smith", "email": "alice@example.com", "role": "Admin"},
{"id": "2", "name": "Bob Jones", "email": "bob@example.com", "role": "User"},
{"id": "3", "name": "Carol White", "email": "carol@example.com", "role": "User"},
]
# Form fields
form_id: str = ""
form_name: str = ""
form_email: str = ""
form_role: str = "User"
# UI state
editing_id: str = ""
show_form: bool = False
@rx.var
def next_id(self) -> str:
"""Generate next available ID."""
if not self.items:
return "1"
max_id = max(int(item["id"]) for item in self.items)
return str(max_id + 1)
def toggle_form(self):
"""Show/hide the form."""
self.show_form = not self.show_form
if not self.show_form:
self.clear_form()
def clear_form(self):
"""Reset all form fields."""
self.form_id = ""
self.form_name = ""
self.form_email = ""
self.form_role = "User"
self.editing_id = ""
def add_item(self):
"""Add a new item to the table."""
if self.form_name and self.form_email:
new_item = {
"id": self.next_id,
"name": self.form_name,
"email": self.form_email,
"role": self.form_role,
}
self.items.append(new_item)
self.clear_form()
self.show_form = False
def edit_item(self, item_id: str):
"""Load item data into form for editing."""
for item in self.items:
if item["id"] == item_id:
self.form_name = item["name"]
self.form_email = item["email"]
self.form_role = item["role"]
self.editing_id = item_id
self.show_form = True
break
def update_item(self):
"""Update an existing item."""
for i, item in enumerate(self.items):
if item["id"] == self.editing_id:
self.items[i] = {
"id": self.editing_id,
"name": self.form_name,
"email": self.form_email,
"role": self.form_role,
}
break
self.clear_form()
self.show_form = False
def delete_item(self, item_id: str):
"""Delete an item from the table."""
self.items = [item for item in self.items if item["id"] != item_id]
def item_form() -> rx.Component:
"""Form for adding/editing items."""
return rx.box(
rx.vstack(
rx.heading(
rx.cond(
DataTableState.editing_id != "",
"Edit Item",
"Add New Item"
),
size="lg",
mb="4",
),
rx.input(
placeholder="Name",
value=DataTableState.form_name,
on_change=DataTableState.set_form_name,
width="100%",
),
rx.input(
placeholder="Email",
value=DataTableState.form_email,
on_change=DataTableState.set_form_email,
type="email",
width="100%",
),
rx.select(
["Admin", "User", "Guest"],
value=DataTableState.form_role,
on_change=DataTableState.set_form_role,
width="100%",
),
rx.hstack(
rx.button(
"Cancel",
on_click=DataTableState.toggle_form,
color_scheme="gray",
),
rx.button(
rx.cond(
DataTableState.editing_id != "",
"Update",
"Add"
),
on_click=rx.cond(
DataTableState.editing_id != "",
DataTableState.update_item,
DataTableState.add_item,
),
color_scheme="blue",
),
spacing="2",
width="100%",
justify="end",
),
spacing="4",
width="100%",
),
bg="white",
padding="6",
border_radius="md",
border="1px solid #e2e8f0",
mb="6",
)
def data_row(item: dict) -> rx.Component:
"""Render a single data row."""
return rx.tr(
rx.td(item["name"]),
rx.td(item["email"]),
rx.td(item["role"]),
rx.td(
rx.hstack(
rx.button(
"Edit",
on_click=lambda: DataTableState.edit_item(item["id"]),
size="sm",
color_scheme="blue",
),
rx.button(
"Delete",
on_click=lambda: DataTableState.delete_item(item["id"]),
size="sm",
color_scheme="red",
),
spacing="2",
)
),
)
def index() -> rx.Component:
"""Main page with data table."""
return rx.container(
rx.vstack(
rx.heading("User Management", size="2xl", mb="6"),
# Add button
rx.button(
"+ Add New User",
on_click=DataTableState.toggle_form,
color_scheme="green",
mb="4",
),
# Form (conditional)
rx.cond(
DataTableState.show_form,
item_form(),
rx.box(),
),
# Data table
rx.table.root(
rx.table.header(
rx.table.row(
rx.table.column_header_cell("Name"),
rx.table.column_header_cell("Email"),
rx.table.column_header_cell("Role"),
rx.table.column_header_cell("Actions"),
),
),
rx.table.body(
rx.foreach(DataTableState.items, data_row),
),
variant="surface",
width="100%",
),
spacing="4",
width="100%",
padding_y="8",
),
max_width="900px",
)
app = rx.App()
app.add_page(index)
"""
File Upload and Processing - Demonstrates file handling in Reflex
Shows: File uploads, async processing, file type validation, progress feedback
"""
import reflex as rx
import asyncio
from pathlib import Path
class FileUploadState(rx.State):
"""State for file upload functionality."""
# Upload state
uploaded_files: list[dict[str, str]] = []
upload_progress: str = ""
is_uploading: bool = False
# Allowed file types
allowed_extensions: list[str] = [".txt", ".csv", ".json", ".pdf", ".png", ".jpg"]
@rx.var
def file_count(self) -> int:
"""Get count of uploaded files."""
return len(self.uploaded_files)
@rx.var
def total_size(self) -> str:
"""Calculate total size of uploaded files."""
total_bytes = sum(int(f.get("size", 0)) for f in self.uploaded_files)
if total_bytes < 1024:
return f"{total_bytes} B"
elif total_bytes < 1024 * 1024:
return f"{total_bytes / 1024:.2f} KB"
else:
return f"{total_bytes / (1024 * 1024):.2f} MB"
async def handle_upload(self, files: list[rx.UploadFile]):
"""Process uploaded files."""
self.is_uploading = True
self.upload_progress = "Processing files..."
# Create uploads directory if it doesn't exist
upload_dir = Path("./uploads")
upload_dir.mkdir(exist_ok=True)
for i, file in enumerate(files):
# Update progress
self.upload_progress = f"Processing {i+1}/{len(files)}: {file.filename}"
# Validate file type
file_ext = Path(file.filename).suffix.lower()
if file_ext not in self.allowed_extensions:
self.upload_progress = f"Skipped {file.filename}: Invalid file type"
await asyncio.sleep(1)
continue
# Read file data
upload_data = await file.read()
file_size = len(upload_data)
# Save file
outfile = upload_dir / file.filename
with open(outfile, "wb") as f:
f.write(upload_data)
# Add to uploaded files list
self.uploaded_files.append({
"name": file.filename,
"size": str(file_size),
"type": file_ext,
"path": str(outfile),
})
# Simulate processing time
await asyncio.sleep(0.5)
self.upload_progress = f"Successfully uploaded {len(files)} file(s)"
self.is_uploading = False
# Clear progress after 3 seconds
await asyncio.sleep(3)
self.upload_progress = ""
def clear_file(self, filename: str):
"""Remove a file from the list."""
self.uploaded_files = [
f for f in self.uploaded_files if f["name"] != filename
]
def clear_all(self):
"""Clear all uploaded files."""
self.uploaded_files = []
self.upload_progress = ""
def file_item(file: dict) -> rx.Component:
"""Render a single uploaded file."""
return rx.box(
rx.hstack(
rx.vstack(
rx.text(file["name"], font_weight="bold"),
rx.hstack(
rx.badge(file["type"], color_scheme="blue"),
rx.text(
f"{int(file['size']) / 1024:.2f} KB",
color="gray.600",
font_size="sm",
),
spacing="2",
),
align="start",
spacing="1",
),
rx.spacer(),
rx.button(
"Remove",
on_click=lambda: FileUploadState.clear_file(file["name"]),
size="sm",
color_scheme="red",
variant="outline",
),
width="100%",
align="center",
),
padding="4",
border="1px solid #e2e8f0",
border_radius="md",
bg="white",
width="100%",
)
def index() -> rx.Component:
"""Main page with file upload."""
return rx.container(
rx.vstack(
rx.heading("File Upload Manager", size="2xl", mb="6"),
# Upload area
rx.box(
rx.vstack(
rx.icon("upload", size=48, color="gray.400"),
rx.heading("Upload Files", size="lg"),
rx.text(
f"Allowed types: {', '.join(FileUploadState.allowed_extensions)}",
color="gray.600",
font_size="sm",
),
rx.upload(
rx.button(
"Select Files",
color_scheme="blue",
size="lg",
),
id="file_upload",
multiple=True,
accept={
"text/plain": [".txt"],
"text/csv": [".csv"],
"application/json": [".json"],
"application/pdf": [".pdf"],
"image/png": [".png"],
"image/jpeg": [".jpg"],
},
),
rx.button(
"Upload Selected Files",
on_click=FileUploadState.handle_upload(
rx.upload_files(upload_id="file_upload")
),
color_scheme="green",
size="lg",
disabled=FileUploadState.is_uploading,
),
# Progress message
rx.cond(
FileUploadState.upload_progress != "",
rx.text(
FileUploadState.upload_progress,
color="blue.600",
font_weight="bold",
),
rx.box(),
),
spacing="4",
align="center",
),
padding="8",
border="2px dashed #cbd5e0",
border_radius="lg",
bg="gray.50",
width="100%",
text_align="center",
),
# Statistics
rx.cond(
FileUploadState.file_count > 0,
rx.box(
rx.hstack(
rx.stat(
rx.stat_label("Files Uploaded"),
rx.stat_number(FileUploadState.file_count),
),
rx.stat(
rx.stat_label("Total Size"),
rx.stat_number(FileUploadState.total_size),
),
rx.button(
"Clear All",
on_click=FileUploadState.clear_all,
color_scheme="red",
variant="outline",
),
spacing="8",
width="100%",
justify="space-between",
align="center",
),
padding="6",
bg="blue.50",
border_radius="md",
width="100%",
),
rx.box(),
),
# Uploaded files list
rx.cond(
FileUploadState.file_count > 0,
rx.vstack(
rx.heading("Uploaded Files", size="lg", mb="2"),
rx.foreach(FileUploadState.uploaded_files, file_item),
spacing="3",
width="100%",
),
rx.box(),
),
spacing="6",
width="100%",
padding_y="8",
),
max_width="800px",
)
app = rx.App()
app.add_page(index)
"""
Form with Validation - Demonstrates form handling and validation
Shows: Form inputs, validation logic, error messages, conditional rendering
"""
import reflex as rx
import re
class FormState(rx.State):
"""State for form with validation."""
# Form fields
username: str = ""
email: str = ""
password: str = ""
confirm_password: str = ""
agree_terms: bool = False
# Validation errors
username_error: str = ""
email_error: str = ""
password_error: str = ""
confirm_password_error: str = ""
# Submission state
submitted: bool = False
def validate_username(self):
"""Validate username field."""
if len(self.username) < 3:
self.username_error = "Username must be at least 3 characters"
elif len(self.username) > 20:
self.username_error = "Username must be less than 20 characters"
elif not re.match("^[a-zA-Z0-9_]+$", self.username):
self.username_error = "Username can only contain letters, numbers, and underscores"
else:
self.username_error = ""
def validate_email(self):
"""Validate email field."""
email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
if not self.email:
self.email_error = "Email is required"
elif not re.match(email_pattern, self.email):
self.email_error = "Please enter a valid email address"
else:
self.email_error = ""
def validate_password(self):
"""Validate password field."""
if len(self.password) < 8:
self.password_error = "Password must be at least 8 characters"
elif not re.search(r"[A-Z]", self.password):
self.password_error = "Password must contain at least one uppercase letter"
elif not re.search(r"[a-z]", self.password):
self.password_error = "Password must contain at least one lowercase letter"
elif not re.search(r"[0-9]", self.password):
self.password_error = "Password must contain at least one number"
else:
self.password_error = ""
def validate_confirm_password(self):
"""Validate password confirmation."""
if self.confirm_password != self.password:
self.confirm_password_error = "Passwords do not match"
else:
self.confirm_password_error = ""
@rx.var
def is_valid(self) -> bool:
"""Check if form is valid."""
return (
self.username != "" and
self.email != "" and
self.password != "" and
self.confirm_password != "" and
self.agree_terms and
self.username_error == "" and
self.email_error == "" and
self.password_error == "" and
self.confirm_password_error == ""
)
def handle_submit(self):
"""Handle form submission."""
# Run all validations
self.validate_username()
self.validate_email()
self.validate_password()
self.validate_confirm_password()
# Submit if valid
if self.is_valid:
self.submitted = True
# In real app: save to database, send to API, etc.
def reset_form(self):
"""Reset form to initial state."""
self.username = ""
self.email = ""
self.password = ""
self.confirm_password = ""
self.agree_terms = False
self.username_error = ""
self.email_error = ""
self.password_error = ""
self.confirm_password_error = ""
self.submitted = False
def form_field(
label: str,
value: str,
on_change,
on_blur,
error: str,
field_type: str = "text",
placeholder: str = "",
) -> rx.Component:
"""Reusable form field with validation."""
return rx.vstack(
rx.text(label, font_weight="bold", mb="1"),
rx.input(
value=value,
on_change=on_change,
on_blur=on_blur,
type=field_type,
placeholder=placeholder,
width="100%",
border_color=rx.cond(error != "", "red.500", "gray.300"),
),
rx.cond(
error != "",
rx.text(error, color="red.500", font_size="sm"),
rx.box(),
),
align="start",
width="100%",
spacing="1",
)
def registration_form() -> rx.Component:
"""Registration form with validation."""
return rx.box(
rx.vstack(
rx.heading("Create Account", size="2xl", mb="6"),
# Username field
form_field(
label="Username",
value=FormState.username,
on_change=FormState.set_username,
on_blur=FormState.validate_username,
error=FormState.username_error,
placeholder="Choose a username",
),
# Email field
form_field(
label="Email",
value=FormState.email,
on_change=FormState.set_email,
on_blur=FormState.validate_email,
error=FormState.email_error,
field_type="email",
placeholder="your.email@example.com",
),
# Password field
form_field(
label="Password",
value=FormState.password,
on_change=FormState.set_password,
on_blur=FormState.validate_password,
error=FormState.password_error,
field_type="password",
placeholder="Min. 8 chars, 1 uppercase, 1 number",
),
# Confirm password field
form_field(
label="Confirm Password",
value=FormState.confirm_password,
on_change=FormState.set_confirm_password,
on_blur=FormState.validate_confirm_password,
error=FormState.confirm_password_error,
field_type="password",
placeholder="Re-enter your password",
),
# Terms checkbox
rx.checkbox(
"I agree to the terms and conditions",
checked=FormState.agree_terms,
on_change=FormState.set_agree_terms,
),
# Submit button
rx.button(
"Create Account",
on_click=FormState.handle_submit,
color_scheme="blue",
size="lg",
width="100%",
disabled=~FormState.is_valid,
),
spacing="4",
width="100%",
),
max_width="500px",
padding="8",
bg="white",
border_radius="lg",
box_shadow="lg",
)
def success_message() -> rx.Component:
"""Success message after submission."""
return rx.box(
rx.vstack(
rx.icon("check_circle", size=64, color="green.500"),
rx.heading("Account Created!", size="2xl", color="green.700"),
rx.text(f"Welcome, {FormState.username}!", font_size="lg"),
rx.text(f"Confirmation email sent to {FormState.email}", color="gray.600"),
rx.button(
"Create Another Account",
on_click=FormState.reset_form,
color_scheme="blue",
mt="4",
),
spacing="4",
align="center",
),
max_width="500px",
padding="8",
bg="white",
border_radius="lg",
box_shadow="lg",
text_align="center",
)
def index() -> rx.Component:
"""Main page."""
return rx.container(
rx.center(
rx.cond(
FormState.submitted,
success_message(),
registration_form(),
),
min_height="100vh",
padding_y="8",
),
bg="gray.50",
)
app = rx.App()
app.add_page(index)
Reflex Examples
Complete, working Reflex applications demonstrating common patterns and features.
Running Examples
Each example is a standalone Reflex app. To run:
1. Install Reflex:
pip install reflex2. Navigate to this directory and copy the example:
cp counter_app.py my_app.py3. Initialize and run:
reflex init
reflex run4. Open browser to http://localhost:3000
Available Examples
1. Counter App (counter_app.py)
Demonstrates:
- Basic state management
- Event handlers
- Component composition
- Inline styling
Features:
- Increment/decrement counter
- Configurable step size
- Reset functionality
Best For: Learning Reflex basics, understanding state and event handlers
---
2. Data Table with CRUD (data_table_crud.py)
Demonstrates:
- List state management
- CRUD operations (Create, Read, Update, Delete)
- Forms with conditional rendering
- Data tables
- Computed vars
Features:
- View user data in table
- Add new users
- Edit existing users
- Delete users
- Form validation
Best For: Building admin panels, data management interfaces
---
3. Form with Validation (form_validation.py)
Demonstrates:
- Complex form handling
- Real-time validation
- Error messages
- Conditional rendering
- Component reusability
Features:
- Username validation (length, characters)
- Email validation (format)
- Password validation (strength requirements)
- Password confirmation matching
- Terms agreement checkbox
- Success/error states
Best For: User registration, complex forms, input validation
---
4. File Upload (file_upload.py)
Demonstrates:
- File upload handling
- Async operations
- Progress feedback
- File type validation
- File management
Features:
- Multi-file upload
- File type restrictions
- Upload progress
- File list with metadata
- File deletion
- Size calculations
Best For: Document management, media uploads, data import
Customization Tips
Styling
All examples use inline styling. You can customize colors, spacing, and layout by modifying style props:
rx.box(
bg="#your-color",
padding="4",
border_radius="md",
)State Extension
Add more state variables and event handlers to extend functionality:
class MyState(rx.State):
new_feature: str = ""
def new_handler(self):
# Your logic
passComponent Reuse
Extract common patterns into reusable functions:
def custom_button(text: str, handler) -> rx.Component:
return rx.button(
text,
on_click=handler,
color_scheme="blue",
size="lg",
)Integration with Existing Projects
These examples can be integrated into the Monaco Payroll System:
1. Data Table CRUD - Adapt for employee management, payroll record editing 2. Form Validation - Use for employee data entry, validation rules 3. File Upload - Integrate with Excel import/export functionality 4. Counter App - Adapt for statistics, counters in dashboard
Additional Resources
- Reflex Documentation: https://reflex.dev/docs
- Component Library: https://reflex.dev/docs/library
- Community Examples: https://github.com/reflex-dev/reflex-examples
- Monaco Payroll Integration: See
references/patterns.mdfor DuckDB/Polars patterns
Common Reflex Patterns
This reference provides detailed examples of common patterns in Reflex applications.
Authentication Flows
Basic Authentication with Session State
import reflex as rx
import bcrypt
class AuthState(rx.State):
"""Authentication state management."""
username: str = ""
is_authenticated: bool = False
error_message: str = ""
def login(self, form_data: dict):
"""Handle login with username/password."""
username = form_data.get("username", "")
password = form_data.get("password", "")
# In production: check against database
# For demo: simple hardcoded check
if username == "admin" and password == "password":
self.username = username
self.is_authenticated = True
return rx.redirect("/dashboard")
else:
self.error_message = "Invalid credentials"
def logout(self):
"""Clear session and redirect to login."""
self.username = ""
self.is_authenticated = False
return rx.redirect("/login")
def check_auth(self):
"""Redirect to login if not authenticated."""
if not self.is_authenticated:
return rx.redirect("/login")
def login_page():
return rx.container(
rx.form(
rx.vstack(
rx.heading("Login"),
rx.input(name="username", placeholder="Username"),
rx.input(name="password", type="password", placeholder="Password"),
rx.cond(
AuthState.error_message != "",
rx.text(AuthState.error_message, color="red"),
rx.box(),
),
rx.button("Login", type="submit"),
),
on_submit=AuthState.login,
),
)
def protected_page():
return rx.container(
rx.vstack(
rx.heading(f"Welcome, {AuthState.username}!"),
rx.button("Logout", on_click=AuthState.logout),
),
on_mount=AuthState.check_auth,
)Protected Routes Pattern
def require_auth(page_func):
"""Decorator to protect routes."""
def wrapper():
return rx.cond(
AuthState.is_authenticated,
page_func(),
rx.redirect("/login"),
)
return wrapper
@require_auth
def admin_page():
return rx.container(
rx.heading("Admin Dashboard"),
# Admin content
)Real-Time Updates
WebSocket State Updates
import reflex as rx
import asyncio
class LiveDataState(rx.State):
"""Real-time data updates."""
current_value: float = 0.0
data_points: list[float] = []
is_streaming: bool = False
async def start_streaming(self):
"""Start streaming data updates."""
self.is_streaming = True
while self.is_streaming:
# Simulate real-time data (replace with actual data source)
import random
self.current_value = random.uniform(0, 100)
self.data_points.append(self.current_value)
# Keep only last 50 points
if len(self.data_points) > 50:
self.data_points = self.data_points[-50:]
# Update every second
await asyncio.sleep(1)
def stop_streaming(self):
"""Stop streaming."""
self.is_streaming = False
def live_dashboard():
return rx.vstack(
rx.heading(f"Current Value: {LiveDataState.current_value:.2f}"),
rx.recharts.line_chart(
rx.recharts.line(data_key="value"),
data=[{"value": v} for v in LiveDataState.data_points],
width="100%",
height=300,
),
rx.button(
"Start Streaming",
on_click=LiveDataState.start_streaming,
disabled=LiveDataState.is_streaming,
),
rx.button(
"Stop Streaming",
on_click=LiveDataState.stop_streaming,
disabled=~LiveDataState.is_streaming,
),
)Auto-Refresh Pattern
class RefreshState(rx.State):
"""Auto-refresh data at intervals."""
data: list[dict] = []
last_updated: str = ""
async def auto_refresh(self):
"""Refresh data every N seconds."""
while True:
await self.load_data()
await asyncio.sleep(30) # Refresh every 30 seconds
async def load_data(self):
"""Load data from source."""
# Replace with actual data loading
from datetime import datetime
self.data = await fetch_from_api()
self.last_updated = datetime.now().strftime("%H:%M:%S")
def auto_refresh_page():
return rx.container(
rx.vstack(
rx.text(f"Last updated: {RefreshState.last_updated}"),
# Display data
),
on_mount=RefreshState.auto_refresh,
)Complex Form Validation
Multi-Step Form
class MultiStepFormState(rx.State):
"""Multi-step form with validation."""
# Current step
current_step: int = 1
# Step 1: Personal info
first_name: str = ""
last_name: str = ""
email: str = ""
# Step 2: Address
street: str = ""
city: str = ""
zip_code: str = ""
# Step 3: Preferences
newsletter: bool = False
notifications: bool = True
@rx.var
def step1_valid(self) -> bool:
"""Check if step 1 is complete."""
return (
len(self.first_name) > 0 and
len(self.last_name) > 0 and
"@" in self.email
)
@rx.var
def step2_valid(self) -> bool:
"""Check if step 2 is complete."""
return (
len(self.street) > 0 and
len(self.city) > 0 and
len(self.zip_code) == 5
)
def next_step(self):
"""Advance to next step."""
if self.current_step < 3:
self.current_step += 1
def prev_step(self):
"""Go back one step."""
if self.current_step > 1:
self.current_step -= 1
def submit_form(self):
"""Submit complete form."""
# Process form data
print(f"Submitted: {self.first_name} {self.last_name}")
# Reset form
self.current_step = 1
def step1():
return rx.vstack(
rx.heading("Step 1: Personal Information"),
rx.input(
placeholder="First Name",
value=MultiStepFormState.first_name,
on_change=MultiStepFormState.set_first_name,
),
rx.input(
placeholder="Last Name",
value=MultiStepFormState.last_name,
on_change=MultiStepFormState.set_last_name,
),
rx.input(
placeholder="Email",
type="email",
value=MultiStepFormState.email,
on_change=MultiStepFormState.set_email,
),
rx.button(
"Next",
on_click=MultiStepFormState.next_step,
disabled=~MultiStepFormState.step1_valid,
),
)
def step2():
return rx.vstack(
rx.heading("Step 2: Address"),
rx.input(
placeholder="Street",
value=MultiStepFormState.street,
on_change=MultiStepFormState.set_street,
),
rx.input(
placeholder="City",
value=MultiStepFormState.city,
on_change=MultiStepFormState.set_city,
),
rx.input(
placeholder="ZIP Code",
value=MultiStepFormState.zip_code,
on_change=MultiStepFormState.set_zip_code,
),
rx.hstack(
rx.button("Back", on_click=MultiStepFormState.prev_step),
rx.button(
"Next",
on_click=MultiStepFormState.next_step,
disabled=~MultiStepFormState.step2_valid,
),
),
)
def step3():
return rx.vstack(
rx.heading("Step 3: Preferences"),
rx.checkbox(
"Subscribe to newsletter",
checked=MultiStepFormState.newsletter,
on_change=MultiStepFormState.set_newsletter,
),
rx.checkbox(
"Enable notifications",
checked=MultiStepFormState.notifications,
on_change=MultiStepFormState.set_notifications,
),
rx.hstack(
rx.button("Back", on_click=MultiStepFormState.prev_step),
rx.button("Submit", on_click=MultiStepFormState.submit_form),
),
)
def multi_step_form():
return rx.container(
rx.cond(
MultiStepFormState.current_step == 1,
step1(),
rx.cond(
MultiStepFormState.current_step == 2,
step2(),
step3(),
),
),
)Dependent Fields Pattern
class DependentFieldsState(rx.State):
"""Form with dependent/conditional fields."""
country: str = ""
state: str = ""
province: str = ""
# Available states/provinces by country
us_states: list[str] = ["California", "New York", "Texas"]
canada_provinces: list[str] = ["Ontario", "Quebec", "British Columbia"]
@rx.var
def show_state_field(self) -> bool:
"""Show state field only for US."""
return self.country == "United States"
@rx.var
def show_province_field(self) -> bool:
"""Show province field only for Canada."""
return self.country == "Canada"
@rx.var
def region_options(self) -> list[str]:
"""Get appropriate region options."""
if self.country == "United States":
return self.us_states
elif self.country == "Canada":
return self.canada_provinces
return []
def dependent_form():
return rx.vstack(
rx.select(
["United States", "Canada", "Other"],
placeholder="Select Country",
value=DependentFieldsState.country,
on_change=DependentFieldsState.set_country,
),
# Conditional US state field
rx.cond(
DependentFieldsState.show_state_field,
rx.select(
DependentFieldsState.us_states,
placeholder="Select State",
value=DependentFieldsState.state,
on_change=DependentFieldsState.set_state,
),
rx.box(),
),
# Conditional Canada province field
rx.cond(
DependentFieldsState.show_province_field,
rx.select(
DependentFieldsState.canada_provinces,
placeholder="Select Province",
value=DependentFieldsState.province,
on_change=DependentFieldsState.set_province,
),
rx.box(),
),
)Data Visualization with Charts
Interactive Dashboard
class DashboardState(rx.State):
"""Dashboard with multiple chart types."""
sales_data: list[dict] = [
{"month": "Jan", "sales": 4000, "profit": 2400},
{"month": "Feb", "sales": 3000, "profit": 1398},
{"month": "Mar", "sales": 2000, "profit": 9800},
{"month": "Apr", "sales": 2780, "profit": 3908},
{"month": "May", "sales": 1890, "profit": 4800},
{"month": "Jun", "sales": 2390, "profit": 3800},
]
category_data: list[dict] = [
{"name": "Electronics", "value": 400},
{"name": "Clothing", "value": 300},
{"name": "Food", "value": 300},
{"name": "Books", "value": 200},
]
def dashboard():
return rx.container(
rx.vstack(
rx.heading("Sales Dashboard", size="2xl"),
# Line chart
rx.box(
rx.heading("Monthly Sales & Profit", size="lg", mb="2"),
rx.recharts.line_chart(
rx.recharts.line(data_key="sales", stroke="#8884d8"),
rx.recharts.line(data_key="profit", stroke="#82ca9d"),
rx.recharts.x_axis(data_key="month"),
rx.recharts.y_axis(),
rx.recharts.legend(),
data=DashboardState.sales_data,
width="100%",
height=300,
),
),
# Bar chart
rx.box(
rx.heading("Category Distribution", size="lg", mb="2"),
rx.recharts.bar_chart(
rx.recharts.bar(data_key="value", fill="#8884d8"),
rx.recharts.x_axis(data_key="name"),
rx.recharts.y_axis(),
data=DashboardState.category_data,
width="100%",
height=300,
),
),
# Pie chart
rx.box(
rx.heading("Market Share", size="lg", mb="2"),
rx.recharts.pie_chart(
rx.recharts.pie(
data=DashboardState.category_data,
data_key="value",
name_key="name",
fill="#8884d8",
),
rx.recharts.legend(),
width="100%",
height=300,
),
),
spacing="8",
),
)Database Patterns
DuckDB Integration
import duckdb
import polars as pl
class DBIntegrationState(rx.State):
"""DuckDB database integration."""
records: list[dict] = []
filtered_records: list[dict] = []
search_term: str = ""
async def load_all_records(self):
"""Load all records from database."""
conn = duckdb.connect("data/app.duckdb")
df = conn.execute("SELECT * FROM users").pl()
self.records = df.to_dicts()
conn.close()
async def search_records(self):
"""Search records by term."""
if not self.search_term:
self.filtered_records = self.records
return
conn = duckdb.connect("data/app.duckdb")
query = """
SELECT * FROM users
WHERE name LIKE ? OR email LIKE ?
"""
search_pattern = f"%{self.search_term}%"
df = conn.execute(query, [search_pattern, search_pattern]).pl()
self.filtered_records = df.to_dicts()
conn.close()
async def add_record(self, data: dict):
"""Insert new record."""
conn = duckdb.connect("data/app.duckdb")
conn.execute(
"INSERT INTO users (name, email) VALUES (?, ?)",
[data["name"], data["email"]]
)
conn.close()
await self.load_all_records()
async def delete_record(self, user_id: int):
"""Delete record by ID."""
conn = duckdb.connect("data/app.duckdb")
conn.execute("DELETE FROM users WHERE id = ?", [user_id])
conn.close()
await self.load_all_records()Polars Data Processing
import polars as pl
class DataProcessingState(rx.State):
"""Polars data processing patterns."""
raw_data: list[dict] = []
processed_data: list[dict] = []
async def process_data(self):
"""Process data using Polars."""
# Convert to Polars DataFrame
df = pl.DataFrame(self.raw_data)
# Example processing pipeline
processed = (
df
.filter(pl.col("amount") > 100)
.group_by("category")
.agg([
pl.col("amount").sum().alias("total"),
pl.col("amount").mean().alias("average"),
pl.col("id").count().alias("count"),
])
.sort("total", descending=True)
)
self.processed_data = processed.to_dicts()Advanced Component Patterns
Reusable Data Card
def data_card(
title: str,
value: str,
change: str,
icon: str,
color: str = "blue",
) -> rx.Component:
"""Reusable card component for displaying metrics."""
return rx.box(
rx.vstack(
rx.hstack(
rx.icon(icon, size=32, color=f"{color}.500"),
rx.spacer(),
rx.badge(change, color_scheme="green" if "+" in change else "red"),
width="100%",
),
rx.text(title, font_size="sm", color="gray.600"),
rx.heading(value, size="2xl", color=f"{color}.600"),
spacing="2",
align="start",
),
padding="6",
border_radius="lg",
bg="white",
box_shadow="md",
width="100%",
)
def metrics_dashboard():
return rx.responsive_grid(
data_card("Total Sales", "$45,231", "+12.5%", "trending_up", "blue"),
data_card("New Users", "1,234", "+5.2%", "person_add", "green"),
data_card("Revenue", "$12,345", "+8.1%", "attach_money", "purple"),
data_card("Orders", "567", "-2.4%", "shopping_cart", "orange"),
columns=[1, 2, 4],
spacing="4",
)Modal Dialog Pattern
class ModalState(rx.State):
"""Modal dialog state."""
show_modal: bool = False
selected_item: dict = {}
def open_modal(self, item: dict):
"""Open modal with item data."""
self.selected_item = item
self.show_modal = True
def close_modal(self):
"""Close modal."""
self.show_modal = False
self.selected_item = {}
def modal_dialog():
return rx.cond(
ModalState.show_modal,
rx.box(
rx.box(
rx.vstack(
rx.hstack(
rx.heading("Item Details"),
rx.spacer(),
rx.button("×", on_click=ModalState.close_modal),
width="100%",
),
rx.text(f"Name: {ModalState.selected_item.get('name', '')}"),
rx.text(f"Value: {ModalState.selected_item.get('value', '')}"),
rx.button("Close", on_click=ModalState.close_modal),
),
bg="white",
padding="6",
border_radius="lg",
max_width="500px",
),
position="fixed",
top="0",
left="0",
width="100vw",
height="100vh",
bg="rgba(0,0,0,0.5)",
display="flex",
align_items="center",
justify_content="center",
z_index="1000",
),
rx.box(),
)Performance Optimization
Lazy Loading Pattern
class LazyLoadState(rx.State):
"""Lazy load data as user scrolls."""
items: list[dict] = []
page: int = 1
has_more: bool = True
is_loading: bool = False
async def load_more(self):
"""Load next page of data."""
if self.is_loading or not self.has_more:
return
self.is_loading = True
# Fetch next page (replace with actual API call)
new_items = await fetch_page(self.page)
if new_items:
self.items.extend(new_items)
self.page += 1
else:
self.has_more = False
self.is_loading = False
def lazy_list():
return rx.vstack(
rx.foreach(LazyLoadState.items, lambda item: rx.text(item["name"])),
rx.cond(
LazyLoadState.has_more,
rx.button(
"Load More",
on_click=LazyLoadState.load_more,
disabled=LazyLoadState.is_loading,
),
rx.text("No more items"),
),
)Debounced Search
import asyncio
class SearchState(rx.State):
"""Debounced search implementation."""
search_query: str = ""
search_results: list[dict] = []
is_searching: bool = False
_search_task: asyncio.Task = None
async def debounced_search(self, query: str):
"""Search with 500ms debounce."""
self.search_query = query
# Cancel previous search
if self._search_task and not self._search_task.done():
self._search_task.cancel()
# Start new search after delay
self._search_task = asyncio.create_task(self._perform_search())
async def _perform_search(self):
"""Perform the actual search after delay."""
await asyncio.sleep(0.5) # 500ms debounce
if not self.search_query:
self.search_results = []
return
self.is_searching = True
# Perform search (replace with actual search)
results = await search_api(self.search_query)
self.search_results = results
self.is_searching = False
def search_box():
return rx.vstack(
rx.input(
placeholder="Search...",
value=SearchState.search_query,
on_change=SearchState.debounced_search,
),
rx.cond(
SearchState.is_searching,
rx.spinner(),
rx.foreach(
SearchState.search_results,
lambda r: rx.text(r["title"]),
),
),
)Related skills
How it compares
Choose reflex-dev for Python-only full-stack Reflex apps; use next-best-practices skills for TypeScript React meta-frameworks.
FAQ
What architecture does reflex-dev document?
reflex-dev documents Reflex compiling Python to a React frontend and FastAPI backend, synchronizing rx.State over WebSockets with server-side event handlers as the only mutation path.
How many Reflex components does the skill reference?
reflex-dev references 60+ built-in Reflex components including layout stacks, inputs, data_table, recharts charts, and interactive buttons with on_click event bindings.