diff --git a/docs/app/assets/tailwind-theme.css b/docs/app/assets/tailwind-theme.css index 2b31897abfa..fbf4e63e176 100644 --- a/docs/app/assets/tailwind-theme.css +++ b/docs/app/assets/tailwind-theme.css @@ -1699,6 +1699,14 @@ /* padding: 0rem 0.125rem 0rem 0.125rem; */ } + /* Override Radix Code (rt-Code) accent coloring so inline code matches slate theme */ + code.rt-Code, + code.rt-Code.rt-variant-soft, + .rt-Code.rt-variant-soft { + color: var(--c-slate-11) !important; + background-color: var(--c-slate-3) !important; + } + .code-error-style { font-family: var(--font-jetbrains); font-size: 0.835rem; @@ -1803,6 +1811,44 @@ } } + .code-block button, + .code-block > button { + border: none !important; + border-width: 0 !important; + background: transparent !important; + background-color: transparent !important; + opacity: 0 !important; + transition: opacity 0.15s ease-out !important; + pointer-events: none; + display: inline-flex !important; + align-items: center !important; + gap: 0.375rem !important; + font-size: 0.8125rem !important; + font-weight: 500 !important; + color: var(--c-slate-11) !important; + top: 8px !important; + right: 12px !important; + padding: 6px 8px !important; + } + + .code-block button::after, + .code-block > button::after { + content: "Copy"; + } + + .code-block:hover button, + .code-block:hover > button, + .code-block button:focus-visible { + opacity: 1 !important; + pointer-events: auto; + } + + .code-block button:hover, + .code-block > button:hover { + background: var(--c-slate-3) !important; + background-color: var(--c-slate-3) !important; + } + .tab-style { color: var(--c-slate-9); @@ -1814,6 +1860,89 @@ letter-spacing: -0.01094rem; } + .pill-tab-list { + display: inline-flex !important; + gap: 2px !important; + padding: 4px !important; + background: var(--c-slate-3) !important; + border-radius: 10px !important; + border-bottom: none !important; + box-shadow: none !important; + width: fit-content !important; + max-width: 100%; + overflow-x: auto; + } + + .pill-tab-list::before, + .pill-tab-list::after { + display: none !important; + content: none !important; + } + + .pill-tab { + appearance: none !important; + background: transparent !important; + background-color: transparent !important; + border: none !important; + padding: 8px 16px !important; + font-size: 0.9375rem !important; + font-weight: 450 !important; + color: var(--c-slate-11) !important; + border-radius: 7px !important; + cursor: pointer; + white-space: nowrap; + transition: color 0.12s, background-color 0.12s, box-shadow 0.12s; + letter-spacing: 0 !important; + line-height: 1.25rem !important; + box-shadow: none !important; + } + + .pill-tab::before, + .pill-tab::after, + .pill-tab:hover::before, + .pill-tab:hover::after, + .pill-tab[data-state='active']::before, + .pill-tab[data-state='active']::after, + .pill-tab[data-state='active']:hover::before, + .pill-tab[data-state='active']:hover::after { + background: transparent !important; + background-color: transparent !important; + box-shadow: none !important; + content: none !important; + display: none !important; + } + + .pill-tab:hover { + color: var(--c-slate-11) !important; + background: transparent !important; + } + + .pill-tab .rt-BaseTabListTriggerInner, + .pill-tab:hover .rt-BaseTabListTriggerInner, + .pill-tab:focus-visible .rt-BaseTabListTriggerInner, + .pill-tab:focus-visible:hover .rt-BaseTabListTriggerInner { + background-color: transparent !important; + background: transparent !important; + } + + .pill-tab[data-state='active'], + .pill-tab[data-state='active']:hover { + color: var(--c-slate-12) !important; + font-weight: 500 !important; + background: var(--c-slate-1) !important; + background-color: var(--c-slate-1) !important; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important; + } + + :where(.dark, .dark *) .pill-tab[data-state='active'] { + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4) !important; + } + + .tab-style:hover:not([data-state='active'])::before { + background: transparent !important; + background-color: transparent !important; + } + .tab-style:hover { color: var(--c-slate-11); } diff --git a/docs/app/reflex_docs/docgen_pipeline.py b/docs/app/reflex_docs/docgen_pipeline.py index 132e72f5464..8a567b9fe65 100644 --- a/docs/app/reflex_docs/docgen_pipeline.py +++ b/docs/app/reflex_docs/docgen_pipeline.py @@ -516,12 +516,18 @@ def _render_alert(self, block: DirectiveBlock) -> rx.Component: """Render a ``md alert`` directive.""" status = block.args[0] if block.args else "info" colors: dict[str, ColorType] = { - "info": "accent", + "info": "slate", "success": "grass", "warning": "amber", "error": "red", } - color: ColorType = colors.get(status, "blue") + color: ColorType = colors.get(status, "slate") + background_shade = 2 if status == "info" else 3 + # For "info" alerts, use the neutral custom slate scale (--c-slate-*) + # so the card matches the codeblock styling instead of the blue-tinted + # default --slate-* scale that rx.color() produces. + bg_override = "var(--c-slate-2)" if status == "info" else None + border_override = "var(--c-slate-4)" if status == "info" else None # First child may be a heading used as the alert title. children = block.children @@ -560,7 +566,14 @@ def title_comp() -> rx.Component: padding="0px", margin_top="16px", ) - return collapsible_box(trigger, body, color) + return collapsible_box( + trigger, + body, + color, + background_shade=background_shade, + background_override=bg_override, + border_override=border_override, + ) # Title only, or text-only (no heading) — simple non-collapsible box. if title_spans: @@ -588,8 +601,8 @@ def title_comp() -> rx.Component: spacing="1", padding=["16px", "24px"], ), - border=f"1px solid {rx.color(color, 4)}", - background_color=f"{rx.color(color, 3)}", + border=f"1px solid {border_override or rx.color(color, 4)}", + background_color=f"{bg_override or rx.color(color, background_shade)}", border_radius="12px", margin_bottom="16px", margin_top="16px", @@ -666,15 +679,19 @@ def _render_tabs(self, block: DirectiveBlock) -> rx.Component: rx.tabs.trigger( title, value=value, - class_name="tab-style font-base font-semibold text-[1.25rem]", + class_name="pill-tab", ) ) contents.append( - rx.tabs.content(self._render_children(body_blocks), value=value), + rx.tabs.content( + self._render_children(body_blocks), + value=value, + class_name="pt-6", + ), ) return rx.tabs.root( - rx.tabs.list(*triggers, class_name="mt-4"), + rx.tabs.list(*triggers, class_name="pill-tab-list mt-2"), *contents, default_value="tab1", ) @@ -702,29 +719,43 @@ def _render_definition(self, block: DirectiveBlock) -> rx.Component: def _render_section(self, block: DirectiveBlock) -> rx.Component: """Render a ``md section`` directive.""" - from reflex_site_shared.styles.colors import c_color - sections = self._split_children_by_heading(block.children) - return rx.box( - rx.vstack( + return rx.el.div( + rx.el.div( *[ - rx.fragment( - rx.text( - rx.text.span(header, font_weight="bold"), - width="100%", + rx.el.div( + rx.el.div( + header, + style={ + "fontWeight": "600", + "color": "var(--c-slate-12)", + "fontSize": "1rem", + "lineHeight": "1.5", + }, + ), + rx.el.div( + self._render_children(body), + style={"width": "100%"}, ), - rx.box(self._render_children(body), width="100%"), + style={ + "display": "flex", + "flexDirection": "column", + "gap": "0.25rem", + "width": "100%", + }, ) for header, body in sections ], - text_align="left", - margin_y="1em", - width="100%", + style={ + "display": "flex", + "flexDirection": "column", + "gap": "1.25rem", + "width": "100%", + "paddingLeft": "1.5rem", + "borderLeft": "1.5px solid var(--c-slate-4)", + }, ), - border_left=f"1.5px {c_color('slate', 4)} solid", - padding_left="1em", - width="100%", - align_items="center", + style={"width": "100%", "margin": "1.5rem 0"}, ) diff --git a/docs/app/reflex_docs/templates/docpage/docpage.py b/docs/app/reflex_docs/templates/docpage/docpage.py index b652d867d01..2779ae9920e 100644 --- a/docs/app/reflex_docs/templates/docpage/docpage.py +++ b/docs/app/reflex_docs/templates/docpage/docpage.py @@ -9,6 +9,7 @@ from reflex.components.radix.themes.base import LiteralAccentColor from reflex.experimental.client_state import ClientStateVar from reflex.utils.format import to_snake_case, to_title_case +from reflex_base.event import run_script from reflex_site_shared.backend.status import StatusState from reflex_site_shared.components.blocks.code import * from reflex_site_shared.components.blocks.demo import * @@ -300,7 +301,271 @@ def docpage_footer(path: str): ) -def breadcrumb(path: str, nav_sidebar: rx.Component): +def _copy_page_menu_item( + icon: rx.Component, + title: str, + description: str, + on_click=None, + href: str | None = None, +) -> rx.Component: + row = rx.el.div( + rx.el.div( + icon, + class_name="flex size-8 items-center justify-center rounded-md border border-slate-5 bg-slate-2 text-slate-11 shrink-0", + ), + rx.el.div( + rx.el.div( + rx.el.span(title, class_name="text-sm font-medium text-slate-12"), + ui.icon( + "ArrowUpRight01Icon", + size=12, + class_name="!text-slate-9", + ) + if href + else rx.fragment(), + class_name="flex items-center gap-1", + ), + rx.el.span( + description, + class_name="text-xs text-slate-10", + ), + class_name="flex flex-col items-start gap-0.5", + ), + class_name="flex items-start gap-3 px-3 py-2 w-full hover:bg-slate-3 transition-colors cursor-pointer", + ) + if href: + return rx.el.a( + row, + href=href, + target="_blank", + rel="noopener noreferrer", + class_name="no-underline", + ) + return rx.el.button( + row, + type="button", + on_click=on_click, + class_name="w-full text-left", + ) + + +DOCS_PROD_BASE = "https://reflex.dev/docs" + + +def _build_prefill_url(base_url: str, path: str, action: str) -> str: + from urllib.parse import quote + + page_md_url = f"{DOCS_PROD_BASE}{path.rstrip('/')}.md" + prompt = f"Read from {page_md_url} {action}" + return f"{base_url}{quote(prompt)}" + + +def _build_reflex_menu_item(path: str) -> rx.Component: + href = _build_prefill_url( + "https://build.reflex.dev/?prompt=", + path, + "and help me build an app based on it.", + ) + return rx.el.a( + rx.el.div( + rx.el.div( + ui.icon( + "AiMagicIcon", + size=16, + class_name="!text-white", + ), + class_name=( + "flex size-8 items-center justify-center rounded-md " + "bg-gradient-to-br from-violet-9 to-violet-11 " + "shadow-[0_0_0_1px_var(--violet-7),0_2px_8px_-2px_var(--violet-a8)] shrink-0" + ), + ), + rx.el.div( + rx.el.div( + rx.el.span( + "Build this with AI", + class_name="text-sm font-semibold text-slate-12", + ), + ui.icon( + "ArrowUpRight01Icon", + size=12, + class_name="!text-violet-11", + ), + class_name="flex items-center gap-1", + ), + rx.el.span( + "Open in Reflex Build", + class_name="text-xs text-slate-10", + ), + class_name="flex flex-col items-start gap-0.5", + ), + class_name="flex items-start gap-3 px-3 py-2.5 w-full", + ), + href=href, + target="_blank", + rel="noopener noreferrer", + class_name=( + "no-underline w-full text-left block " + "bg-gradient-to-br from-violet-2 to-slate-1 " + "hover:from-violet-3 hover:to-violet-2 " + "border-b border-slate-4 transition-colors cursor-pointer" + ), + ) + + +def _llm_menu_item_href(base_url: str, path: str) -> str: + return _build_prefill_url( + base_url, path, "so I can ask questions about its contents" + ) + + +def _copy_page_button(doc_content: str, path: str = "") -> rx.Component: + copy_action = run_script( + """ +((function() { + const mdUrl = window.location.pathname.replace(/\\/$/, '') + '.md'; + const animate = () => { + document.querySelectorAll('[data-copy-icon]').forEach((icon) => { + if (icon.dataset.animating === '1') return; + icon.dataset.animating = '1'; + const original = icon.innerHTML; + const check = ''; + icon.style.transition = 'transform 140ms cubic-bezier(0.34, 1.56, 0.64, 1), opacity 140ms ease-out, color 140ms ease-out'; + icon.style.transform = 'scale(0.2)'; + icon.style.opacity = '0'; + setTimeout(() => { + icon.innerHTML = check; + icon.style.color = 'var(--c-grass-11)'; + icon.style.transform = 'scale(1)'; + icon.style.opacity = '1'; + }, 140); + setTimeout(() => { + icon.style.transform = 'scale(0.2)'; + icon.style.opacity = '0'; + }, 1500); + setTimeout(() => { + icon.innerHTML = original; + icon.style.color = ''; + icon.style.transform = 'scale(1)'; + icon.style.opacity = '1'; + icon.dataset.animating = '0'; + }, 1640); + }); + }; + animate(); + fetch(mdUrl) + .then((r) => (r.ok ? r.text() : Promise.reject(r.status))) + .then((text) => { + if (navigator.clipboard && navigator.clipboard.writeText) { + return navigator.clipboard.writeText(text).catch(() => { + const ta = document.createElement('textarea'); + ta.value = text; + ta.style.position = 'fixed'; + ta.style.opacity = '0'; + document.body.appendChild(ta); + ta.select(); + try { document.execCommand('copy'); } catch (e) {} + document.body.removeChild(ta); + }); + } + const ta = document.createElement('textarea'); + ta.value = text; + ta.style.position = 'fixed'; + ta.style.opacity = '0'; + document.body.appendChild(ta); + ta.select(); + try { document.execCommand('copy'); } catch (e) {} + document.body.removeChild(ta); + }) + .catch((err) => console.error('Copy page failed:', err)); +})()) + """ + ) + trigger = rx.el.div( + rx.el.button( + rx.el.span( + ui.icon("Copy01Icon", size=16), + custom_attrs={"data-copy-icon": "main"}, + class_name="inline-flex items-center justify-center transition-transform", + ), + type="button", + aria_label="Copy page as Markdown", + on_click=copy_action, + class_name=( + "flex items-center justify-center px-2.5 h-8 " + "border border-slate-5 border-r-0 rounded-l-md text-slate-11 " + "hover:text-slate-12 hover:bg-slate-3 active:scale-[0.96] " + "transition-all cursor-pointer" + ), + ), + ui.popover.root( + ui.popover.trigger( + render_=rx.el.button( + ui.icon("ArrowDown01Icon", size=14), + type="button", + aria_label="Copy page options", + class_name=( + "flex items-center justify-center px-1.5 h-8 " + "border border-slate-5 rounded-r-md text-slate-11 " + "hover:text-slate-12 hover:bg-slate-3 active:scale-[0.96] " + "transition-all cursor-pointer" + ), + ), + ), + ui.popover.portal( + ui.popover.positioner( + ui.popover.popup( + render_=rx.el.div( + _build_reflex_menu_item(path=path), + _copy_page_menu_item( + icon=ui.icon("Copy01Icon", size=16), + title="Copy page", + description="Copy page as Markdown for LLMs", + on_click=copy_action, + ), + _copy_page_menu_item( + icon=ui.icon("File01Icon", size=16), + title="llms-full.txt", + description="View all docs as Markdown for LLMs", + href="/docs/llms-full.txt", + ), + rx.el.div(class_name="h-px bg-slate-4 my-1 mx-2"), + _copy_page_menu_item( + icon=ui.icon("MessageProgrammingIcon", size=16), + title="Open in ChatGPT", + description="Ask ChatGPT about this page", + href=_llm_menu_item_href( + "https://chatgpt.com/?hints=search&q=", path + ), + ), + _copy_page_menu_item( + icon=ui.icon("AiChat02Icon", size=16), + title="Open in Claude", + description="Ask Claude about this page", + href=_llm_menu_item_href( + "https://claude.ai/new?q=", path + ), + ), + class_name=( + "flex flex-col min-w-[260px] py-1 " + "bg-slate-1 border border-slate-5 rounded-lg shadow-lg " + "data-[state=open]:animate-in data-[state=open]:fade-in-0 " + "data-[state=open]:zoom-in-95 data-[state=open]:slide-in-from-top-2" + ), + ), + ), + side="bottom", + align="end", + side_offset=6, + ), + ), + ), + class_name="hidden lg:flex flex-row items-center shrink-0", + ) + return trigger + + +def breadcrumb(path: str, nav_sidebar: rx.Component, doc_content: str | None = None): from reflex_docs.components.docpage.navbar.buttons.sidebar import ( docs_sidebar_drawer, ) @@ -360,8 +625,13 @@ def breadcrumb(path: str, nav_sidebar: rx.Component): class_name="flex flex-row items-center gap-[5px] lg:gap-4 overflow-hidden", ), rx.box( - ui.icon("ArrowDown01Icon", size=14, class_name="!text-slate-9"), - class_name="p-[0.563rem] lg:hidden flex", + _copy_page_button(doc_content, path=path) if doc_content else rx.fragment(), + ui.icon( + "ArrowDown01Icon", + size=14, + class_name="!text-slate-9 lg:hidden flex", + ), + class_name="flex flex-row items-center gap-2 lg:p-0 p-[0.563rem]", ), class_name=ui.cn( "relative z-10 flex flex-row justify-between items-center gap-4 lg:gap-0 border-slate-4 bg-slate-1 mt-[139px] lg:p-0 border-b lg:border-none w-full max-lg:py-2", @@ -513,7 +783,11 @@ def wrapper(*args, **kwargs) -> rx.Component: ), rx.box( rx.box( - breadcrumb(path=path, nav_sidebar=nav_sidebar), + breadcrumb( + path=path, + nav_sidebar=nav_sidebar, + doc_content=doc_content, + ), class_name=( "px-0 pt-0 mb-[2rem]" + rx.cond( @@ -533,8 +807,8 @@ def wrapper(*args, **kwargs) -> rx.Component: class_name="lg:mt-0 h-auto", ), class_name=ui.cn( - "flex-1 h-auto mx-auto lg:max-w-[42rem] px-4 overflow-y-auto", - "lg:max-w-[56rem]" if not show_right_sidebar else "", + "flex-1 h-auto mx-auto lg:max-w-[52rem] px-4 overflow-y-auto", + "lg:max-w-[64rem]" if not show_right_sidebar else "", ), ), rx.box( @@ -613,7 +887,7 @@ def wrapper(*args, **kwargs) -> rx.Component: ), ), class_name=( - "w-[240px] h-screen sticky top-0 shrink-0 hidden xl:block" + "w-[240px] h-screen sticky top-0 shrink-0 hidden 2xl:block" ), ) if show_right_sidebar and not pseudo_right_bar diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/learn.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/learn.py index 99455effa1c..a0a2149b23e 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/learn.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/learn.py @@ -10,8 +10,8 @@ def get_sidebar_items_learn(): create_item( "Getting Started", children=[ - getting_started.introduction, getting_started.installation, + getting_started.introduction, getting_started.basics, getting_started.project_structure, getting_started.dashboard_tutorial, diff --git a/docs/app/reflex_docs/views/docs_navbar.py b/docs/app/reflex_docs/views/docs_navbar.py index 0d753dcae4d..03f3180bc43 100644 --- a/docs/app/reflex_docs/views/docs_navbar.py +++ b/docs/app/reflex_docs/views/docs_navbar.py @@ -1,14 +1,38 @@ import reflex as rx import reflex_components_internal as ui from reflex_components_internal.blocks.demo_form import demo_form_dialog +from reflex_site_shared.components.icons import get_icon from reflex_site_shared.components.marketing_button import button -from reflex_site_shared.constants import REFLEX_ASSETS_CDN, REFLEX_URL +from reflex_site_shared.constants import ( + GITHUB_STARS, + GITHUB_URL, + REFLEX_ASSETS_CDN, + REFLEX_URL, +) from reflex_docs.components.docpage.navbar.buttons.sidebar import navbar_sidebar_button from reflex_docs.pages.docs import ai_builder, getting_started, hosting from reflex_docs.views.search import search_bar +def github_button() -> rx.Component: + label = f"View Reflex on GitHub - {GITHUB_STARS // 1000}K stars" + return rx.el.a( + button( + get_icon(icon="github_navbar", class_name="shrink-0"), + f"{GITHUB_STARS // 1000}K", + custom_attrs={"aria-label": label}, + size="sm", + variant="ghost", + native_button=False, + ), + href=GITHUB_URL, + target="_blank", + rel="noopener noreferrer", + custom_attrs={"aria-label": label}, + ) + + def logo() -> rx.Component: return rx.el.elements.a( rx.el.div( @@ -71,7 +95,7 @@ def menu_item(text: str, href: str, active_str: str = "") -> rx.Component: href=href, ), class_name=ui.cn( - "xl:flex hidden h-full items-center justify-center", + "md:flex hidden h-full items-center justify-center", rx.cond(active, active_cn, ""), ), custom_attrs={"role": "menuitem"}, @@ -91,6 +115,12 @@ def navigation_menu() -> rx.Component: custom_attrs={"role": "menubar"}, ), ui.navigation_menu.list( + ui.navigation_menu.item( + github_button(), + unstyled=True, + class_name="md:flex hidden", + custom_attrs={"role": "menuitem"}, + ), ui.navigation_menu.item( search_bar(), unstyled=True, @@ -112,7 +142,7 @@ def navigation_menu() -> rx.Component: ), ui.navigation_menu.item( navbar_sidebar_button(), - class_name="xl:hidden flex", + class_name="md:hidden flex", unstyled=True, custom_attrs={"role": "menuitem"}, ), diff --git a/docs/getting_started/basics.md b/docs/getting_started/basics.md index 7d6e7f26ae9..fa55f27282f 100644 --- a/docs/getting_started/basics.md +++ b/docs/getting_started/basics.md @@ -2,9 +2,9 @@ import reflex as rx ``` -# Reflex Basics +# Basics -This page gives an introduction to the most common concepts that you will use to build Reflex apps. +**~10 min** · An introduction to the most common concepts you'll use to build Reflex apps. ```md section # You will learn how to: @@ -18,13 +18,7 @@ This page gives an introduction to the most common concepts that you will use to - Create pages and navigate between them ``` -[Install](/docs/getting_started/installation) `reflex` with uv before continuing. - -```bash -uv add reflex -``` - -Import the `reflex` library to get started. +If you haven't yet, [install Reflex](/docs/getting_started/installation) before continuing. Every example below imports the library as `rx`: ```python import reflex as rx @@ -46,7 +40,7 @@ Components can be nested inside each other to create complex UIs. To nest components as children, pass them as positional arguments to the parent component. In the example below, the `rx.text` and `my_button` components are children of the `rx.box` component. ```python demo exec -def my_page(): +def my_container(): return rx.box( rx.text("This is a page"), # Reference components defined in other functions. @@ -54,7 +48,7 @@ def my_page(): ) ``` -You can also use any base HTML element through the [`rx.el`](/docs/library/other/html) namespace. This allows you to use standard HTML elements directly in your Reflex app when you need more control or when a specific component isn't available in the Reflex component library. +You can also use any base HTML element through the [rx.el](/docs/library/other/html) namespace. This allows you to use standard HTML elements directly in your Reflex app when you need more control or when a specific component isn't available in the Reflex component library. ```python demo exec def my_div(): @@ -142,7 +136,7 @@ Vars can be referenced in multiple components, and will automatically update whe So far, we've defined state vars but we haven't shown how to change them. All state changes are handled through functions in the state class, called [event handlers](/docs/events/events_overview). ```md alert -Event handlers are the ONLY way to change state in Reflex. +Event handlers are the **only** way to change state in Reflex. ``` Components have special props, such as `on_click`, called event triggers that can be used to make components interactive. Event triggers connect components to event handlers, which update the state. @@ -214,7 +208,7 @@ def text_input(): Make sure that the event handler has the same number of arguments as the event trigger, or an error will be raised. ``` -## Compile-time vs. runtime (IMPORTANT) +## Compile-time vs. runtime Before we dive deeper into state, it's important to understand the difference between compile-time and runtime in Reflex. @@ -314,7 +308,7 @@ In the next sections, we will show how to handle these cases. ## Conditional rendering -As mentioned above, you cannot use Python `if/else` statements with state vars in components. Instead, use the [`rx.cond`](/docs/components/conditional_rendering) function to conditionally render components. +As mentioned above, you cannot use Python `if/else` statements with state vars in components. Instead, use the [rx.cond](/docs/components/conditional_rendering) function to conditionally render components. ```python demo exec class LoginState(rx.State): @@ -338,7 +332,7 @@ def show_login(): ## Rendering lists -To iterate over a var that is a list, use the [`rx.foreach`](/docs/components/rendering_iterables) function to render a list of components. +To iterate over a var that is a list, use the [rx.foreach](/docs/components/rendering_iterables) function to render a list of components. Pass the list var and a function that returns a component as arguments to `rx.foreach`. @@ -398,13 +392,26 @@ def index(): return rx.text("Root Page") -rx.app = rx.App() +app = rx.App() app.add_page(index, route="/") ``` ## Next Steps -Now that you have a basic understanding of how Reflex works, the next step is to start coding your own apps. Try one of the following tutorials: +You've got the core pieces — components, state, events, compile-time vs. runtime. Time to build. + +```md alert info +# Build something real → + +- [Dashboard tutorial](/docs/getting_started/dashboard_tutorial) — a data app with tables, forms, and state. +- [Chatapp tutorial](/docs/getting_started/chatapp_tutorial) — streaming AI responses end-to-end. +- [Open-source templates](/docs/getting_started/open_source_templates) — full apps to fork. +``` -- [Dashboard Tutorial](/docs/getting_started/dashboard_tutorial) -- [Chatapp Tutorial](/docs/getting_started/chatapp_tutorial) +```md alert info +# Go deeper → + +- [Vars](/docs/vars/base_vars) and [var operations](/docs/vars/var-operations) — the full API. +- [Events](/docs/events/events_overview) and [pages](/docs/pages/overview) — routing, triggers, handlers. +- [How Reflex works](/docs/advanced_onboarding/how-reflex-works) — what runs where, and why. +``` diff --git a/docs/getting_started/chat_tutorial_style.py b/docs/getting_started/chat_tutorial_style.py index 758a089d987..57ec09c9e78 100644 --- a/docs/getting_started/chat_tutorial_style.py +++ b/docs/getting_started/chat_tutorial_style.py @@ -2,27 +2,32 @@ import reflex as rx -shadow = "rgba(0, 0, 0, 0.15) 0px 2px 8px" -chat_margin = "20%" +chat_margin = "15%" message_style = { - "padding": "1em", - "border_radius": "5px", - "margin_y": "0.5em", - "box_shadow": shadow, + "padding": "0.75em 1em", + "border_radius": "18px", + "margin_y": "0.35em", "max_width": "30em", "display": "inline-block", + "line_height": "1.5", + "font_size": "0.95rem", } # Set specific styles for questions and answers. question_style = message_style | { - "background_color": rx.color("gray", 4), + "background_color": rx.color("slate", 3), + "color": rx.color("slate", 12), + "border": f"1px solid {rx.color('slate', 4)}", "margin_left": chat_margin, + "border_bottom_right_radius": "6px", } answer_style = message_style | { - "background_color": rx.color("accent", 8), + "background_color": rx.color("accent", 9), + "color": "white", "margin_right": chat_margin, + "border_bottom_left_radius": "6px", } # Styles for the action bar. -input_style = {"border_width": "1px", "box_shadow": shadow, "width": "350px"} -button_style = {"background_color": rx.color("accent", 10), "box_shadow": shadow} +input_style = {"border_width": "1px", "width": "350px"} +button_style = {"background_color": rx.color("accent", 9)} diff --git a/docs/getting_started/chatapp_tutorial.md b/docs/getting_started/chatapp_tutorial.md index b280cf40e30..5fa623fcf62 100644 --- a/docs/getting_started/chatapp_tutorial.md +++ b/docs/getting_started/chatapp_tutorial.md @@ -12,13 +12,70 @@ if "OPENAI_API_KEY" not in os.environ: openai.api_key = "YOUR_OPENAI_KEY" ``` -# Interactive Tutorial: AI Chat App +# AI Chat App -This tutorial will walk you through building an AI chat app with Reflex. This app is fairly complex, but don't worry - we'll break it down into small steps. +**~30 min hands-on** · Build a streaming AI chatbot in pure Python — UI, state, and OpenAI integration in one Reflex app. -You can find the full source code for this app [here](https://github.com/reflex-dev/reflex-chat). +You can find the full source code for this app on [GitHub](https://github.com/reflex-dev/reflex-chat). -### What You'll Learn +```python eval +rx.box( + rx.vstack( + rx.vstack( + rx.box( + rx.text("What is Reflex?", style=style.question_style), + text_align="right", + width="100%", + ), + rx.box( + rx.text( + "Reflex is a way to build full-stack web apps in pure Python — " + "frontend, backend, and state all in one place.", + style=style.answer_style, + ), + text_align="left", + width="100%", + ), + rx.box( + rx.text("Can I deploy it?", style=style.question_style), + text_align="right", + width="100%", + ), + rx.box( + rx.text( + "Yes! A single `reflex deploy` ships it to production.", + style=style.answer_style, + ), + text_align="left", + width="100%", + ), + spacing="0", + width="100%", + ), + rx.hstack( + rx.input( + placeholder="Ask a question", + style=style.input_style, + is_disabled=True, + ), + rx.button("Ask", style=style.button_style, is_disabled=True), + spacing="3", + padding_top="1.5em", + justify="center", + width="100%", + ), + align="stretch", + spacing="0", + width="100%", + padding="2.5em 4em", + ), + border=f"1px solid {rx.color('slate', 5)}", + border_radius="12px", + margin_y="1em", +) +``` + +## What You'll Learn In this tutorial you'll learn how to: @@ -33,52 +90,27 @@ In this tutorial you'll learn how to: # Video: Example of Setting up the Chat App ``` -We will start by creating a new project and setting up our development environment. First, create a new directory for your project and navigate to it. - -```bash -~ $ mkdir chatapp -~ $ cd chatapp -``` - -Next, we will create a virtual environment for our project. This is optional, but recommended. In this example, we will use [venv](https://docs.python.org/3/library/venv.html) to create our virtual environment. - -```bash -chatapp $ python3 -m venv venv -$ source venv/bin/activate -``` - -Now, we will install Reflex and create a new project. This will create a new directory structure in our project directory. - -> **Note:** When prompted to select a template, choose option 0 for a blank project. +We will start by creating a new project and setting up our development environment. If you haven't installed [uv](https://docs.astral.sh/uv/) yet, see the [installation guide](/docs/getting_started/installation). Then create a new project directory and scaffold a Reflex app: ```bash -chatapp $ pip install reflex -chatapp $ reflex init -────────────────────────────────── Initializing chatapp ─────────────────────────────────── -Success: Initialized chatapp -chatapp $ ls -assets chatapp rxconfig.py venv +mkdir chatapp +cd chatapp +uv init +uv add reflex +uv run reflex init ``` -```python eval -rx.box(height="20px") +```md alert info +When prompted to select a template, choose option **0** for a blank project. ``` You can run the template app to make sure everything is working. ```bash -chatapp $ reflex run -─────────────────────────────────── Starting Reflex App ─────────────────────────────────── -Compiling: ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 1/1 0:00:00 -─────────────────────────────────────── App Running ─────────────────────────────────────── -App running at: http://localhost:3000 -``` - -```python eval -rx.box(height="20px") +uv run reflex run ``` -You should see your app running at [http://localhost:3000]({"http://localhost:3000"}). +You should see your app running at [http://localhost:3000](http://localhost:3000). Reflex also starts the backend server which handles all the state management and communication with the frontend. You can test the backend server is running by navigating to [http://localhost:8000/ping]({"http://localhost:8000/ping"}). @@ -88,7 +120,7 @@ Now that we have our project set up, in the next section we will start building Let's start with defining the frontend for our chat app. In Reflex, the frontend can be broken down into independent, reusable components. See the [components docs](/docs/components/props) for more information. -### Display A Question And Answer +### Display Q&A We will modify the `index` function in `chatapp/chatapp.py` file to return a component that displays a single question and answer. @@ -551,28 +583,18 @@ In the next section, we will finish our chatbot by adding AI! We will use OpenAI's API to give our chatbot some intelligence. -### Configure the OpenAI API Key +### Configure OpenAI -First, ensure you have an active OpenAI subscription. -Next, install the latest openai package: +First, ensure you have an active OpenAI subscription and install the latest `openai` package: ```bash pip install --upgrade openai ``` -Direct Configuration of API in Code - -Update the state.py file to include your API key directly: - -```python -# state.py -import os -from openai import AsyncOpenAI - -import reflex as rx +Then export your API key so the app can read it at runtime: -# Initialize the OpenAI client -client = AsyncOpenAI(api_key="YOUR_OPENAI_API_KEY") # Replace with your actual API key +```bash +export OPENAI_API_KEY="sk-..." ``` ### Using the API @@ -643,23 +665,13 @@ Finally, we have our chatbot! ### Final Code -This application is a simple, interactive chatbot built with Reflex that leverages OpenAI's API for intelligent responses. The chatbot features a clean interface with streaming responses for a natural conversation experience. - -Key Features - -1. Real-time streaming responses -2. Clean, visually distinct chat bubbles for questions and answers -3. Simple input interface with question field and submit button - -Project Structure - -Below is the full chatbot code with a commented title that corresponds to the filename. +The finished project is split across three files — `chatapp.py` for UI and app setup, `state.py` for state and API integration, and `style.py` for styling: ```text chatapp/ -├── chatapp.py # UI components and app setup -├── state.py # State management and API integration -└── style.py # Styling definitions +├── chatapp.py +├── state.py +└── style.py ``` The `chatapp.py` file: diff --git a/docs/getting_started/dashboard_tutorial.md b/docs/getting_started/dashboard_tutorial.md index 757e6e731d1..8bbd3305ebe 100644 --- a/docs/getting_started/dashboard_tutorial.md +++ b/docs/getting_started/dashboard_tutorial.md @@ -2,26 +2,24 @@ import reflex as rx ``` -# Tutorial: Data Dashboard +# Data Dashboard -During this tutorial you will build a small data dashboard, where you can input data and it will be rendered in table and a graph. This tutorial does not assume any existing Reflex knowledge, but we do recommend checking out the quick [Basics Guide](/docs/getting_started/basics) first. +**~20 min hands-on** · Build a small data dashboard where users can input data that renders in a table and a graph. -The techniques you’ll learn in the tutorial are fundamental to building any Reflex app, and fully understanding it will give you a deep understanding of Reflex. +This tutorial does not assume any existing Reflex knowledge, but we do recommend checking out the quick [Basics Guide](/docs/getting_started/basics) first. The techniques you'll learn are fundamental to any Reflex app. This tutorial is divided into several sections: -- **Setup for the Tutorial**: A starting point to follow the tutorial -- **Overview**: The fundamentals of Reflex UI (components and props) -- **Showing Dynamic Data**: How to use State to render data that will change in your app. -- **Add Data to your App**: Using a Form to let a user add data to your app and introduce event handlers. -- **Plotting Data in a Graph**: How to use Reflex's graphing components. -- **Final Cleanup and Conclusion**: How to further customize your app and add some extra styling to it. +- **Setup**: Get your machine ready. +- **Overview**: Components and props. +- **Dynamic data with State**: Render data that changes. +- **Add data with a form**: Forms + event handlers. +- **Plot a graph**: Reflex's graphing components. +- **Customize** + **[Full app](#full-app-styled)**: Customize and see the finished code. -### What are you building? +## What are you building? -In this tutorial, you are building an interactive data dashboard with Reflex. - -You can see what the finished app and code will look like here: +An interactive data dashboard: a table of users, a form to add more, and a bar chart that updates as data changes. Want to skip ahead? Jump to the [Full app](#full-app-styled) at the bottom. ```python exec import dataclasses @@ -132,163 +130,61 @@ def graph5(): return rx.recharts.bar_chart( rx.recharts.bar( data_key="value", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), + fill=rx.color("accent", 9), + radius=6, + bar_size=48, + ), + rx.recharts.x_axis( + data_key="name", + tick_line=False, + axis_line=False, + padding={"left": 24, "right": 24}, + ), + rx.recharts.y_axis( + tick_line=False, + axis_line=False, + allow_decimals=False, + ), + rx.recharts.cartesian_grid( + stroke_dasharray="3 3", + vertical=False, + stroke=rx.color("slate", 4), ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), data=State5.users_for_graph, width="100%", - height=250, + height=200, + margin={"top": 8, "right": 8, "bottom": 0, "left": 0}, ) ``` ```python eval -rx.vstack( - add_customer_button5(), - 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("Gender"), - ), - ), - rx.table.body( - rx.foreach(State5.users, show_user5), - ), - variant="surface", - size="3", - width="100%", - ), - graph5(), - align="center", - width="100%", - on_mouse_enter=State5.transform_data, - border_width="2px", - border_radius="10px", - padding="1em", -) -``` - -```python -import reflex as rx -from collections import Counter - - -@dataclasses.dataclass -class User: - """The user model.""" - - name: str - email: str - gender: str - - -class State(rx.State): - users: list[User] = [ - User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), - User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), - ] - users_for_graph: list[dict] = [] - - def add_user(self, form_data: dict): - self.users.append(User(**form_data)) - self.transform_data() - - def transform_data(self): - """Transform user gender group data into a format suitable for visualization in graphs.""" - # Count users of each gender group - gender_counts = Counter(user.gender for user in self.users) - - # Transform into list of dict so it can be used in the graph - self.users_for_graph = [ - {"name": gender_group, "value": count} - for gender_group, count in gender_counts.items() - ] - - -def show_user(user: User): - """Show a user in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.gender), - style={"_hover": {"bg": rx.color("gray", 3)}}, - align="center", - ) - - -def add_customer_button() -> rx.Component: - return rx.dialog.root( - rx.dialog.trigger( - rx.button( - rx.icon("plus", size=26), - rx.text("Add User", size="4"), - ), - ), - rx.dialog.content( - rx.dialog.title( - "Add New User", - ), - rx.dialog.description( - "Fill the form with the user's info", - ), - rx.form( - rx.flex( - rx.input(placeholder="User Name", name="name", required=True), - rx.input( - placeholder="user@reflex.dev", - name="email", - ), - rx.select( - ["Male", "Female"], - placeholder="male", - name="gender", - ), - rx.flex( - rx.dialog.close( - rx.button( - "Cancel", - variant="soft", - color_scheme="gray", - ), - ), - rx.dialog.close( - rx.button("Submit", type="submit"), - ), - spacing="3", - justify="end", - ), - direction="column", - spacing="4", +rx.box( + rx.vstack( + rx.hstack( + rx.vstack( + rx.text( + "Users", + size="4", + weight="bold", + color=rx.color("slate", 12), + text_align="left", + width="100%", ), - on_submit=State.add_user, - reset_on_submit=False, + rx.text( + "Add customers and watch the chart update.", + size="2", + color=rx.color("slate", 10), + text_align="left", + width="100%", + ), + spacing="1", + align="start", ), - max_width="450px", - ), - ) - - -def graph(): - return rx.recharts.bar_chart( - rx.recharts.bar( - data_key="value", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), + rx.spacer(), + add_customer_button5(), + align="center", + width="100%", ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), - data=State.users_for_graph, - width="100%", - height=250, - ) - - -def index() -> rx.Component: - return rx.vstack( - add_customer_button(), rx.table.root( rx.table.header( rx.table.row( @@ -298,55 +194,41 @@ def index() -> rx.Component: ), ), rx.table.body( - rx.foreach(State.users, show_user), + rx.foreach(State5.users, show_user5), ), variant="surface", - size="3", + size="2", width="100%", ), - graph(), - align="center", + graph5(), + align="stretch", width="100%", - ) - - -app = rx.App( - theme=rx.theme(radius="full", accent_color="grass"), -) - -app.add_page( - index, - title="Customer Data App", - description="A simple app to manage customer data.", - on_load=State.transform_data, + on_mouse_enter=State5.transform_data, + spacing="4", + padding="1.75em 2em", + ), + border=f"1px solid {rx.color('slate', 5)}", + border_radius="12px", + margin_y="1em", + background=rx.color("slate", 1), ) ``` -Don't worry if you don't understand the code above, in this tutorial we are going to walk you through the whole thing step by step. - -## Setup for the tutorial +## Setup -Check out the [installation docs](/docs/getting_started/installation) to get Reflex set up on your machine. Follow these to create a folder called `dashboard_tutorial`, which you will `cd` into, then run `uv init` and `uv add reflex`. - -We will choose template `0` when we run `uv run reflex init` to get the blank template. Finally run `uv run reflex run` to start the app and confirm everything is set up correctly. +1. [Install Reflex](/docs/getting_started/installation) if you haven't already. +2. Create a folder called `dashboard_tutorial` and `cd` into it. +3. Run `uv init` and `uv add reflex`. +4. Run `uv run reflex init` and choose template `0` (the blank template). +5. Run `uv run reflex run` to start the app and confirm everything works. ## Overview -Now that you’re set up, let’s get an overview of Reflex! - -### Inspecting the starter code - -Within our `dashboard_tutorial` folder we just `cd`'d into, there is a `rxconfig.py` file that contains the configuration for our Reflex app. (Check out the [config docs](/docs/advanced_onboarding/configuration) for more information) - -There is also an `assets` folder where static files such as images and stylesheets can be placed to be referenced within your app. ([asset docs](/docs/assets/overview) for more information) - -Most importantly there is a folder also called `dashboard_tutorial` which contains all the code for your app. Inside of this folder there is a file named `dashboard_tutorial.py`. To begin this tutorial we will delete all the code in this file so that we can start from scratch and explain every step as we go. +### Starter code -The first thing we need to do is import `reflex`. Once we have done this we can create a component, which is a reusable piece of user interface code. Components are used to render, manage, and update the UI elements in your application. +The `reflex init` command scaffolds an `rxconfig.py` (app [config](/docs/advanced_onboarding/configuration)), an `assets/` folder for static files, and a `dashboard_tutorial/dashboard_tutorial.py` module containing your app. Open that module and replace its contents — we'll build the app up from scratch. -Let's look at the example below. Here we have a function called `index` that returns a `text` component (an in-built Reflex UI component) that displays the text "Hello World!". - -Next we define our app using `app = rx.App()` and add the component we just defined (`index`) to a page using `app.add_page(index)`. The function name (in this example `index`) which defines the component, must be what we pass into the `add_page`. The definition of the app and adding a component to a page are required for every Reflex app. +A minimal Reflex page is just a component function plus an app that registers it: ```python import reflex as rx @@ -360,50 +242,17 @@ app = rx.App() app.add_page(index) ``` -This code will render a page with the text "Hello World!" when you run your app like below: - -```python eval -rx.text("Hello World!", border_width="2px", border_radius="10px", padding="1em") -``` - ```md alert info -For the rest of the tutorial the `app=rx.App()` and `app.add_page` will be implied and not shown in the code snippets. +For the rest of the tutorial the `app = rx.App()` and `app.add_page` lines are implied and not shown — we'll come back to them in [Customize](#customize). ``` -### Creating a table +### Create a table -Let's create a new component that will render a table. We will use the `table` component to do this. The `table` component has a `root`, which takes in a `header` and a `body`, which in turn take in `row` components. The `row` component takes in `cell` components which are the actual data that will be displayed in the table. +The `rx.table` component has a `root` that wraps a `header` and a `body`. The header takes `row` → `column_header_cell` components; the body takes `row` → `cell` components holding the actual data. Props like `variant` and `size` customize the look: ```python eval -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("Gender"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.cell("Danilo Sousa"), - rx.table.cell("danilo@example.com"), - rx.table.cell("Male"), - ), - rx.table.row( - rx.table.cell("Zahra Ambessa"), - rx.table.cell("zahra@example.com"), - rx.table.cell("Female"), - ), - ), - border_width="2px", - border_radius="10px", - padding="1em", -) -``` - -```python -def index() -> rx.Component: - return rx.table.root( +rx.box( + rx.table.root( rx.table.header( rx.table.row( rx.table.column_header_cell("Name"), @@ -423,39 +272,12 @@ def index() -> rx.Component: rx.table.cell("Female"), ), ), - ) -``` - -Components in Reflex have `props`, which can be used to customize the component and are passed in as keyword arguments to the component function. - -The `rx.table.root` component has for example the `variant` and `size` props, which customize the table as seen below. - -```python eval -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("Gender"), - ), - ), - rx.table.body( - rx.table.row( - rx.table.cell("Danilo Sousa"), - rx.table.cell("danilo@example.com"), - rx.table.cell("Male"), - ), - rx.table.row( - rx.table.cell("Zahra Ambessa"), - rx.table.cell("zahra@example.com"), - rx.table.cell("Female"), - ), + variant="surface", + size="3", ), - variant="surface", - size="3", - border_width="2px", - border_radius="10px", - padding="1em", + border=f"1px solid {rx.color('slate', 5)}", + border_radius="12px", + padding="2em", ) ``` @@ -486,85 +308,38 @@ def index() -> rx.Component: ) ``` -## Showing dynamic data (State) - -Up until this point all the data we are showing in the app is static. This is not very useful for a data dashboard. We need to be able to show dynamic data that can be added to and updated. +## Dynamic data with State -This is where `State` comes in. `State` is a Python class that stores variables that can change when the app is running, as well as the functions that can change those variables. +The table above is static — the rows are hardcoded. To make it dynamic, we move the data onto **state**: a Python class whose fields ([state vars](/docs/state/overview)) hold the app's data and whose methods ([event handlers](/docs/events/events_overview)) mutate them. -To define a state class, subclass `rx.State` and define fields that store the state of your app. The state variables (vars) should have a type annotation, and can be initialized with a default value. Check out the [basics](/docs/getting_started/basics) section for a simple example of how state works. - -In the example below we define a `State` class called `State` that has a variable called `users` that is a list of lists of strings. Each list in the `users` list represents a user and contains their name, email and gender. +We'll model each row as a `User` dataclass so we can access fields by name (`user.name`) instead of by index: ```python -class State(rx.State): - users: list[list[str]] = [ - ["Danilo Sousa", "danilo@example.com", "Male"], - ["Zahra Ambessa", "zahra@example.com", "Female"], - ] -``` - -To iterate over a state var that is a list, we use the [`rx.foreach`](/docs/components/rendering_iterables) function to render a list of components. The `rx.foreach` component takes an `iterable` (list, tuple or dict) and a `function` that renders each item in the `iterable`. +import dataclasses -```md alert info -# Why can we not just splat this in a `for` loop -You might be wondering why a `foreach` is even needed to render this state variable and why we cannot just splat a `for` loop. Check out this [documentation]() to learn why. -``` +@dataclasses.dataclass +class User: + name: str + email: str + gender: str -Here the render function is `show_user` which takes in a single user and returns a `table.row` component that displays the users name, email and gender. -```python exec -class State1(rx.State): - users: list[list[str]] = [ - ["Danilo Sousa", "danilo@example.com", "Male"], - ["Zahra Ambessa", "zahra@example.com", "Female"], +class State(rx.State): + users: list[User] = [ + User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), + User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), ] - - -def show_user1(person: list): - """Show a person in a table row.""" - return rx.table.row( - rx.table.cell(person[0]), - rx.table.cell(person[1]), - rx.table.cell(person[2]), - ) ``` -```python eval -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("Gender"), - ), - ), - rx.table.body( - rx.foreach(State1.users, show_user1), - ), - variant="surface", - size="3", - border_width="2px", - border_radius="10px", - padding="1em", -) -``` +To iterate a list state var, use [`rx.foreach`](/docs/components/rendering_iterables) — it takes an iterable and a function that renders each item. Here `show_user` receives a `User` and returns a `table.row`: ```python -class State(rx.State): - users: list[list[str]] = [ - ["Danilo Sousa", "danilo@example.com", "Male"], - ["Zahra Ambessa", "zahra@example.com", "Female"], - ] - - -def show_user(person: list): - """Show a person in a table row.""" +def show_user(user: User) -> rx.Component: return rx.table.row( - rx.table.cell(person[0]), - rx.table.cell(person[1]), - rx.table.cell(person[2]), + rx.table.cell(user.name), + rx.table.cell(user.email), + rx.table.cell(user.gender), ) @@ -585,17 +360,11 @@ def index() -> rx.Component: ) ``` -As you can see the output above looks the same as before, except now the data is no longer static and can change with user input to the app. - -### Using a proper class structure for our data - -So far our data has been defined in a list of lists, where the data is accessed by index i.e. `user[0]`, `user[1]`. This is not very maintainable as our app gets bigger. - -A better way to structure our data in Reflex is to use a class to represent a user. This way we can access the data using attributes i.e. `user.name`, `user.email`. - -In Reflex when we create these classes to showcase our data, we can use dataclasses. +```md alert info +# Why not a `for` loop? -The `show_user` render function is also updated to access the data by named attributes, instead of indexing. +A regular `for` loop runs at compile time, but state vars change at runtime — so the rendered rows wouldn't update. `rx.foreach` tells the compiler to re-render when the state var changes. See [compile-time vs runtime](/docs/getting_started/basics#compile-time-vs.-runtime). +``` ```python exec import dataclasses @@ -627,53 +396,8 @@ def show_user2(user: User): ``` ```python eval -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("Gender"), - ), - ), - rx.table.body( - rx.foreach(State2.users, show_user2), - ), - variant="surface", - size="3", - border_width="2px", - border_radius="10px", - padding="1em", -) -``` - -```python -@dataclasses.dataclass -class User: - """The user model.""" - - name: str - email: str - gender: str - - -class State(rx.State): - users: list[User] = [ - User(name="Danilo Sousa", email="danilo@example.com", gender="Male"), - User(name="Zahra Ambessa", email="zahra@example.com", gender="Female"), - ] - - -def show_user(user: User): - """Show a person in a table row.""" - return rx.table.row( - rx.table.cell(user.name), - rx.table.cell(user.email), - rx.table.cell(user.gender), - ) - - -def index() -> rx.Component: - return rx.table.root( +rx.box( + rx.table.root( rx.table.header( rx.table.row( rx.table.column_header_cell("Name"), @@ -682,16 +406,20 @@ def index() -> rx.Component: ), ), rx.table.body( - rx.foreach(State.users, show_user), + rx.foreach(State2.users, show_user2), ), variant="surface", size="3", - ) + ), + border=f"1px solid {rx.color('slate', 5)}", + border_radius="12px", + padding="2em", +) ``` -Next let's add a form to the app so we can add new users to the table. +The table looks the same, but the rows now come from state — next we'll add a form that appends to `State.users` so new rows appear automatically. -## Using a Form to Add Data +## Add data with a form We build a form using `rx.form`, which takes several components such as `rx.input` and `rx.select`, which represent the form fields that allow you to add information to submit with the form. Check out the [form](/docs/library/forms/form) docs for more information on form components. @@ -835,9 +563,10 @@ rx.vstack( variant="surface", size="3", ), - border_width="2px", - border_radius="10px", - padding="1em", + spacing="4", + border=f"1px solid {rx.color('slate', 5)}", + border_radius="12px", + padding="2em", ) ``` @@ -901,7 +630,7 @@ def index() -> rx.Component: ) ``` -### Putting the Form in an Overlay +### Put the form in a dialog In Reflex, we like to make the user interaction as intuitive as possible. Placing the form we just constructed in an overlay creates a focused interaction by dimming the background, and ensures a cleaner layout when you have multiple action points such as editing and deleting as well. @@ -1061,9 +790,10 @@ rx.vstack( variant="surface", size="3", ), - border_width="2px", - border_radius="10px", - padding="1em", + spacing="4", + border=f"1px solid {rx.color('slate', 5)}", + border_radius="12px", + padding="2em", ) ``` @@ -1168,11 +898,11 @@ def index() -> rx.Component: ) ``` -## Plotting Data in a Graph +## Plot a graph -The last part of this tutorial is to plot the user data in a graph. We will use Reflex's built-in graphing library recharts to plot the number of users of each gender. +Next we'll plot the user data in a graph using Reflex's built-in recharts library, counting users by gender. -### Transforming the data for the graph +### Transform the data The graphing components in Reflex expect to take in a list of dictionaries. Each dictionary represents a data point on the graph and contains the x and y values. We will create a new event handler in the state called `transform_data` to transform the user data into the format that the graphing components expect. We must also create a new state variable called `users_for_graph` to store the transformed data, which will be used to render the graph. @@ -1204,7 +934,7 @@ As we can see above the `transform_data` event handler uses the `Counter` class Finally we can see that whenever we add a new user through submitting the form and running the `add_user` event handler, we call the `transform_data` event handler to update the `users_for_graph` state variable. -### Rendering the graph +### Render the graph We use the `rx.recharts.bar_chart` component to render the graph. We pass through the state variable for our graphing data as `data=State.users_for_graph`. We also pass in a `rx.recharts.bar` component which represents the bars on the graph. The `rx.recharts.bar` component takes in the `data_key` prop which is the key in the data dictionary that represents the y value of the bar. The `stroke` and `fill` props are used to set the color of the bars. @@ -1345,9 +1075,10 @@ rx.vstack( size="3", ), graph(), - border_width="2px", - border_radius="10px", - padding="1em", + spacing="4", + border=f"1px solid {rx.color('slate', 5)}", + border_radius="12px", + padding="2em", ) ``` @@ -1475,11 +1206,11 @@ def index() -> rx.Component: ) ``` -One thing you may have noticed about your app is that the graph does not appear initially when you run the app, and that you must add a user to the table for it to first appear. This occurs because the `transform_data` event handler is only called when a user is added to the table. In the next section we will explore a solution to this. +If you run the app locally with no seed users, the graph is empty until you add one — `transform_data` only runs when a user is added. The next section fixes that by calling it on page load. -## Final Cleanup +## Customize -### Revisiting app.add_page +### Revisit `app.add_page` At the beginning of this tutorial we mentioned that the `app.add_page` function is required for every Reflex app. This function is used to add a component to a page. @@ -1508,9 +1239,10 @@ rx.vstack( ), graph(), on_mouse_enter=State4.transform_data, - border_width="2px", - border_radius="10px", - padding="1em", + spacing="4", + border=f"1px solid {rx.color('slate', 5)}", + border_radius="12px", + padding="2em", ) ``` @@ -1523,7 +1255,7 @@ app.add_page( ) ``` -### Revisiting app=rx.App() +### Revisit `rx.App()` At the beginning of the tutorial we also mentioned that we defined our app using `app=rx.App()`. We can also pass in some props to the `rx.App` component to customize the app. @@ -1531,7 +1263,7 @@ The most important one is `theme` which allows you to customize the look and fee The `radius` prop sets the global radius value for the app that is inherited by all components that have a `radius` prop. It can be overwritten locally for a specific component by manually setting the `radius` prop. -The `accent_color` prop sets the accent color of the app. Check out other options for the accent color [here](/docs/library/other/theme). +The `accent_color` prop sets the accent color of the app. See the [theme docs](/docs/library/other/theme) for the full list of options. To see other props that can be set at the app level check out this [documentation](/docs/styling/theming) @@ -1541,41 +1273,70 @@ app = rx.App( ) ``` -Unfortunately in this tutorial here we cannot actually apply this to the live example on the page, but if you copy and paste the code below into a reflex app locally you can see it in action. +The theme applies at the app level, so you'll need to run locally to see it in action. -## Conclusion +## Full app styled -Finally let's make some final styling updates to our app. We will add some hover styling to the table rows and center the table inside the `show_user` with `style=\{"_hover": \{"bg": rx.color("gray", 3)}}, align="center"`. +Finally let's make some styling updates. We will add hover styling to the table rows and center the table inside `show_user` with `style={"_hover": {"bg": rx.color("gray", 3)}}, align="center"`. In addition, we will add some `width="100%"` and `align="center"` to the `index()` component to center the items on the page and ensure they stretch the full width of the page. Check out the full code and interactive app below: ```python eval -rx.vstack( - add_customer_button5(), - 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("Gender"), +rx.box( + rx.vstack( + rx.hstack( + rx.vstack( + rx.text( + "Users", + size="4", + weight="bold", + color=rx.color("slate", 12), + text_align="left", + width="100%", + ), + rx.text( + "Add customers and watch the chart update.", + size="2", + color=rx.color("slate", 10), + text_align="left", + width="100%", + ), + spacing="1", + align="start", ), + rx.spacer(), + add_customer_button5(), + align="center", + width="100%", ), - rx.table.body( - rx.foreach(State5.users, show_user5), + 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("Gender"), + ), + ), + rx.table.body( + rx.foreach(State5.users, show_user5), + ), + variant="surface", + size="2", + width="100%", ), - variant="surface", - size="3", + graph5(), + align="stretch", width="100%", + on_mouse_enter=State5.transform_data, + spacing="4", + padding="1.75em 2em", ), - graph5(), - align="center", - width="100%", - on_mouse_enter=State5.transform_data, - border_width="2px", - border_radius="10px", - padding="1em", + border=f"1px solid {rx.color('slate', 5)}", + border_radius="12px", + margin_y="1em", + background=rx.color("slate", 1), ) ``` @@ -1683,38 +1444,86 @@ def graph(): return rx.recharts.bar_chart( rx.recharts.bar( data_key="value", - stroke=rx.color("accent", 9), - fill=rx.color("accent", 8), + fill=rx.color("accent", 9), + radius=6, + bar_size=48, + ), + rx.recharts.x_axis( + data_key="name", + tick_line=False, + axis_line=False, + padding={"left": 24, "right": 24}, + ), + rx.recharts.y_axis( + tick_line=False, + axis_line=False, + allow_decimals=False, + ), + rx.recharts.cartesian_grid( + stroke_dasharray="3 3", + vertical=False, + stroke=rx.color("slate", 4), ), - rx.recharts.x_axis(data_key="name"), - rx.recharts.y_axis(), data=State.users_for_graph, width="100%", - height=250, + height=200, + margin={"top": 8, "right": 8, "bottom": 0, "left": 0}, ) def index() -> rx.Component: - return rx.vstack( - add_customer_button(), - 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("Gender"), + return rx.box( + rx.vstack( + rx.hstack( + rx.vstack( + rx.text( + "Users", + size="4", + weight="bold", + color=rx.color("slate", 12), + text_align="left", + width="100%", + ), + rx.text( + "Add customers and watch the chart update.", + size="2", + color=rx.color("slate", 10), + text_align="left", + width="100%", + ), + spacing="1", + align="start", ), + rx.spacer(), + add_customer_button(), + align="center", + width="100%", ), - rx.table.body( - rx.foreach(State.users, show_user), + 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("Gender"), + ), + ), + rx.table.body( + rx.foreach(State.users, show_user), + ), + variant="surface", + size="2", + width="100%", ), - variant="surface", - size="3", + graph(), + align="stretch", width="100%", + spacing="4", + padding="1.75em 2em", ), - graph(), - align="center", - width="100%", + border=f"1px solid {rx.color('slate', 5)}", + border_radius="12px", + margin_y="1em", + background=rx.color("slate", 1), ) @@ -1730,19 +1539,16 @@ app.add_page( ) ``` -And that is it for your first dashboard tutorial. In this tutorial we have created - -- a table to display user data -- a form to add new users to the table -- a dialog to showcase the form -- a graph to visualize the user data +## Recap -In addition to the above we have we have +You built: -- explored state to allow you to show dynamic data that changes over time -- explored events to allow you to make your app interactive and respond to user actions -- added styling to the app to make it look better +- A table that displays user data. +- A form (inside a dialog) to add new users. +- A bar chart that visualizes the distribution. -## Advanced Section (Hooking this up to a Database) +Along the way you learned: -Coming Soon! +- **State** — how to store data that changes over time. +- **Events** — how to respond to user actions and update the UI. +- **Styling** — tweaking theme, layout, and hover states. diff --git a/docs/getting_started/installation.md b/docs/getting_started/installation.md index ac981feefd6..441f9e4e1a5 100644 --- a/docs/getting_started/installation.md +++ b/docs/getting_started/installation.md @@ -1,35 +1,18 @@ -```python exec -import reflex as rx - -app_name = "my_app_name" -default_url = "http://localhost:3000" -``` - # Installation -Reflex requires Python 3.10+. - -```md video https://youtube.com/embed/ITOZkzjtjUA?start=758&end=1206 -# Video: Installation -``` +~3 minutes · Requires Python 3.10+. ## Virtual Environment -We **highly recommend** creating a virtual environment for your project. - -[uv](https://docs.astral.sh/uv/) is the recommended modern option. [venv](https://docs.python.org/3/library/venv.html), [conda](https://conda.io/) and [poetry](https://python-poetry.org/) are some alternatives. +We recommend [uv](https://docs.astral.sh/uv/) as the default; [venv](https://docs.python.org/3/library/venv.html), [conda](https://conda.io/), and [poetry](https://python-poetry.org/) are alternatives. -# Install Reflex on your system +## Install Reflex on your system `````md tabs ## macOS/Linux -We will go with [uv](https://docs.astral.sh/uv/) here. - -### Prerequisites - -#### Install uv +### Install uv ```bash curl -LsSf https://astral.sh/uv/install.sh | sh @@ -39,51 +22,30 @@ After installation, restart your terminal or run `source ~/.bashrc` (or `source Alternatively, install via [Homebrew, PyPI, or other methods](https://docs.astral.sh/uv/getting-started/installation/). -**macOS (Apple Silicon) users:** Install [Rosetta 2](https://support.apple.com/en-us/HT211861). Run this command: - -`/usr/sbin/softwareupdate --install-rosetta --agree-to-license` - -### Create the project directory - -Replace `{app_name}` with your project name. Switch to the new directory. +```md alert warning +# macOS (Apple Silicon) users: install Rosetta 2 -```bash -mkdir {app_name} -cd {app_name} +Run `/usr/sbin/softwareupdate --install-rosetta --agree-to-license`. See [Apple's instructions](https://support.apple.com/en-us/HT211861) for details. ``` -### Initialize uv project - -```bash -uv init -``` +### Set up the Reflex project -### Add Reflex to the project +Replace `` with your project name, then switch into the new directory. ```bash +mkdir +cd +uv init uv add reflex -``` - -### Initialize the Reflex project - -```bash uv run reflex init ``` ## Windows -For Windows users, we recommend using [Windows Subsystem for Linux (WSL)](https://learn.microsoft.com/en-us/windows/wsl/about) for optimal performance. +For Windows users, we recommend [WSL](https://learn.microsoft.com/en-us/windows/wsl/about) for optimal performance — WSL users should follow the macOS/Linux tab; the rest of this section covers native Windows. -**WSL users:** Refer to the macOS/Linux instructions above. - -For the rest of this section we will work with native Windows (non-WSL). - -We will go with [uv](https://docs.astral.sh/uv/) here. - -### Prerequisites - -#### Install uv +### Install uv ```powershell powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" @@ -93,30 +55,15 @@ After installation, restart your terminal (PowerShell or Command Prompt). Alternatively, install via [WinGet, Scoop, or other methods](https://docs.astral.sh/uv/getting-started/installation/). -### Create the project directory +### Set up the Reflex project -Replace `{app_name}` with your project name. Switch to the new directory. +Replace `` with your project name, then switch into the new directory. -```bash -mkdir {app_name} -cd {app_name} -``` - -### Initialize uv project - -```bash +```powershell +mkdir +cd uv init -``` - -### Add Reflex to the project - -```bash uv add reflex -``` - -### Initialize the Reflex project - -```bash uv run reflex init ``` @@ -127,6 +74,7 @@ Bun requires runtime components of Visual C++ libraries to run on Windows. This ``` ````` + Running `uv run reflex init` will return the option to start with a blank Reflex app, premade templates built by the Reflex team, or to try our [AI builder](https://build.reflex.dev/). ```bash @@ -139,7 +87,7 @@ Get started with a template: Which template would you like to use? (0): ``` -From here select an option. +If this is your first time, pick **(0) A blank Reflex app** — the rest of the docs assume you started there. ## Run the App @@ -149,12 +97,16 @@ Run it in development mode: uv run reflex run ``` -Your app runs at [http://localhost:3000](http://localhost:3000). +Your app runs at [http://localhost:3000](http://localhost:3000). Reflex _hot reloads_ any code changes in real time — your edits show up automatically. -Reflex prints logs to the terminal. To increase log verbosity to help with debugging, use the `--loglevel` flag: +For troubleshooting, increase log verbosity with the `--loglevel` flag: ```bash uv run reflex run --loglevel debug ``` -Reflex will _hot reload_ any code changes in real time when running in development mode. Your code edits will show up on [http://localhost:3000](http://localhost:3000) automatically. +```md alert info +# Next: Build your first app → + +Reflex is installed. The [Introduction](/docs/getting-started/introduction) walks through a working counter app in pure Python — the shortest path from "it runs" to "I understand it." +``` diff --git a/docs/getting_started/introduction.md b/docs/getting_started/introduction.md index ca5c5e24ad8..98db68866b0 100644 --- a/docs/getting_started/introduction.md +++ b/docs/getting_started/introduction.md @@ -4,33 +4,31 @@ import reflex as rx # Introduction -**Reflex** is an open-source framework for quickly building beautiful, interactive web applications in **pure Python**. +**~5 min** · **Reflex** lets you build and deploy full-stack web apps — frontend, backend, and database — in **pure Python**. No JavaScript, no separate API, no context switching. ## Goals ```md section ### Pure Python -Use Python for everything. Don't worry about learning a new language. +Write your entire app — frontend, backend, database — in Python. No need to learn another language. ### Easy to Learn -Build and share your first app in minutes. No web development experience required. +Ship your first app in minutes. No web development experience required. ### Full Flexibility -Remain as flexible as traditional web frameworks. Reflex is easy to use, yet allows for advanced use cases. - -Build anything from small data science apps to large, multi-page websites. **This entire site was built and deployed with Reflex!** +Build anything from small data apps to large multi-page websites. **This entire site was built and deployed with Reflex.** ### Batteries Included -No need to reach for a bunch of different tools. Reflex handles the user interface, server-side logic, and deployment of your app. +One tool covers it all: UI, server-side logic, and deployment. ``` -## An example: Make it count +## Build a counter -Here, we go over a simple counter app that lets the user count up or down. +We'll build a counter app that lets the user count up or down. In ~20 lines of Python you'll touch the three core pieces of every Reflex app: **state**, **event handlers**, and **components**. ```python exec class CounterExampleState(rx.State): @@ -60,17 +58,14 @@ class IntroTabsState(rx.State): def tabs(): return rx.tabs.root( rx.tabs.list( - rx.tabs.trigger("Frontend", value="tab1", class_name="tab-style"), - rx.tabs.trigger("Backend", value="tab2", class_name="tab-style"), - rx.tabs.trigger("Page", value="tab3", class_name="tab-style"), + rx.tabs.trigger("Frontend", value="tab1", class_name="pill-tab"), + rx.tabs.trigger("Backend", value="tab2", class_name="pill-tab"), + rx.tabs.trigger("Page", value="tab3", class_name="pill-tab"), + class_name="pill-tab-list", ), rx.tabs.content( rx.markdown( - """The frontend is built declaratively using Reflex components. Components are compiled down to JS and served to the users browser, therefore: - -- Only use Reflex components, vars, and var operations when building your UI. Any other logic should be put in your `State` (backend). - -- Use `rx.cond` and `rx.foreach` (replaces if statements and for loops), for creating dynamic UIs. + """The frontend is built declaratively with Reflex components, which compile to JS and run in the browser. Use `rx.cond` and `rx.foreach` instead of `if` and `for` for dynamic UIs. Any non-UI logic belongs in `State`. """, ), value="tab1", @@ -86,9 +81,7 @@ def tabs(): ), rx.tabs.content( rx.markdown( - """Each page is a Python function that returns a Reflex component. You can define multiple pages and navigate between them, see the [Routing](/docs/pages/overview) section for more information. - -- Start with a single page and scale to 100s of pages. + """Each page is a Python function returning a Reflex component. Add as many as you want and link between them — see [Routing](/docs/pages/overview) for details. """, ), value="tab3", @@ -143,12 +136,12 @@ rx.box( self.count -= 1""", background=rx.cond( IntroTabsState.value == "tab2", - "var(--c-violet-3) !important", + "var(--c-slate-3) !important", "transparent", ), border=rx.cond( IntroTabsState.value == "tab2", - "1px solid var(--c-violet-5)", + "1px solid var(--c-slate-5)", "none !important", ), class_name="code-block", @@ -171,12 +164,12 @@ rx.box( )""", border=rx.cond( IntroTabsState.value == "tab1", - "1px solid var(--c-violet-5)", + "1px solid var(--c-slate-5)", "none !important", ), background=rx.cond( IntroTabsState.value == "tab1", - "var(--c-violet-3) !important", + "var(--c-slate-3) !important", "transparent", ), class_name="code-block", @@ -186,12 +179,12 @@ rx.box( app.add_page(index)""", background=rx.cond( IntroTabsState.value == "tab3", - "var(--c-violet-3) !important", + "var(--c-slate-3) !important", "transparent", ), border=rx.cond( IntroTabsState.value == "tab3", - "1px solid var(--c-violet-5)", + "1px solid var(--c-slate-5)", "none !important", ), class_name="code-block", @@ -210,7 +203,7 @@ Let's break this example down. import reflex as rx ``` -We begin by importing the `reflex` package (aliased to `rx`). We reference Reflex objects as `rx.*` by convention. +Import Reflex as `rx`. All Reflex objects are accessed as `rx.*`. ### State @@ -219,9 +212,7 @@ class State(rx.State): count: int = 0 ``` -The state defines all the variables (called **[vars](/docs/vars/base_vars)**) in an app that can change, as well as the functions (called **[event_handlers](#event-handlers)**) that change them. - -Here our state has a single var, `count`, which holds the current value of the counter. We initialize it to `0`. +State holds the app's mutable data. Variables declared here are called **[vars](/docs/vars/base_vars)**. Our counter has one: `count`, starting at `0`. ### Event Handlers @@ -236,13 +227,7 @@ def decrement(self): self.count -= 1 ``` -Within the state, we define functions, called **event handlers**, that change the state vars. - -Event handlers are the only way that we can modify the state in Reflex. -They can be called in response to user actions, such as clicking a button or typing in a text box. -These actions are called **events**. - -Our counter app has two event handlers, `increment` and `decrement`. +**Event handlers** are the only way to modify state. They're triggered by user actions (clicks, typing, etc.) — those actions are called **events**. Our counter has two handlers: `increment` and `decrement`. ### User Interface (UI) @@ -264,70 +249,44 @@ def index(): ) ``` -This function defines the app's user interface. - -We use different components such as `rx.hstack`, `rx.button`, and `rx.heading` to build the frontend. Components can be nested to create complex layouts, and can be styled using the full power of CSS or [Tailwind CSS](/docs/styling/tailwind). - -Reflex comes with [50+ built-in components](/docs/library) to help you get started. -We are actively adding more components. Also, it's easy to [wrap your own React components](/docs/wrapping-react/overview). - -```python -(rx.heading(State.count, font_size="2em"),) -``` - -Components can reference the app's state vars. -The `rx.heading` component displays the current value of the counter by referencing `State.count`. -All components that reference state will reactively update whenever the state changes. - -```python -( - rx.button( - "Decrement", - color_scheme="ruby", - on_click=State.decrement, - ), -) -``` - -Components interact with the state by binding events triggers to event handlers. -For example, `on_click` is an event that is triggered when a user clicks a component. +The UI is built from components (`rx.hstack`, `rx.button`, `rx.heading`) that can be nested and styled with CSS or [Tailwind](/docs/styling/tailwind). Reflex ships with [50+ built-in components](/docs/library), and you can [wrap any React component](/docs/wrapping-react/overview). -The first button in our app binds its `on_click` event to the `State.decrement` event handler. Similarly the second button binds `on_click` to `State.increment`. +Components reference state vars (`rx.heading(State.count, …)`) and reactively re-render when state changes. Event triggers (`on_click=State.decrement`) wire UI to handlers. -In other words, the sequence goes like this: +The sequence goes like this: -- User clicks "increment" on the UI. -- `on_click` event is triggered. -- Event handler `State.increment` is called. -- `State.count` is incremented. -- UI updates to reflect the new value of `State.count`. +1. User clicks "Increment". +2. `on_click` fires. +3. `State.increment` runs on the server. +4. `State.count` is updated. +5. UI re-renders with the new value. ### Add pages -Next we define our app and add the counter component to the base route. - ```python app = rx.App() app.add_page(index) ``` -## Next Steps - -🎉 And that's it! +Create the app and register the page at the base route. -We've created a simple, yet fully interactive web app in pure Python. +## Next Steps -By continuing with our documentation, you will learn how to build awesome apps with Reflex. Use the sidebar to navigate through the sections, or search (`Ctrl+K` or `Cmd+K`) to quickly find a page. +🎉 You've built a fully interactive web app in pure Python. -For a glimpse of the possibilities, check out these resources: +```md alert info +# Keep learning → -- For a more real-world example, check out either the [dashboard tutorial](/docs/getting_started/dashboard_tutorial) or the [chatapp tutorial](/docs/getting_started/chatapp_tutorial). -- Check out our open-source [templates](/docs/getting_started/open_source_templates)! -- We have an AI Builder that can generate full Reflex apps or help with your existing app! Check it out at [Reflex Build](https://build.reflex.dev/)! -- Deploy your app with a single command using [Reflex Cloud](https://reflex.dev/docs/hosting/deploy-quick-start/)! +- [Dashboard tutorial](/docs/getting_started/dashboard_tutorial) — build a real data app. +- [Chatapp tutorial](/docs/getting_started/chatapp_tutorial) — wire up streaming AI responses. +- [How Reflex works](/docs/advanced_onboarding/how-reflex-works) — what happens under the hood. +``` -If you want to learn more about how Reflex works, check out the [How Reflex Works](/docs/advanced_onboarding/how-reflex-works) section. +```md alert info +# Ship faster with AI → -## Join our Community +- [Reflex Build](https://build.reflex.dev/) — generate a full app from a prompt. +- [Reflex Cloud](https://reflex.dev/docs/hosting/deploy-quick-start/) — one-command deploy. +``` -If you have questions about anything related to Reflex, you're always welcome to ask our community on [GitHub Discussions](https://github.com/orgs/reflex-dev/discussions), [Discord](https://discord.gg/T5WSbC2YtQ), [Forum](https://forum.reflex.dev), and [X](https://twitter.com/getreflex). +Browse our [open-source templates](/docs/getting_started/open_source_templates), or press `Cmd+K` / `Ctrl+K` to search the docs. diff --git a/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py index df8d6418866..f18568d5912 100644 --- a/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py +++ b/packages/reflex-components-code/src/reflex_components_code/shiki_code_block.py @@ -59,9 +59,9 @@ def copy_script() -> Any: setTimeout(() => { svgIcon.innerHTML = originalPath; // Restore original SVG content transition(svgIcon, 1, '1'); - }, 125); - }, 600); - }, 125); + }, 60); + }, 350); + }, 60); } else { // console.error('SVG element not found within the parent.'); } @@ -803,9 +803,9 @@ def create( "_hover": { "background": color("gray", 4), }, - "transition": "background 0.250s ease-out", + "transition": "background 0.12s ease-out", "&>svg": { - "transition": "transform 0.250s ease-out, opacity 0.250s ease-out", + "transition": "transform 0.12s ease-out, opacity 0.12s ease-out", }, "_active": { "background": color("gray", 5), diff --git a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/code.py b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/code.py index 91f96bd6f4e..213909e6b0f 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/code.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/code.py @@ -4,10 +4,13 @@ import reflex_site_shared.styles.fonts as fonts from reflex_site_shared import styles +EXPAND_THRESHOLD_LINES = 20 +COLLAPSED_MAX_HEIGHT = "400px" + @rx.memo -def code_block(code: str, language: str): - """Code block. +def _plain_code_block(code: str, language: str): + """Shared plain code block implementation. Returns: The component. @@ -23,6 +26,60 @@ def code_block(code: str, language: str): ) +def code_block(code: str, language: str): + """Code block. Shows an Expand/Collapse toggle when longer than the threshold. + + Returns: + The component. + """ + # During import-graph introspection ``code`` may be a Var, not a Python str. + # Skip the line-count check in that case and render the plain block. + if not isinstance(code, str): + return _plain_code_block(code=code, language=language) + if code.count("\n") + 1 > EXPAND_THRESHOLD_LINES: + return rx.el.div( + _plain_code_block(code=code, language=language), + rx.el.details( + rx.el.summary( + rx.el.span( + "Expand", + rx.icon( + "chevron-down", + size=14, + class_name="inline-block align-[-2px] ml-1", + ), + class_name="group-open/details:hidden", + ), + rx.el.span( + "Collapse", + rx.icon( + "chevron-up", + size=14, + class_name="inline-block align-[-2px] ml-1", + ), + class_name="hidden group-open/details:inline", + ), + class_name=( + "list-none cursor-pointer text-center text-sm font-medium " + "text-[var(--c-slate-11)] hover:text-[var(--c-slate-12)] " + "pt-12 pb-3 rounded-b-xl " + "bg-gradient-to-t from-[var(--c-slate-2)] from-55% to-transparent " + "group-open/details:pt-3 group-open/details:bg-none " + "[&::-webkit-details-marker]:hidden [&::marker]:hidden" + ), + ), + class_name="group/details absolute bottom-0 left-0 right-0", + ), + class_name=( + "relative max-h-[400px] overflow-hidden mt-4 mb-4 rounded-xl " + "border border-[var(--c-slate-4)] bg-[var(--c-slate-2)] " + "[&_.code-block]:!border-0 " + "has-[details[open]]:max-h-none" + ), + ) + return _plain_code_block(code=code, language=language) + + @rx.memo def code_block_dark(code: str, language: str): """Code block dark. diff --git a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/collapsible.py b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/collapsible.py index 12027e779fe..c15b7383eca 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/collapsible.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/collapsible.py @@ -13,12 +13,17 @@ def collapsible_box( color: ColorType, *, item_border_radius: str = "12px", + background_shade: int = 3, + background_override: str | None = None, + border_override: str | None = None, ) -> rx.Component: """Collapsible accordion wrapper shared by alert and video directives. Returns: The component. """ + bg = background_override or f"{rx.color(color, background_shade)}" + border_color = border_override or f"{rx.color(color, 4)}" return rx.box( rx.accordion.root( rx.accordion.item( @@ -44,7 +49,7 @@ def collapsible_box( body, border_radius=item_border_radius, padding=["16px", "24px"], - background_color=f"{rx.color(color, 3)}", + background_color=bg, border="none", ), background="transparent !important", @@ -53,9 +58,9 @@ def collapsible_box( collapsible=True, width="100%", ), - border=f"1px solid {rx.color(color, 4)}", + border=f"1px solid {border_color}", border_radius="12px", - background_color=f"{rx.color(color, 3)} !important", + background_color=f"{bg} !important", width="100%", margin_bottom="16px", margin_top="16px", diff --git a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/headings.py b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/headings.py index d092c7e33fc..47812c37e85 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/headings.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/headings.py @@ -136,7 +136,7 @@ def create( underline="none", href=href, on_click=lambda: rx.set_clipboard(href), - class_name="flex flex-row items-center gap-6 hover:!text-violet-11 cursor-pointer mb-6 transition-colors group text-m-slate-12 dark:text-m-slate-3 ", + class_name="flex flex-row items-center gap-2 hover:!text-violet-11 cursor-pointer mb-3 transition-colors group text-m-slate-12 dark:text-m-slate-3 ", ) @@ -153,7 +153,7 @@ def h1_comp(text: str) -> rx.Component: return h_comp_common( text=text, heading="h1", - class_name="lg:text-5xl text-3xl font-[525]", + class_name="lg:text-4xl text-3xl font-semibold", ) @@ -167,7 +167,7 @@ def h1_comp_xd(text: str) -> rx.Component: return h_comp_common( text=text, heading="h1", - class_name="lg:text-5xl text-3xl font-[525]", + class_name="lg:text-4xl text-3xl font-semibold", ) @@ -182,7 +182,7 @@ def h2_comp(text: str) -> rx.Component: text=text, heading="h2", mt="8", - class_name="lg:text-4xl text-2xl font-[525]", + class_name="lg:text-3xl text-2xl font-semibold", ) @@ -197,7 +197,7 @@ def h2_comp_xd(text: str) -> rx.Component: text=text, heading="h2", mt="8", - class_name="lg:text-3xl text-2xl font-[525]", + class_name="lg:text-2xl text-xl font-semibold", ) @@ -212,7 +212,7 @@ def h3_comp(text: str) -> rx.Component: text=text, heading="h3", mt="4", - class_name="lg:text-2xl text-xl font-[525]", + class_name="lg:text-xl text-lg font-semibold", ) @@ -227,7 +227,7 @@ def h3_comp_xd(text: str) -> rx.Component: text=text, heading="h3", mt="4", - class_name="lg:text-2xl text-lg font-[525]", + class_name="lg:text-xl text-lg font-semibold", ) @@ -242,7 +242,7 @@ def h4_comp(text: str) -> rx.Component: text=text, heading="h4", mt="2", - class_name="lg:text-xl text-lg font-[525]", + class_name="lg:text-base text-base font-semibold", ) @@ -257,7 +257,7 @@ def h4_comp_xd(text: str) -> rx.Component: text=text, heading="h4", mt="2", - class_name="lg:text-xl text-lg font-[525]", + class_name="lg:text-base text-base font-semibold", ) diff --git a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/typography.py b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/typography.py index 2e3fbfa5f36..ccf0ed8edb6 100644 --- a/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/typography.py +++ b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/typography.py @@ -16,7 +16,11 @@ def definition(title: str, *children) -> rx.Component: """ return rx.vstack( rx.heading( - title, font_size="1em", font_weight="bold", color=rx.color("mauve", 12) + title, + as_="h3", + font_size="1em", + font_weight="bold", + color=rx.color("mauve", 12), ), *children, color=rx.color("mauve", 10), @@ -39,7 +43,7 @@ def text_comp(text: rx.Var[str]) -> rx.Component: Returns: The component. """ - return rx.text(text, class_name="font-[475] text-secondary-11 mb-4 leading-7") + return rx.text(text, class_name="font-normal text-secondary-11 mb-4 leading-7") @rx.memo @@ -51,7 +55,7 @@ def text_comp_2(text: rx.Var[str]) -> rx.Component: """ return rx.text( text, - class_name="font-[475] text-secondary-11 max-w-[80%] mb-10", + class_name="font-normal text-secondary-11 max-w-[80%] mb-10", ) @@ -62,7 +66,7 @@ def list_comp(text: rx.Var[str]) -> rx.Component: Returns: The component. """ - return rx.list_item(text, class_name="font-[475] text-secondary-11 mb-4") + return rx.list_item(text, class_name="font-normal text-secondary-11 mb-4") @rx.memo