R

ReflexKR

Reflex Docs

Full-Stack Python Web Development with Reflex

·

Course overview

1

Meet Reflex and set up your environment

2

Drawing the screen with components

3

State and Vars: let data draw the screen

4

Events and forms: adding customers

5

Shaping data and charts

6

Wrap-up, debugging and next steps

·

Appendix

Full-Stack Python Web Development with Reflex
Lesson 2

Time 0:35–1:15

Drawing the screen with components

In this lesson, you'll build a static screen with no state yet. When you're done, you'll see a header with a "Customers" title and a description inside a rounded card.

2-1. Project structure (5 min)

tutorial_261019/                 # project root (run reflex commands here)
├── rxconfig.py                  # app name and plugin settings
├── pyproject.toml / uv.lock     # Python dependencies (managed by uv)
├── assets/                      # static files such as images → referenced like "/favicon.ico"
├── reflex.lock/                 # frontend package lock files (commit these)
├── .web/                        # compiled React code (generated, don't edit)
└── tutorial_261019/             # Python package with the same name as app_name
    ├── __init__.py
    └── tutorial_261019.py       # the file we'll work in (app = rx.App())

Here's rxconfig.py. app_name must match the inner folder name, or Reflex won't find your app.

import reflex as rx

config = rx.Config(
    app_name="tutorial_261019",
    plugins=[
        rx.plugins.SitemapPlugin(),
        rx.plugins.TailwindV4Plugin(),
        rx.plugins.RadixThemesPlugin(),   # provides the default look for buttons, tables, etc.
    ],
)

2-2. The smallest Reflex app (5 min)

Delete everything in tutorial_261019/tutorial_261019.py and start with this:

import reflex as rx


def index() -> rx.Component:
    return rx.heading("Customers")


app = rx.App()
app.add_page(index, title="Customer Data App")
  • index() is a function that returns a piece of UI (a component). One page = one function.
  • app = rx.App() must be at the top level of the module. Reflex looks for your app by this variable name.
  • Because the function is named index, add_page(index) registers it at the root path /. title is the browser tab title.
  • While the server is running, saving the file recompiles the app automatically (hot reload).

Check your browser — After you save, only the "Customers" heading is left in the top-left corner. It's normal for it to sit right against the edge, since there's no padding yet.

localhost:3000

Live preview

Customers

Step 2-2: only the Customers heading is visible

2-3. Two rules of component syntax (10 min)

Reflex UI code really comes down to these two rules:

  1. Positional arguments = children: components you pass in order inside the parentheses become the content nested inside.
  2. Keyword arguments = props and styles: pass them as name=value, like size="6" or margin="0".
rx.box(                                   # <div>
    rx.heading("Customers", size="6"),   # child 1
    rx.badge("2"),                        # child 2
    display="flex",                       # style
    gap="12px",
)
  • Write CSS properties in snake_case: align-items → align_items, margin-top → margin_top.
  • Names that clash with Python keywords get a trailing _: as → as_, type → type_.
  • rx.color("slate", 11) is the Radix color palette. Higher numbers are darker, and the colors adapt to dark mode automatically. Use 1–2 for backgrounds, 4–6 for borders and 11–12 for text.

Instructor note — Describing it as "HTML tags turned into function calls" makes it click quickly: rx.box = <div>, rx.text = <p>, rx.heading = <h1>–<h6>. Open the browser dev tools (F12) and show the tags that were actually generated.

2-4. Build the header (10 min)

The header has a left side (title + badge + description) and a right side (a button). The button comes in Lesson 4, so for now you'll build only the left side.

def index() -> rx.Component:
    return rx.box(
        rx.box(
            rx.box(
                rx.heading("Customers", size="6", as_="h3", margin="0"),
                rx.badge("2", variant="soft", color_scheme="gray", radius="full"),
                display="flex",
                align_items="center",
                gap="12px",
            ),
            rx.text(
                "Your customer directory and audience breakdown.",
                size="2",
                color=rx.color("slate", 11),
                margin_top="6px",
            ),
        ),
        display="flex",
        align_items="center",
        justify_content="space-between",
        flex_wrap="wrap",
        gap="16px",
        padding="24px",
    )
  • The badge's "2" is a placeholder. In Lesson 3 you'll replace it with State.users.length() to show the real customer count.
  • justify_content="space-between" pushes the children to opposite ends. That's why the button will sit at the far right.
  • flex_wrap="wrap" moves the button to the next line on narrow screens (mobile support).

Check your browser — You should see a gray rounded badge (2) next to the title, with a gray description underneath. There's no card border yet.

localhost:3000

Live preview

Customers

2

Your customer directory and audience breakdown.

Step 2-4: header with a title, badge and description

2-5. Wrap it in a card (5 min)

Wrap the header in one more rx.box to give it a card shape. This outer box becomes the frame for the whole app, and the table and chart will go inside it later.

def index() -> rx.Component:
    return rx.box(
        header,            # the header box from 2-4
        # the table (Lesson 3) and chart (Lesson 5) go here
        width="100%",
        min_width="0",
        max_width="64em",            # maximum card width
        margin_x="auto",             # center horizontally
        margin_y="24px",
        border=f"1px solid {rx.color('slate', 5)}",
        border_radius="16px",
        background=rx.color("slate", 1),
        overflow="hidden",           # keep content from spilling past the rounded corners
        box_shadow="0 4px 24px rgba(0, 0, 0, 0.03)",
        style={
            "& *": {"box_sizing": "border-box"},
            "& p, & h3": {"text_align": "left"},
        },
    )
  • Use style={...} for CSS selectors that are hard to express as keyword arguments. "& *" means "every element inside this box."
  • You can use rx.color(...) inside an f-string. Just be careful to mix single and double quotes correctly.

Check your browser — The header now sits inside a rounded card centered on the page. If you make the browser window narrower, the card shrinks with it.

localhost:3000

Live preview

Customers

2

Your customer directory and audience breakdown.

Step 2-5: the header wrapped in a card

Exercises

  1. Change the description text into your own language, save, and watch hot reload pick it up.
  2. Change border_radius to "4px" and then "32px" and compare the difference.
  3. Add rx.color_mode.button() to the header and check that the colors switch nicely in dark mode.

Checkpoint

  • You can explain the difference between positional and keyword arguments.
  • You can rewrite CSS properties in snake_case.
  • The card header appears centered on the page.

← Previous

Lesson 1 · Meet Reflex and set up your environment

Next →

Lesson 3 · State and Vars: let data draw the screen

Get news about new lessons

We'll email you when this course gets new lessons or a follow-up course opens.

We collect: your email address · Purpose: news about new lessons or the launch of this course · Kept until: you ask us to remove it. We never use it for anything else or share it. Reply to any notice email and we'll delete it right away.

Questions & answers

Ask anything about this lesson. Questions from every language version of this page appear together.

Shown as plain text, without links or formatting.

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
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex