From f5bdd86fe0c675d9b2292c74004e869b84f40fce Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Fri, 24 Apr 2026 20:15:50 -0700 Subject: [PATCH 01/12] docs: redesign API Reference (interactive demo, code panel, prop table polish) - Lay out the per-component live demo and Python source side-by-side, with syntax highlighting and live-bound prop values driven by the same state vars as the demo (preview source string is parsed so the code reflects what's actually rendered, including child components). - Replace the dropdown / checkbox controls with a compact pill-button panel scoped to styling props (variant, size, color_scheme, radius, high_contrast, loading, disabled). Skip the demo + controls when no styling props apply to the component. - Rework the props table: - Columns: prop | type | default | description (descriptions inline, no longer hidden behind a hover icon). - Literal types render as individual chips that wrap; long unions no longer truncate to "...". - Long rows clamp to ~4 lines with a fade-out mask across all four cells and a centered "Show more / Show less" toggle row beneath them; the row's bottom border is suppressed so the toggle reads as part of the same logical row. - Sidebar / popover dark-mode polish: align the left-sidebar background with the code-block bg, swap the active highlight to slate-3, drop the violet active-text in favor of slate-12, tone down the "Build with AI" violet badge and gradient. - Drop "Copy to markdown" and "Ask AI about this page" from the right-hand TOC sidebar. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/app/reflex_docs/pages/docs/component.py | 503 +++++++++++++----- .../reflex_docs/templates/docpage/docpage.py | 11 +- .../templates/docpage/sidebar/sidebar.py | 6 +- 3 files changed, 392 insertions(+), 128 deletions(-) diff --git a/docs/app/reflex_docs/pages/docs/component.py b/docs/app/reflex_docs/pages/docs/component.py index 77be602c2a8..001eaa71978 100644 --- a/docs/app/reflex_docs/pages/docs/component.py +++ b/docs/app/reflex_docs/pages/docs/component.py @@ -2,6 +2,7 @@ import hashlib import os +import re import textwrap from pathlib import Path from types import UnionType @@ -23,7 +24,7 @@ render_docgen_document, render_markdown, ) -from reflex_docs.templates.docpage import docdemobox, docpage, h1_comp, h2_comp +from reflex_docs.templates.docpage import docdemobox, docpage, h1_comp, h2_comp, h3_comp def get_code_style(color: str): @@ -79,6 +80,40 @@ class PropDocsState(rx.State): ] +_PILL_BTN_CLASS = ( + "font-small cursor-pointer rounded-md px-2.5 py-1 text-slate-11 " + "border border-slate-5 bg-slate-1 hover:bg-slate-3 transition-colors" +) +_PILL_BTN_ACTIVE_CLASS = ( + "font-small cursor-pointer rounded-md px-2.5 py-1 text-slate-12 " + "border border-slate-8 bg-slate-4" +) + + +def _pill_button(label: str, active, on_click) -> rx.Component: + return rx.el.button( + label, + type="button", + on_click=on_click, + class_name=rx.cond(active, _PILL_BTN_ACTIVE_CLASS, _PILL_BTN_CLASS), + ) + + +def _pill_row(values, var, setter) -> rx.Component: + return rx.box( + *[_pill_button(v, var == v, setter(v)) for v in values], + class_name="flex flex-wrap gap-1.5", + ) + + +def _bool_pills(var, setter) -> rx.Component: + return rx.box( + _pill_button("false", ~var, setter(False)), + _pill_button("true", var, setter(True)), + class_name="flex flex-wrap gap-1.5", + ) + + def render_select(prop: PropDocumentation, component: type[Component], prop_dict: dict): if ( not safe_issubclass(component, (RadixThemesComponent, RadixPrimitiveComponent)) @@ -104,10 +139,7 @@ def render_select(prop: PropDocumentation, component: type[Component], prop_dict PropDocsState._create_setter(name, var) setter = getattr(PropDocsState, f"set_{name}") prop_dict[prop.name] = var - return rx.checkbox( - var, - on_change=setter, - ) + return _bool_pills(var, setter) except TypeError: pass @@ -137,17 +169,7 @@ def render_select(prop: PropDocumentation, component: type[Component], prop_dict PropDocsState._create_setter(name, var) setter = getattr(PropDocsState, f"set_{name}") prop_dict[prop.name] = var - return rx.select.root( - rx.select.trigger(class_name="w-32 font-small text-slate-11"), - rx.select.content( - rx.select.group(*[ - rx.select.item(item, value=item, class_name="font-small") - for item in literal_values - ]) - ), - value=var, - on_change=setter, - ) + return _pill_row(literal_values, var, setter) # Get the first non-empty option. non_empty_args = [a for a in type_.__args__ if str(a) != ""] option = non_empty_args[0] if non_empty_args else type_.__args__[0] @@ -199,27 +221,8 @@ def render_select(prop: PropDocumentation, component: type[Component], prop_dict ), ), ) - return rx.select.root( - rx.select.trigger(class_name="font-small w-32 text-slate-11"), - rx.select.content( - rx.select.group(*[ - rx.select.item( - item, - value=item, - class_name="font-small", - _hover=( - {"background": f"var(--{item}-9)"} - if prop.name == "color_scheme" - else None - ), - ) - for item in list(map(str, type_.__args__)) - if item != "" - ]), - ), - value=var, - on_change=setter, - ) + literal_values = [str(a) for a in type_.__args__ if str(a) != ""] + return _pill_row(literal_values, var, setter) def hovercard(trigger: rx.Component, content: rx.Component) -> rx.Component: @@ -262,10 +265,8 @@ def safe_issubclass(cls, class_or_tuple): def prop_docs( prop: PropDocumentation, - prop_dict: dict, component: type[Component], - is_interactive: bool, -) -> list[rx.Component]: +) -> tuple[list[rx.Component], bool]: """Generate the docs for a prop.""" # Get the type of the prop. type_ = prop.type @@ -335,59 +336,57 @@ def prop_docs( default_value = prop.default_value if prop.default_value is not None else "-" # Get the color of the prop. color = TYPE_COLORS.get(short_type_name, "gray") + + description = prop.description or "" + is_long_row = ( + len(description) > 160 + or (literal_values and len(literal_values) > 8 and prop.name not in common_types) + ) + + cell_content_class = ( + ( + "cell-content max-h-[6.5em] overflow-hidden " + "[mask-image:linear-gradient(to_bottom,black_85%,transparent)] " + "[-webkit-mask-image:linear-gradient(to_bottom,black_85%,transparent)]" + ) + if is_long_row + else "cell-content" + ) + # Return the docs for the prop. return [ rx.table.cell( rx.box( rx.code(prop.name, class_name="code-style text-nowrap leading-normal"), - hovercard( - rx.icon( - tag="info", - size=15, - class_name="!text-slate-9 shrink-0", - ), - rx.text(prop.description, class_name="font-small text-slate-11"), - ), - class_name="flex flex-row items-center gap-2", + class_name=cell_content_class, ), - class_name="justify-start pl-4", + class_name="justify-start pl-4 align-top py-3", ), rx.table.cell( rx.box( - rx.cond( - (len(literal_values) > 0) & (prop.name not in common_types), - rx.code( - ( - " | ".join( - [f'"{v}"' for v in literal_values[:max_prop_values]] - + ["..."] - ) - if len(literal_values) > max_prop_values - else type_name - ), - style=get_code_style(color), - class_name="code-style text-nowrap leading-normal", - ), - rx.code( - type_name, - style=get_code_style(color), - class_name="code-style text-nowrap leading-normal", - ), - ), - rx.cond( - len(literal_values) > max_prop_values - and prop.name not in common_types, - hovercard( - rx.icon( - tag="circle-ellipsis", - size=15, - class_name="!text-slate-9 shrink-0", - ), - rx.text( - " | ".join([f'"{v}"' for v in literal_values]), - class_name="font-small text-slate-11", + rx.box( + rx.box( + *( + [ + rx.code( + f'"{v}"', + style=get_code_style(color), + class_name="code-style leading-normal text-nowrap", + ) + for v in literal_values + ] + if literal_values and prop.name not in common_types + else [ + rx.code( + type_name, + style=get_code_style(color), + class_name="code-style leading-normal whitespace-normal break-words", + ) + ] ), + class_name="flex flex-wrap gap-1", ), + class_name=cell_content_class, ), rx.cond( (origin == Union) @@ -410,9 +409,9 @@ def prop_docs( (prop.name == "color_scheme") | (prop.name == "accent_color"), color_scheme_hovercard(literal_values), ), - class_name="flex flex-row items-center gap-2", + class_name="flex flex-row items-start gap-2", ), - class_name="justify-start pl-4", + class_name="justify-start pl-4 align-top py-3", ), rx.table.cell( rx.box( @@ -427,23 +426,28 @@ def prop_docs( ), class_name="code-style leading-normal text-nowrap", ), - class_name="flex", + class_name=cell_content_class, ), - class_name="justify-start pl-4", + class_name="justify-start pl-4 align-top py-3", ), rx.table.cell( - render_select(prop, component, prop_dict), - class_name="justify-start pl-4", - ) - if is_interactive - else rx.fragment(), - ] + rx.box( + rx.text( + description, + class_name="font-small text-slate-11 whitespace-normal leading-snug break-words", + ), + class_name=cell_content_class, + ), + class_name="justify-start pl-4 align-top py-3 w-full", + ), + ], is_long_row def generate_props( props: tuple[PropDocumentation, ...], component: type[Component], previews: dict[str, str], + display_name: str | None = None, ) -> rx.Component: prop_list = list(props) if len(prop_list) == 0: @@ -474,16 +478,96 @@ def generate_props( ]: is_interactive = False - body = rx.table.body( - *[ + styling_props = { + "variant", + "size", + "color_scheme", + "radius", + "high_contrast", + "loading", + "disabled", + "weight", + "align", + "justify", + "direction", + "orientation", + } + + interactive_controls: list[tuple[PropDocumentation, rx.Component]] = [] + if is_interactive: + for prop in prop_list: + if prop.name.startswith("on_"): + continue + if prop.name not in styling_props: + continue + control = render_select(prop, component, prop_dict) + if not isinstance(control, Fragment): + interactive_controls.append((prop, control)) + + def _toggle_row() -> rx.Component: + return rx.el.tr( + rx.el.td( + rx.el.details( + rx.el.summary( + rx.el.span( + "Show more", + rx.icon( + "chevron-down", + size=12, + class_name="inline-block align-[-2px] ml-1", + ), + class_name="group-open/details:hidden", + ), + rx.el.span( + "Show less", + rx.icon( + "chevron-up", + size=12, + class_name="inline-block align-[-2px] ml-1", + ), + class_name="hidden group-open/details:inline", + ), + class_name=( + "block list-none cursor-pointer text-center text-xs " + "font-medium text-slate-11 hover:text-slate-12 py-2 " + "[&::-webkit-details-marker]:hidden [&::marker]:hidden" + ), + ), + class_name="group/details", + ), + col_span=4, + class_name=( + "!p-0 !border-t-0 " + "[box-shadow:0_-1px_0_0_var(--gray-a4)_inset]" + ), + ), + class_name="api-toggle-row bg-slate-2", + ) + + data_row_class = ( + "[&:has(+_tr.api-toggle-row_details[open])_.cell-content]:!max-h-none " + "[&:has(+_tr.api-toggle-row_details[open])_.cell-content]:!overflow-visible " + "[&:has(+_tr.api-toggle-row_details[open])_.cell-content]:![mask-image:none] " + "[&:has(+_tr.api-toggle-row_details[open])_.cell-content]:![-webkit-mask-image:none] " + "[&>td]:!shadow-none" + ) + + rows: list[rx.Component] = [] + for prop in prop_list: + if prop.name.startswith("on_"): # ignore event trigger props + continue + cells, is_long_row = prop_docs(prop, component) + rows.append( rx.table.row( - *prop_docs(prop, prop_dict, component, is_interactive), align="center" + *cells, + align="center", + class_name=data_row_class if is_long_row else "", ) - for prop in prop_list - if not prop.name.startswith("on_") # ignore event trigger props - ], - class_name="bg-slate-2", - ) + ) + if is_long_row: + rows.append(_toggle_row()) + + body = rx.table.body(*rows, class_name="bg-slate-2") comp: rx.Component try: @@ -507,12 +591,194 @@ def generate_props( print(f"Failed to create component {component.__name__}, error: {e}") comp = rx.fragment() - interactive_component = ( - docdemobox(comp) if not isinstance(comp, Fragment) else "", - ) + if not isinstance(comp, Fragment) and interactive_controls: + component_call = display_name or f"rx.{component.__name__.lower()}" + highlighted = { + "variant", + "size", + "color_scheme", + "radius", + "high_contrast", + "loading", + "disabled", + } + bool_excluded = { + "open", + "checked", + "as_child", + "default_open", + "default_checked", + } + + def _is_bool_prop(p: PropDocumentation) -> bool: + try: + inner = get_args(p.type)[0] + return ( + safe_issubclass(inner, bool) and p.name not in bool_excluded + ) + except Exception: + return False + + line_class = "font-mono text-sm whitespace-pre" + kw_class = "text-violet-11" + str_class = "text-orange-11" + bool_class = "text-blue-11" + prop_name_class = "text-slate-12" + token_re = re.compile( + r'(rx\.[\w.]+)|("[^"]*")|(\b(?:True|False|None)\b)|(\b\d+(?:\.\d+)?\b)|(\w+)|(\s+)|(.)' + ) + + def _render_static_line(line: str) -> rx.Component: + if not line.strip(): + return rx.el.div(" ", class_name=line_class) + children: list = [] + for m in token_re.finditer(line): + kw, s, b, n, ident, ws, other = m.groups() + if kw is not None: + children.append(rx.el.span(kw, class_name=kw_class)) + elif s is not None: + children.append(rx.el.span(s, class_name=str_class)) + elif b is not None: + children.append(rx.el.span(b, class_name=bool_class)) + elif n is not None: + children.append(rx.el.span(n, class_name=bool_class)) + elif ident is not None: + children.append(ident) + elif ws is not None: + children.append(ws) + else: + children.append(other) + return rx.el.div(*children, class_name=line_class) + + def _py_bool(var): + return rx.cond(var, "True", "False") + + def _render_dynamic_prop(indent: str, p, var) -> rx.Component: + if _is_bool_prop(p): + return rx.el.div( + indent, + rx.el.span(p.name, class_name=prop_name_class), + "=", + rx.el.span(_py_bool(var), class_name=bool_class), + ",", + class_name=line_class, + ) + return rx.el.div( + indent, + rx.el.span(p.name, class_name=prop_name_class), + "=", + rx.el.span('"', var, '"', class_name=str_class), + ",", + class_name=line_class, + ) + + preview_source = previews.get(component.__name__, "") + code_children: list[rx.Component] = [] + used_preview = False + + if preview_source: + src = preview_source.strip() + m = re.match(r"^lambda\s+\*\*props\s*:\s*", src) + if m: + src = src[m.end():] + src = textwrap.dedent(src).rstrip() + for line in src.split("\n"): + if "**props" not in line: + code_children.append(_render_static_line(line)) + continue + # Split the line around the **props token. + match = re.search(r"\*\*props\s*,?\s*", line) + before = line[: match.start()] if match else line + after = line[match.end():] if match else "" + base_indent = len(line) - len(line.lstrip()) + inline = bool(before.strip()) + # Compute prop-line indent: 4 deeper than the line's own + # indent when the **props was inline; otherwise reuse the + # line's indent (multi-line preview format). + prop_indent = " " * (base_indent + 4 if inline else base_indent) + if inline: + trimmed_before = before.rstrip() + if not trimmed_before.endswith(","): + trimmed_before += "," + code_children.append(_render_static_line(trimmed_before)) + for p, _ in interactive_controls: + if p.name not in highlighted: + continue + var = prop_dict.get(p.name) + if var is None: + continue + code_children.append(_render_dynamic_prop(prop_indent, p, var)) + trimmed_after = after.lstrip(", \t") + if inline and trimmed_after.strip(): + closing_indent = " " * base_indent + code_children.append( + _render_static_line(closing_indent + trimmed_after) + ) + used_preview = True + + if not used_preview: + code_children.append( + rx.el.div( + rx.el.span(component_call, class_name=kw_class), + "(", + class_name=line_class, + ) + ) + for p, _ in interactive_controls: + if p.name not in highlighted: + continue + var = prop_dict.get(p.name) + if var is None: + continue + code_children.append(_render_dynamic_prop(" ", p, var)) + code_children.append(rx.el.div(")", class_name=line_class)) + + interactive_component = rx.box( + rx.box( + comp, + class_name=( + "flex flex-col items-center justify-center p-6 flex-1 " + "bg-slate-2 border-r border-slate-4 min-w-0" + ), + ), + rx.box( + *code_children, + class_name="flex-1 p-4 bg-slate-1 min-w-0 overflow-x-auto", + ), + class_name=( + "flex flex-row w-full rounded-xl border border-slate-4 " + "overflow-hidden" + ), + ) + else: + interactive_component = rx.fragment() + + controls_panel: rx.Component = rx.fragment() + if interactive_controls: + controls_panel = rx.box( + *[ + rx.box( + rx.code( + prop.name, + class_name="code-style text-nowrap leading-normal text-slate-11", + ), + control, + class_name="grid grid-cols-[8rem_1fr] gap-4 items-start", + ) + for prop, control in interactive_controls + ], + class_name="flex flex-col gap-3 border border-slate-4 rounded-md p-4 bg-slate-2 mb-4 w-full", + ) + return rx.vstack( interactive_component, - rx.scroll_area( + controls_panel, + rx.heading( + "Props", + as_="h3", + class_name="font-large text-slate-12 mt-4 mb-2 text-left self-start", + ), + rx.box( rx.table.root( rx.el.style( """ @@ -524,32 +790,30 @@ def generate_props( rx.table.header( rx.table.row( rx.table.column_header_cell( - "Prop", - class_name=table_header_class_name, + "prop", + class_name=table_header_class_name + " w-[9rem]", ), rx.table.column_header_cell( - "Type | Values", - class_name=table_header_class_name, + "type", + class_name=table_header_class_name + " w-[28rem]", ), rx.table.column_header_cell( - "Default", - class_name=table_header_class_name, + "default", + class_name=table_header_class_name + " w-[4rem]", ), rx.table.column_header_cell( - "Interactive", + "description", class_name=table_header_class_name, - ) - if is_interactive - else rx.fragment(), + ), ), class_name="bg-slate-3", ), body, variant="surface", size="1", - class_name="px-0 w-full border border-slate-4", + class_name="px-0 border border-slate-4 w-full", ), - class_name="max-h-96 mb-4", + class_name="mb-4 w-full overflow-hidden", ), ) @@ -650,9 +914,6 @@ def component_docs( """Generates documentation for a given component.""" component = component_tuple[0] doc = generate_documentation(component) - props = generate_props(doc.props, component, previews) - triggers = generate_event_triggers(doc.event_handlers) - children = generate_valid_children(component) # Map for component display name overrides (e.g., for Python reserved keywords) component_display_name_map = { @@ -663,6 +924,10 @@ def component_docs( component_tuple[1], component_tuple[1] ) + props = generate_props(doc.props, component, previews, comp_display_name) + triggers = generate_event_triggers(doc.event_handlers) + children = generate_valid_children(component) + return rx.box( h2_comp(text=comp_display_name), rx.box( diff --git a/docs/app/reflex_docs/templates/docpage/docpage.py b/docs/app/reflex_docs/templates/docpage/docpage.py index 2779ae9920e..6e831d63cc2 100644 --- a/docs/app/reflex_docs/templates/docpage/docpage.py +++ b/docs/app/reflex_docs/templates/docpage/docpage.py @@ -377,6 +377,7 @@ def _build_reflex_menu_item(path: str) -> rx.Component: class_name=( "flex size-8 items-center justify-center rounded-md " "bg-gradient-to-br from-violet-9 to-violet-11 " + "dark:from-violet-7 dark:to-violet-9 " "shadow-[0_0_0_1px_var(--violet-7),0_2px_8px_-2px_var(--violet-a8)] shrink-0" ), ), @@ -408,6 +409,8 @@ def _build_reflex_menu_item(path: str) -> rx.Component: "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 " + "dark:from-violet-a3 dark:to-slate-2 " + "dark:hover:from-violet-a4 dark:hover:to-slate-3 " "border-b border-slate-4 transition-colors cursor-pointer" ), ) @@ -548,7 +551,7 @@ def _copy_page_button(doc_content: str, path: str = "") -> rx.Component: ), class_name=( "flex flex-col min-w-[260px] py-1 " - "bg-slate-1 border border-slate-5 rounded-lg shadow-lg " + "bg-white dark:bg-slate-2 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" ), @@ -773,7 +776,7 @@ def wrapper(*args, **kwargs) -> rx.Component: sidebar, class_name=( "w-[19.5rem] shrink-0 hidden lg:block z-10 border-r border-m-slate-4 dark:border-m-slate-10 sticky left-0 " - "before:content-[''] before:absolute before:top-0 before:bottom-0 before:right-0 before:w-[100vw] before:bg-white-1 dark:before:bg-m-slate-11 before:-z-10 " + "before:content-[''] before:absolute before:top-0 before:bottom-0 before:right-0 before:w-[100vw] before:bg-white-1 dark:before:bg-[#1a1b1d] before:-z-10 " + rx.cond( HostingBannerState.is_banner_visible, " top-[113px] h-[calc(100vh-113px)]", @@ -869,10 +872,6 @@ def wrapper(*args, **kwargs) -> rx.Component: ), rx.el.div( feedback_button_toc(), - copy_to_markdown(text=doc_content) - if doc_content - else None, - ask_ai_chat(), class_name="flex flex-col mt-1.5 justify-start", ), class_name="flex flex-col justify-start gap-y-4 overflow-y-auto sticky top-4", diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py index 47b1dc48689..4bb528548c5 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py @@ -117,7 +117,7 @@ def sidebar_leaf( item.link == url, sidebar_link( rx.el.div( - class_name="absolute left-0 top-1/2 -translate-y-1/2 w-full h-8 rounded-lg bg-m-slate-2 dark:bg-m-slate-10 z-[-1]", + class_name="absolute left-0 top-1/2 -translate-y-1/2 w-full h-8 rounded-lg bg-m-slate-2 dark:bg-slate-3 z-[-1]", ), rx.flex( rx.text( @@ -328,7 +328,7 @@ def sidebar_category(name: str, url: str, icon: str, index: int): "flex flex-row justify-start items-center gap-2.5 w-full text-sm text-secondary-11 hover:text-secondary-12 h-8", rx.cond( SidebarState.sidebar_index == index, - "text-primary-10 dark:text-primary-9", + "text-slate-12", "", ), ), @@ -338,7 +338,7 @@ def sidebar_category(name: str, url: str, icon: str, index: int): rx.cond( SidebarState.sidebar_index == index, rx.el.div( - class_name="absolute left-0 top-0 w-full h-full bg-m-slate-2 dark:bg-m-slate-10 rounded-lg z-[-1]", + class_name="absolute left-0 top-0 w-full h-full bg-m-slate-2 dark:bg-slate-3 rounded-lg z-[-1]", ), ), rx.el.a( From 6abe5a3b74e657b82d941d8b001562da1e99add7 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Sun, 26 Apr 2026 14:55:49 -0700 Subject: [PATCH 02/12] docs: HL/LL toggle + sidebar Tutorials section + copy fixes - Wire up the existing High Level / Low Level toggle on component pages: fix the LL existence check to use the absolute actual_path (was a relative path that didn't resolve from the dev-server cwd), and route the LL `out` handler through the same absolute path. - Register `-ll.md` files in `doc_markdown_sources` so they're served as static assets at `/-ll.md`. Update the right-rail copy button to fetch from the right URL on `/low` pages and reject responses whose content-type isn't markdown (which would have copied the SPA shell HTML instead of the doc). - Color type chips in the props table via `color_scheme` + `variant= soft` instead of the inline `style=` dict, which radix's `` was stripping (chips were showing only the outline, no fill). - Add a `Tutorials` accordion in the Onboarding sidebar that pulls the Dashboard and Chat App tutorials out of `Getting Started`. Map it to a `graduation-cap` icon in `sidebar_icon`. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/app/reflex_docs/pages/docs/__init__.py | 12 ++++- docs/app/reflex_docs/pages/docs/component.py | 44 +++++++++---------- .../reflex_docs/templates/docpage/docpage.py | 14 +++++- .../templates/docpage/sidebar/sidebar.py | 1 + .../docpage/sidebar/sidebar_items/learn.py | 7 ++- 5 files changed, 50 insertions(+), 28 deletions(-) diff --git a/docs/app/reflex_docs/pages/docs/__init__.py b/docs/app/reflex_docs/pages/docs/__init__.py index 61b1230093c..168a8408ac2 100644 --- a/docs/app/reflex_docs/pages/docs/__init__.py +++ b/docs/app/reflex_docs/pages/docs/__init__.py @@ -236,7 +236,17 @@ def comp(_actual=actual_path, _virtual=virtual_doc): # Build doc_markdown_sources mapping for _virtual, _actual in all_docs.items(): - if _virtual.endswith("-style.md") or _virtual.endswith("-ll.md"): + if _virtual.endswith("-style.md"): + continue + if _virtual.endswith("-ll.md"): + # Register low-level docs at /-ll.md so the copy button can + # fetch them from the served URL. + _hl_virtual = _virtual.replace("-ll.md", ".md") + _hl_route = doc_route_from_path(_hl_virtual) + if not _check_whitelisted_path(_hl_route): + continue + _ll_route = _hl_route.rstrip("/") + "-ll" + doc_markdown_sources[_ll_route] = _actual continue _route = doc_route_from_path(_virtual) if not _check_whitelisted_path(_route): diff --git a/docs/app/reflex_docs/pages/docs/component.py b/docs/app/reflex_docs/pages/docs/component.py index 001eaa71978..724f2dc0e16 100644 --- a/docs/app/reflex_docs/pages/docs/component.py +++ b/docs/app/reflex_docs/pages/docs/component.py @@ -24,7 +24,7 @@ render_docgen_document, render_markdown, ) -from reflex_docs.templates.docpage import docdemobox, docpage, h1_comp, h2_comp, h3_comp +from reflex_docs.templates.docpage import docpage, h1_comp, h2_comp def get_code_style(color: str): @@ -338,9 +338,8 @@ def prop_docs( color = TYPE_COLORS.get(short_type_name, "gray") description = prop.description or "" - is_long_row = ( - len(description) > 160 - or (literal_values and len(literal_values) > 8 and prop.name not in common_types) + is_long_row = len(description) > 160 or ( + literal_values and len(literal_values) > 8 and prop.name not in common_types ) cell_content_class = ( @@ -370,7 +369,8 @@ def prop_docs( [ rx.code( f'"{v}"', - style=get_code_style(color), + color_scheme=color, + variant="soft", class_name="code-style leading-normal text-nowrap", ) for v in literal_values @@ -379,7 +379,8 @@ def prop_docs( else [ rx.code( type_name, - style=get_code_style(color), + color_scheme=color, + variant="soft", class_name="code-style leading-normal whitespace-normal break-words", ) ] @@ -537,8 +538,7 @@ def _toggle_row() -> rx.Component: ), col_span=4, class_name=( - "!p-0 !border-t-0 " - "[box-shadow:0_-1px_0_0_var(--gray-a4)_inset]" + "!p-0 !border-t-0 [box-shadow:0_-1px_0_0_var(--gray-a4)_inset]" ), ), class_name="api-toggle-row bg-slate-2", @@ -613,9 +613,7 @@ def _toggle_row() -> rx.Component: def _is_bool_prop(p: PropDocumentation) -> bool: try: inner = get_args(p.type)[0] - return ( - safe_issubclass(inner, bool) and p.name not in bool_excluded - ) + return safe_issubclass(inner, bool) and p.name not in bool_excluded except Exception: return False @@ -630,7 +628,7 @@ def _is_bool_prop(p: PropDocumentation) -> bool: def _render_static_line(line: str) -> rx.Component: if not line.strip(): - return rx.el.div(" ", class_name=line_class) + return rx.el.div(class_name=line_class + " min-h-[1em]") children: list = [] for m in token_re.finditer(line): kw, s, b, n, ident, ws, other = m.groups() @@ -680,7 +678,7 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: src = preview_source.strip() m = re.match(r"^lambda\s+\*\*props\s*:\s*", src) if m: - src = src[m.end():] + src = src[m.end() :] src = textwrap.dedent(src).rstrip() for line in src.split("\n"): if "**props" not in line: @@ -689,7 +687,7 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: # Split the line around the **props token. match = re.search(r"\*\*props\s*,?\s*", line) before = line[: match.start()] if match else line - after = line[match.end():] if match else "" + after = line[match.end() :] if match else "" base_indent = len(line) - len(line.lstrip()) inline = bool(before.strip()) # Compute prop-line indent: 4 deeper than the line's own @@ -746,8 +744,7 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: class_name="flex-1 p-4 bg-slate-1 min-w-0 overflow-x-auto", ), class_name=( - "flex flex-row w-full rounded-xl border border-slate-4 " - "overflow-hidden" + "flex flex-row w-full rounded-xl border border-slate-4 overflow-hidden" ), ) else: @@ -795,7 +792,7 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: ), rx.table.column_header_cell( "type", - class_name=table_header_class_name + " w-[28rem]", + class_name=table_header_class_name + " w-[34rem]", ), rx.table.column_header_cell( "default", @@ -803,7 +800,7 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: ), rx.table.column_header_cell( "description", - class_name=table_header_class_name, + class_name=table_header_class_name + " w-[18rem]", ), ), class_name="bg-slate-3", @@ -952,8 +949,8 @@ def multi_docs( component_docs(component_tuple, previews) for component_tuple in component_list[1:] ] - fname = path.strip("/") + ".md" - ll_doc_exists = os.path.exists(fname.replace(".md", "-ll.md")) + ll_actual_path = actual_path.replace(".md", "-ll.md") + ll_doc_exists = os.path.exists(ll_actual_path) active_class_name = "font-small bg-slate-2 p-2 text-slate-11 rounded-xl shadow-large w-28 cursor-default border border-slate-4 text-center" @@ -1024,10 +1021,9 @@ def out(): @docpage(set_path=path + "low", t=title + " (Low Level)") def ll(): - ll_actual = fname.replace(".md", "-ll.md") ll_virtual = virtual_path.replace(".md", "-ll.md") - toc = get_docgen_toc(ll_actual) - doc_content = Path(ll_actual).read_text(encoding="utf-8") + toc = get_docgen_toc(ll_actual_path) + doc_content = Path(ll_actual_path).read_text(encoding="utf-8") if component_list: toc.append((1, "API Reference")) for component_tuple in component_list[1:]: @@ -1035,7 +1031,7 @@ def ll(): return (toc, doc_content), rx.box( links("ll", ll_doc_exists, path), render_docgen_document( - virtual_filepath=ll_virtual, actual_filepath=ll_actual + virtual_filepath=ll_virtual, actual_filepath=ll_actual_path ), h1_comp(text="API Reference"), rx.box(*components, class_name="flex flex-col"), diff --git a/docs/app/reflex_docs/templates/docpage/docpage.py b/docs/app/reflex_docs/templates/docpage/docpage.py index 6e831d63cc2..9f1f6a193df 100644 --- a/docs/app/reflex_docs/templates/docpage/docpage.py +++ b/docs/app/reflex_docs/templates/docpage/docpage.py @@ -426,7 +426,10 @@ def _copy_page_button(doc_content: str, path: str = "") -> rx.Component: copy_action = run_script( """ ((function() { - const mdUrl = window.location.pathname.replace(/\\/$/, '') + '.md'; + const cleanPath = window.location.pathname.replace(/\\/$/, ''); + const mdUrl = cleanPath.endsWith('/low') + ? cleanPath.replace(/\\/low$/, '-ll.md') + : cleanPath + '.md'; const animate = () => { document.querySelectorAll('[data-copy-icon]').forEach((icon) => { if (icon.dataset.animating === '1') return; @@ -457,7 +460,14 @@ def _copy_page_button(doc_content: str, path: str = "") -> rx.Component: }; animate(); fetch(mdUrl) - .then((r) => (r.ok ? r.text() : Promise.reject(r.status))) + .then((r) => { + if (!r.ok) return Promise.reject(r.status); + const ct = r.headers.get('content-type') || ''; + if (!ct.includes('markdown') && !ct.includes('text/plain')) { + return Promise.reject('not-markdown'); + } + return r.text(); + }) .then((text) => { if (navigator.clipboard && navigator.clipboard.writeText) { return navigator.clipboard.writeText(text).catch(() => { diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py index 4bb528548c5..4cfc5b3d785 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py @@ -153,6 +153,7 @@ def sidebar_leaf( def sidebar_icon(name): icon_map = { "Getting Started": "rocket", + "Tutorials": "graduation-cap", "Advanced Onboarding": "newspaper", "Components": "layers", "Pages": "sticky-note", 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 a0a2149b23e..0f4d652eb5d 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 @@ -14,9 +14,14 @@ def get_sidebar_items_learn(): getting_started.introduction, getting_started.basics, getting_started.project_structure, + getting_started.open_source_templates, + ], + ), + create_item( + "Tutorials", + children=[ getting_started.dashboard_tutorial, getting_started.chatapp_tutorial, - getting_started.open_source_templates, ], ), create_item( From 8158f1a1142fcbd7a112dc6e4bdce9381cd374e0 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Sun, 26 Apr 2026 15:25:04 -0700 Subject: [PATCH 03/12] docs: drop scroll-area wrapper from API Reference field/method tables The Fields/Methods tables on /docs/api-reference// pages were wrapped in `rx.scroll_area(max_height="35em")`, which capped each table at ~560px and made it scroll internally. Render the full table inline instead so the page just gets longer. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/app/reflex_docs/pages/docs/source.py | 86 +++++++++++------------ 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/docs/app/reflex_docs/pages/docs/source.py b/docs/app/reflex_docs/pages/docs/source.py index 3ceadda4036..fd15d5d82c0 100644 --- a/docs/app/reflex_docs/pages/docs/source.py +++ b/docs/app/reflex_docs/pages/docs/source.py @@ -25,66 +25,60 @@ def format_fields( fields: tuple[FieldDocumentation, ...], ) -> rx.Component: return ( - rx.scroll_area( - rx.table.root( - rx.table.header( - rx.table.row(*[ - rx.table.column_header_cell( - header, class_name=table_header_class_name - ) - for header in headers - ]) - ), - rx.table.body( - *[ - rx.table.row( - rx.table.cell( - format_field(field), - ), - rx.table.cell( - render_markdown(field.description or ""), - class_name="font-small text-slate-11", - ), - ) - for field in fields - ], - ), - ), - max_height="35em", - ), - ) - - -def format_methods(methods: tuple[MethodDocumentation, ...]) -> rx.Component: - return rx.scroll_area( rx.table.root( rx.table.header( - rx.table.row( - rx.table.column_header_cell("Signature"), - rx.table.column_header_cell("Description"), - ) + rx.table.row(*[ + rx.table.column_header_cell( + header, class_name=table_header_class_name + ) + for header in headers + ]) ), rx.table.body( *[ rx.table.row( rx.table.cell( - rx.code( - method.name + method.signature, - class_name="code-style", - ), - white_space="normal", + format_field(field), ), rx.table.cell( - method.description or "", - white_space="normal", - class_name="font-small text-slate-11 text-nowrap", + render_markdown(field.description or ""), + class_name="font-small text-slate-11", ), ) - for method in methods + for field in fields ], ), ), - max_height="35em", + ) + + +def format_methods(methods: tuple[MethodDocumentation, ...]) -> rx.Component: + return rx.table.root( + rx.table.header( + rx.table.row( + rx.table.column_header_cell("Signature"), + rx.table.column_header_cell("Description"), + ) + ), + rx.table.body( + *[ + rx.table.row( + rx.table.cell( + rx.code( + method.name + method.signature, + class_name="code-style", + ), + white_space="normal", + ), + rx.table.cell( + method.description or "", + white_space="normal", + class_name="font-small text-slate-11 text-nowrap", + ), + ) + for method in methods + ], + ), ) From 7ff5f768f10c12b2d16ebe44e656da4ff5275fb9 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Sun, 26 Apr 2026 15:52:12 -0700 Subject: [PATCH 04/12] docs: rename Hosting card, fix Edit/Raise issue links, add docs issue template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "How to Host" card on the docs landing page is now labeled "Deployment". - Right-rail "Raise an issue" link previously pointed at the archived `reflex-dev/reflex-web` repo and used `&` in the URL, which sent the literal `amp;body=...` to GitHub. Repoint at `reflex-dev/reflex`, fix the entity, prefill title/body with the current page path, pre-select the new "Documentation" issue template tab, and pre-add the `documentation` label. - "Edit this page" link likewise pointed at `reflex-web/tree/main` and produced `…//.md` (extra slash, missing `docs/` prefix). Use `reflex/blob/main/docs.md` so the link lands on the actual markdown file. - Add `.github/ISSUE_TEMPLATE/documentation.md` so the `?template=documentation.md` URL resolves. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/ISSUE_TEMPLATE/documentation.md | 19 +++++++++++++++++++ .../pages/docs_landing/views/hosting.py | 2 +- .../reflex_docs/templates/docpage/docpage.py | 10 ++++++++-- 3 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/documentation.md diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md new file mode 100644 index 00000000000..ba81d364437 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -0,0 +1,19 @@ +--- +name: Documentation +about: Report a problem with the docs at reflex.dev/docs +title: "" +labels: documentation +assignees: "" +--- + +**Page** +Path: + +**What's wrong?** +A clear description of the issue (typo, missing info, broken example, etc.). + +**Suggested fix (optional)** +If you have a suggestion for how to improve the page, share it here. + +**Screenshots (optional)** +If applicable, add screenshots to help explain. diff --git a/docs/app/reflex_docs/pages/docs_landing/views/hosting.py b/docs/app/reflex_docs/pages/docs_landing/views/hosting.py index a8aac6a0ee5..4c7bfba9d5d 100644 --- a/docs/app/reflex_docs/pages/docs_landing/views/hosting.py +++ b/docs/app/reflex_docs/pages/docs_landing/views/hosting.py @@ -21,7 +21,7 @@ def hosting_section() -> rx.Component: faded_borders(), link_item( "CloudServerIcon", - "How to Host", + "Deployment", "Step-by-step instructions to deploy your Reflex application to the cloud, including configuration and setup guides.", hosting_page.deploy_quick_start.path, ), diff --git a/docs/app/reflex_docs/templates/docpage/docpage.py b/docs/app/reflex_docs/templates/docpage/docpage.py index 9f1f6a193df..d726e535d0c 100644 --- a/docs/app/reflex_docs/templates/docpage/docpage.py +++ b/docs/app/reflex_docs/templates/docpage/docpage.py @@ -241,11 +241,17 @@ def docpage_footer(path: str): rx.box( link_pill( "Raise an issue", - href=f"https://github.com/reflex-dev/reflex-web/issues/new?title=Issue with reflex.dev documentation&body=Path: {path}", + href=( + "https://github.com/reflex-dev/reflex/issues/new" + "?template=documentation.md" + "&labels=documentation" + f"&title=Issue with reflex.dev{path}" + f"&body=Path: {path}%0A%0A" + ), ), link_pill( "Edit this page", - f"https://github.com/reflex-dev/reflex-web/tree/main{path}.md", + f"https://github.com/reflex-dev/reflex/blob/main/docs{path}.md", ), class_name="lg:flex hidden flex-row items-center gap-2 w-auto", ), From 26d2839ca061ce4ea918fb585075eba5603be75e Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Mon, 27 Apr 2026 10:00:18 -0700 Subject: [PATCH 05/12] =?UTF-8?q?docs:=20address=20review=20=E2=80=94=20pa?= =?UTF-8?q?lette=20tokens,=20ui.cn,=20color=20grid,=20uppercase=20headers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace hardcoded #1a1b1d sidebar bg with dark:before:bg-secondary-2. - Switch _PILL_BTN_CLASS to text-sm font-medium and the secondary-* palette. - Use border-secondary-* / bg-secondary-* for the controls panel and props table header / chrome. - Merge prop-table column header classes via ui.cn(...) instead of string concatenation. - Pack the color_scheme popover into a fixed grid-cols-[repeat(6,2rem)] swatch grid so the rows align evenly; give inactive swatches a transparent border so the active selection doesn't shift the layout. - Uppercase the props table headers (prop / type / default / description). Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/app/reflex_docs/pages/docs/component.py | 36 ++++++++++--------- .../reflex_docs/templates/docpage/docpage.py | 2 +- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/docs/app/reflex_docs/pages/docs/component.py b/docs/app/reflex_docs/pages/docs/component.py index 724f2dc0e16..6d279f538fa 100644 --- a/docs/app/reflex_docs/pages/docs/component.py +++ b/docs/app/reflex_docs/pages/docs/component.py @@ -9,6 +9,7 @@ from typing import Literal, Union, _GenericAlias, get_args, get_origin import reflex as rx +import reflex_components_internal as ui from reflex.components.base.fragment import Fragment from reflex.components.component import Component from reflex.components.radix.primitives.base import RadixPrimitiveComponent @@ -81,12 +82,12 @@ class PropDocsState(rx.State): _PILL_BTN_CLASS = ( - "font-small cursor-pointer rounded-md px-2.5 py-1 text-slate-11 " - "border border-slate-5 bg-slate-1 hover:bg-slate-3 transition-colors" + "text-sm font-medium cursor-pointer rounded-md px-2.5 py-1 text-secondary-11 " + "border border-secondary-5 bg-secondary-1 hover:bg-secondary-3 transition-colors" ) _PILL_BTN_ACTIVE_CLASS = ( - "font-small cursor-pointer rounded-md px-2.5 py-1 text-slate-12 " - "border border-slate-8 bg-slate-4" + "text-sm font-medium cursor-pointer rounded-md px-2.5 py-1 text-secondary-12 " + "border border-secondary-8 bg-secondary-4" ) @@ -197,7 +198,7 @@ def render_select(prop: PropDocumentation, component: type[Component], prop_dict ), ), rx.popover.content( - rx.grid( + rx.box( *[ rx.box( rx.icon( @@ -209,16 +210,18 @@ def render_select(prop: PropDocumentation, component: type[Component], prop_dict bg=f"var(--{color}-9)", on_click=PropDocsState.setvar(f"{name}", color), border=rx.cond( - var == color, "2px solid var(--gray-12)", "" + var == color, + "2px solid var(--gray-12)", + "2px solid transparent", ), - class_name="relative shrink-0 rounded-md size-8 cursor-pointer", + class_name="relative shrink-0 rounded-md size-8 cursor-pointer box-border", ) for color in list(map(str, type_.__args__)) if color != "" ], - columns="6", - spacing="3", + class_name="grid grid-cols-[repeat(6,2rem)] gap-3", ), + class_name="w-fit", ), ) literal_values = [str(a) for a in type_.__args__ if str(a) != ""] @@ -459,7 +462,8 @@ def generate_props( ) table_header_class_name = ( - "font-small text-slate-12 text-normal w-auto justify-start pl-4 font-bold" + "text-xs text-secondary-11 w-auto justify-start pl-4 font-semibold " + "uppercase tracking-wider" ) prop_dict = {} @@ -764,7 +768,7 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: ) for prop, control in interactive_controls ], - class_name="flex flex-col gap-3 border border-slate-4 rounded-md p-4 bg-slate-2 mb-4 w-full", + class_name="flex flex-col gap-3 border border-secondary-4 rounded-md p-4 bg-secondary-2 mb-4 w-full", ) return rx.vstack( @@ -788,22 +792,22 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: rx.table.row( rx.table.column_header_cell( "prop", - class_name=table_header_class_name + " w-[9rem]", + class_name=ui.cn(table_header_class_name, "w-[9rem]"), ), rx.table.column_header_cell( "type", - class_name=table_header_class_name + " w-[34rem]", + class_name=ui.cn(table_header_class_name, "w-[34rem]"), ), rx.table.column_header_cell( "default", - class_name=table_header_class_name + " w-[4rem]", + class_name=ui.cn(table_header_class_name, "w-[4rem]"), ), rx.table.column_header_cell( "description", - class_name=table_header_class_name + " w-[18rem]", + class_name=ui.cn(table_header_class_name, "w-[18rem]"), ), ), - class_name="bg-slate-3", + class_name="bg-secondary-3", ), body, variant="surface", diff --git a/docs/app/reflex_docs/templates/docpage/docpage.py b/docs/app/reflex_docs/templates/docpage/docpage.py index d726e535d0c..32021a0276e 100644 --- a/docs/app/reflex_docs/templates/docpage/docpage.py +++ b/docs/app/reflex_docs/templates/docpage/docpage.py @@ -792,7 +792,7 @@ def wrapper(*args, **kwargs) -> rx.Component: sidebar, class_name=( "w-[19.5rem] shrink-0 hidden lg:block z-10 border-r border-m-slate-4 dark:border-m-slate-10 sticky left-0 " - "before:content-[''] before:absolute before:top-0 before:bottom-0 before:right-0 before:w-[100vw] before:bg-white-1 dark:before:bg-[#1a1b1d] before:-z-10 " + "before:content-[''] before:absolute before:top-0 before:bottom-0 before:right-0 before:w-[100vw] before:bg-white-1 dark:before:bg-secondary-2 before:-z-10 " + rx.cond( HostingBannerState.is_banner_visible, " top-[113px] h-[calc(100vh-113px)]", From 1cdcfb84f4fbb7436abb591591c6bc153d026de8 Mon Sep 17 00:00:00 2001 From: Carlos Date: Mon, 27 Apr 2026 19:52:16 +0200 Subject: [PATCH 06/12] small followups --- docs/app/reflex_docs/pages/docs/component.py | 77 +++++++++----------- 1 file changed, 34 insertions(+), 43 deletions(-) diff --git a/docs/app/reflex_docs/pages/docs/component.py b/docs/app/reflex_docs/pages/docs/component.py index 6d279f538fa..f8bd680a5a9 100644 --- a/docs/app/reflex_docs/pages/docs/component.py +++ b/docs/app/reflex_docs/pages/docs/component.py @@ -182,47 +182,39 @@ def render_select(prop: PropDocumentation, component: type[Component], prop_dict prop_dict[prop.name] = var if prop.name == "color_scheme": - return rx.popover.root( - rx.popover.trigger( - rx.box( - rx.button( - rx.text(var, class_name="font-small"), - # Match the select.trigger svg icon - rx.html( - """""" - ), - color_scheme=var, - variant="surface", - class_name="w-32 justify-between", - ), - ), + return ui.popover( + trigger=rx.button( + rx.text(var, class_name="text-sm font-medium"), + ui.icon("ArrowDown01Icon"), + color_scheme=var, + variant="surface", + class_name="w-32 justify-between cursor-pointer", ), - rx.popover.content( - rx.box( - *[ - rx.box( - rx.icon( - "check", - size=15, - display=rx.cond(var == color, "block", "none"), - class_name="text-gray-12 absolute top-1/2 left-1/2 translate-x-[-50%] translate-y-[-50%]", - ), - bg=f"var(--{color}-9)", - on_click=PropDocsState.setvar(f"{name}", color), - border=rx.cond( - var == color, - "2px solid var(--gray-12)", - "2px solid transparent", - ), - class_name="relative shrink-0 rounded-md size-8 cursor-pointer box-border", - ) - for color in list(map(str, type_.__args__)) - if color != "" - ], - class_name="grid grid-cols-[repeat(6,2rem)] gap-3", - ), - class_name="w-fit", + content=rx.box( + *[ + rx.box( + rx.icon( + "check", + size=15, + display=rx.cond(var == color, "block", "none"), + class_name="text-gray-12 absolute top-1/2 left-1/2 translate-x-[-50%] translate-y-[-50%]", + ), + bg=f"var(--{color}-9)", + on_click=PropDocsState.setvar(f"{name}", color), + border=rx.cond( + var == color, + "2px solid var(--gray-12)", + "2px solid transparent", + ), + class_name="relative shrink-0 rounded-md size-8 cursor-pointer box-border", + ) + for color in list(map(str, type_.__args__)) + if color != "" + ], + class_name="grid grid-cols-[repeat(6,2rem)] gap-3 p-3", ), + align="start", + class_name="w-fit", ) literal_values = [str(a) for a in type_.__args__ if str(a) != ""] return _pill_row(literal_values, var, setter) @@ -462,8 +454,7 @@ def generate_props( ) table_header_class_name = ( - "text-xs text-secondary-11 w-auto justify-start pl-4 font-semibold " - "uppercase tracking-wider" + "text-xs text-secondary-11 w-auto justify-start pl-4 font-semibold capitalize" ) prop_dict = {} @@ -768,7 +759,7 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: ) for prop, control in interactive_controls ], - class_name="flex flex-col gap-3 border border-secondary-4 rounded-md p-4 bg-secondary-2 mb-4 w-full", + class_name="flex flex-col gap-3 border border-secondary-4 rounded-md p-4 bg-secondary-1 mb-4 w-full", ) return rx.vstack( @@ -807,7 +798,7 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: class_name=ui.cn(table_header_class_name, "w-[18rem]"), ), ), - class_name="bg-secondary-3", + class_name="bg-secondary-2", ), body, variant="surface", From f077045c9c50ce299251b40f37335c364eac8bed Mon Sep 17 00:00:00 2001 From: Carlos Date: Tue, 28 Apr 2026 14:33:13 +0200 Subject: [PATCH 07/12] bunch of api ref fixes --- docs/app/assets/tailwind-theme.css | 16 +--- docs/app/reflex_docs/pages/docs/component.py | 81 +++++++++++++------ docs/library/data-display/data_list.md | 17 +++- docs/library/data-display/progress.md | 2 +- docs/library/disclosure/accordion.md | 22 +++-- docs/library/disclosure/segmented_control.md | 16 ++++ docs/library/disclosure/tabs.md | 6 +- docs/library/forms/input.md | 2 +- docs/library/forms/radio_group.md | 7 +- docs/library/forms/select.md | 4 +- docs/library/forms/upload.md | 2 +- docs/library/layout/center.md | 12 +++ docs/library/layout/flex.md | 14 ++++ docs/library/layout/grid.md | 18 +++++ docs/library/layout/section.md | 14 ++++ docs/library/layout/stack.md | 20 +++++ docs/library/overlay/context_menu.md | 44 ++++++++-- docs/library/overlay/dropdown_menu.md | 50 ++++++++++-- .../themes/layout/container.py | 5 +- 19 files changed, 276 insertions(+), 76 deletions(-) diff --git a/docs/app/assets/tailwind-theme.css b/docs/app/assets/tailwind-theme.css index fbf4e63e176..8fde35c2b36 100644 --- a/docs/app/assets/tailwind-theme.css +++ b/docs/app/assets/tailwind-theme.css @@ -1699,14 +1699,6 @@ /* 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; @@ -1812,7 +1804,7 @@ } .code-block button, - .code-block > button { + .code-block>button { border: none !important; border-width: 0 !important; background: transparent !important; @@ -1832,19 +1824,19 @@ } .code-block button::after, - .code-block > button::after { + .code-block>button::after { content: "Copy"; } .code-block:hover button, - .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 { + .code-block>button:hover { background: var(--c-slate-3) !important; background-color: var(--c-slate-3) !important; } diff --git a/docs/app/reflex_docs/pages/docs/component.py b/docs/app/reflex_docs/pages/docs/component.py index f8bd680a5a9..33bb993e8a4 100644 --- a/docs/app/reflex_docs/pages/docs/component.py +++ b/docs/app/reflex_docs/pages/docs/component.py @@ -78,6 +78,9 @@ class PropDocsState(rx.State): "DrawerPortal", "DrawerContent", "DrawerClose", + "Container", + "Spacer", + "Skeleton", ] @@ -135,7 +138,8 @@ def render_select(prop: PropDocumentation, component: type[Component], prop_dict "default_checked", ]: name = get_id(f"{component.__qualname__}_{prop.name}") - PropDocsState.add_var(name, bool, False) + default = prop.name == "loading" and component.__name__ == "Spinner" + PropDocsState.add_var(name, bool, default) var = getattr(PropDocsState, name) PropDocsState._create_setter(name, var) setter = getattr(PropDocsState, f"set_{name}") @@ -144,14 +148,16 @@ def render_select(prop: PropDocumentation, component: type[Component], prop_dict except TypeError: pass - if not isinstance(type_, _GenericAlias) or ( - type_.__origin__ not in (Literal, Union) + type_origin = get_origin(type_) + if not isinstance(type_, (_GenericAlias, UnionType)) or ( + type_origin not in (Literal, Union, UnionType) ): return rx.fragment() # For the Union[Literal, Breakpoints] type - if type_.__origin__ is Union: + if type_origin in (Union, UnionType): if not all( - arg.__name__ in ["Literal", "Breakpoints"] for arg in type_.__args__ + getattr(arg, "__name__", "") in ["Literal", "Breakpoints"] + for arg in type_.__args__ ): return rx.fragment() else: @@ -460,18 +466,10 @@ def generate_props( prop_dict = {} is_interactive = True - if not issubclass( - component, (RadixThemesComponent, RadixPrimitiveComponent) - ) or component.__name__ in [ - "Theme", - "ThemePanel", - "DrawerRoot", - "DrawerTrigger", - "DrawerOverlay", - "DrawerPortal", - "DrawerContent", - "DrawerClose", - ]: + if ( + not issubclass(component, (RadixThemesComponent, RadixPrimitiveComponent)) + or component.__name__ in EXCLUDED_COMPONENTS + ): is_interactive = False styling_props = { @@ -489,12 +487,26 @@ def generate_props( "orientation", } + # Props that a specific component visually ignores (e.g. CSS overrides + # or deprecated HTML attributes), so we hide them from the interactive + # controls to avoid confusion. + per_component_skip = { + "Center": {"align", "justify"}, + "TableRoot": {"align"}, + "TableCell": {"align"}, + "TableRowHeaderCell": {"align"}, + "TableColumnHeaderCell": {"align"}, + "AccordionRoot": {"orientation"}, + "SegmentedControlRoot": {"color_scheme"}, + } + skip_props = per_component_skip.get(component.__name__, set()) + interactive_controls: list[tuple[PropDocumentation, rx.Component]] = [] if is_interactive: for prop in prop_list: if prop.name.startswith("on_"): continue - if prop.name not in styling_props: + if prop.name not in styling_props or prop.name in skip_props: continue control = render_select(prop, component, prop_dict) if not isinstance(control, Fragment): @@ -574,7 +586,7 @@ def _toggle_row() -> rx.Component: else: try: - comp = rx.vstack(component.create("Test", **prop_dict)) + comp = rx.vstack(component.create("Preview", **prop_dict)) except Exception: comp = rx.fragment() if "data" in component.__name__.lower(): @@ -596,6 +608,11 @@ def _toggle_row() -> rx.Component: "high_contrast", "loading", "disabled", + "weight", + "align", + "justify", + "direction", + "orientation", } bool_excluded = { "open", @@ -1000,17 +1017,24 @@ def out(): toc = get_docgen_toc(actual_path) doc_content = Path(actual_path).read_text(encoding="utf-8") # Append API Reference headings for the component list - if component_list: + if components: toc.append((1, "API Reference")) for component_tuple in component_list[1:]: toc.append((2, component_tuple[1])) + api_ref_section = ( + [ + h1_comp(text="API Reference"), + rx.box(*components, class_name="flex flex-col"), + ] + if components + else [] + ) return (toc, doc_content), rx.box( links("hl", ll_doc_exists, path), render_docgen_document( virtual_filepath=virtual_path, actual_filepath=actual_path ), - h1_comp(text="API Reference"), - rx.box(*components, class_name="flex flex-col"), + *api_ref_section, class_name="flex flex-col w-full", ) @@ -1019,17 +1043,24 @@ def ll(): ll_virtual = virtual_path.replace(".md", "-ll.md") toc = get_docgen_toc(ll_actual_path) doc_content = Path(ll_actual_path).read_text(encoding="utf-8") - if component_list: + if components: toc.append((1, "API Reference")) for component_tuple in component_list[1:]: toc.append((2, component_tuple[1])) + api_ref_section = ( + [ + h1_comp(text="API Reference"), + rx.box(*components, class_name="flex flex-col"), + ] + if components + else [] + ) return (toc, doc_content), rx.box( links("ll", ll_doc_exists, path), render_docgen_document( virtual_filepath=ll_virtual, actual_filepath=ll_actual_path ), - h1_comp(text="API Reference"), - rx.box(*components, class_name="flex flex-col"), + *api_ref_section, class_name="flex flex-col w-full", ) diff --git a/docs/library/data-display/data_list.md b/docs/library/data-display/data_list.md index 3de31747c4f..d9560ec1bca 100644 --- a/docs/library/data-display/data_list.md +++ b/docs/library/data-display/data_list.md @@ -14,9 +14,20 @@ DataListRoot: | ) DataListItem: | lambda **props: rx.data_list.root( - rx.foreach( - [["Status", "Authorized"], ["ID", "U-474747"], ["Name", "Developer Success"], ["Email", "foo@reflex.dev"]], - lambda item: rx.data_list.item(rx.data_list.label(item[0]), rx.data_list.value(item[1]), **props), + rx.data_list.item( + rx.data_list.label("Status"), + rx.data_list.value(rx.badge("Authorized", variant="soft", radius="full", size="2")), + **props, + ), + rx.data_list.item( + rx.data_list.label("Name"), + rx.data_list.value(rx.heading("Developer Success", size="4")), + **props, + ), + rx.data_list.item( + rx.data_list.label("Email"), + rx.data_list.value(rx.link("foo@reflex.dev", href="mailto:foo@reflex.dev")), + **props, ), ) DataListLabel: | diff --git a/docs/library/data-display/progress.md b/docs/library/data-display/progress.md index 4ba52b45fd1..245a1d6f3a9 100644 --- a/docs/library/data-display/progress.md +++ b/docs/library/data-display/progress.md @@ -3,7 +3,7 @@ components: - rx.progress Progress: | - lambda **props: rx.progress(value=50, **props) + lambda **props: rx.box(rx.progress(value=50, **props), width="20rem") --- # Progress diff --git a/docs/library/disclosure/accordion.md b/docs/library/disclosure/accordion.md index fea3f31dbe6..58183bdc51f 100644 --- a/docs/library/disclosure/accordion.md +++ b/docs/library/disclosure/accordion.md @@ -5,22 +5,32 @@ components: AccordionRoot: | lambda **props: rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content"), rx.accordion.item( - header="Second Item", content="The second accordion item's content", + header="First Item", + content="The first accordion item's content", + **props, + ), + rx.accordion.item( + header="Second Item", + content="The second accordion item's content", + **props, + ), + rx.accordion.item( + header="Third item", + content="The third accordion item's content", + **props, ), - rx.accordion.item(header="Third item", content="The third accordion item's content"), width="300px", **props, ) AccordionItem: | lambda **props: rx.accordion.root( - rx.accordion.item(header="First Item", content="The first accordion item's content", **props), rx.accordion.item( - header="Second Item", content="The second accordion item's content", **props, + header="Single Item", + content="The accordion item's content", + **props, ), - rx.accordion.item(header="Third item", content="The third accordion item's content", **props), width="300px", ) --- diff --git a/docs/library/disclosure/segmented_control.md b/docs/library/disclosure/segmented_control.md index d889b6b1229..67a7e12ddbf 100644 --- a/docs/library/disclosure/segmented_control.md +++ b/docs/library/disclosure/segmented_control.md @@ -2,6 +2,22 @@ components: - rx.segmented_control.root - rx.segmented_control.item + +SegmentedControlRoot: | + lambda **props: rx.segmented_control.root( + rx.segmented_control.item("Inbox", value="inbox"), + rx.segmented_control.item("Drafts", value="drafts"), + rx.segmented_control.item("Sent", value="sent"), + default_value="inbox", + **props, + ) + +SegmentedControlItem: | + lambda **props: rx.segmented_control.root( + rx.segmented_control.item("Inbox", value="inbox", **props), + rx.segmented_control.item("Drafts", value="drafts"), + default_value="inbox", + ) --- ```python exec diff --git a/docs/library/disclosure/tabs.md b/docs/library/disclosure/tabs.md index 04e44511b6c..4e633d66414 100644 --- a/docs/library/disclosure/tabs.md +++ b/docs/library/disclosure/tabs.md @@ -59,9 +59,9 @@ TabsList: | TabsTrigger: | lambda **props: rx.tabs.root( rx.tabs.list( - rx.tabs.trigger("Account", value="account", **props,), - rx.tabs.trigger("Documents", value="documents"), - rx.tabs.trigger("Settings", value="settings"), + rx.tabs.trigger("Account", value="account", **props), + rx.tabs.trigger("Documents", value="documents", **props), + rx.tabs.trigger("Settings", value="settings", **props), ), rx.box( rx.tabs.content( diff --git a/docs/library/forms/input.md b/docs/library/forms/input.md index a26ddc9ad87..e8c766a93c9 100644 --- a/docs/library/forms/input.md +++ b/docs/library/forms/input.md @@ -3,7 +3,7 @@ components: - rx.input - rx.input.slot -Input: | +TextFieldRoot: | lambda **props: rx.input(placeholder="Search the docs", **props) TextFieldSlot: | diff --git a/docs/library/forms/radio_group.md b/docs/library/forms/radio_group.md index a7fcce9c716..bbd7e12bc75 100644 --- a/docs/library/forms/radio_group.md +++ b/docs/library/forms/radio_group.md @@ -21,9 +21,10 @@ RadioGroupItem: | lambda **props: rx.radio_group.root( rx.radio_group.item(value="1", **props), rx.radio_group.item(value="2", **props), - rx.radio_group.item(value="3",), - rx.radio_group.item(value="4",), - rx.radio_group.item(value="5",), + rx.radio_group.item(value="3", **props), + rx.radio_group.item(value="4", **props), + rx.radio_group.item(value="5", **props), + default_value="1", ) --- diff --git a/docs/library/forms/select.md b/docs/library/forms/select.md index 25f1951dbc7..8f5f3f49c85 100644 --- a/docs/library/forms/select.md +++ b/docs/library/forms/select.md @@ -59,8 +59,8 @@ SelectItem: | rx.select.content( rx.select.group( rx.select.item("apple", value="apple", **props), - rx.select.item("grape", value="grape"), - rx.select.item("pear", value="pear"), + rx.select.item("grape", value="grape", **props), + rx.select.item("pear", value="pear", **props), ), ), default_value="pear", diff --git a/docs/library/forms/upload.md b/docs/library/forms/upload.md index 7ea40bad823..9f242ff59f2 100644 --- a/docs/library/forms/upload.md +++ b/docs/library/forms/upload.md @@ -151,7 +151,7 @@ def upload_component(): Below is an example of how to allow multiple file uploads (in this case images). ```python demo box -rx.image(src="https://web.reflex-assets.dev/other/upload.gif") +rx.image(src="https://web.reflex-assets.dev/other/upload.webp") ``` ```python diff --git a/docs/library/layout/center.md b/docs/library/layout/center.md index 3d422553680..3ec68eac1c2 100644 --- a/docs/library/layout/center.md +++ b/docs/library/layout/center.md @@ -1,6 +1,18 @@ --- components: - rx.center + +Center: | + lambda **props: rx.center( + rx.card("Card 1", size="2"), + rx.card("Card 2", size="2"), + rx.card("Card 3", size="2"), + gap="0.75rem", + width="100%", + height="10rem", + border_radius="0.5rem", + **props, + ) --- ```python exec diff --git a/docs/library/layout/flex.md b/docs/library/layout/flex.md index 31f6bcd1458..1dbe38e488c 100644 --- a/docs/library/layout/flex.md +++ b/docs/library/layout/flex.md @@ -1,6 +1,20 @@ --- components: - rx.flex + +Flex: | + lambda **props: rx.flex( + rx.card("Card 1", size="2", width="5rem"), + rx.card("Card 2", size="2", width="5rem"), + rx.card("Card 3", size="2", width="5rem"), + gap="0.75rem", + width="100%", + min_height="10rem", + border="1px dashed var(--gray-7)", + border_radius="0.5rem", + padding="0.75rem", + **props, + ) --- ```python exec diff --git a/docs/library/layout/grid.md b/docs/library/layout/grid.md index f09071a2d71..72baa475857 100644 --- a/docs/library/layout/grid.md +++ b/docs/library/layout/grid.md @@ -1,6 +1,24 @@ --- components: - rx.grid + +Grid: | + lambda **props: rx.grid( + rx.card("Card 1", size="2", width="5rem"), + rx.card("Card 2", size="2", width="5rem"), + rx.card("Card 3", size="2", width="5rem"), + rx.card("Card 4", size="2", width="5rem"), + rx.card("Card 5", size="2", width="5rem"), + rx.card("Card 6", size="2", width="5rem"), + columns="3", + gap="0.75rem", + width="100%", + min_height="10rem", + border="1px dashed var(--gray-7)", + border_radius="0.5rem", + padding="0.75rem", + **props, + ) --- ```python exec diff --git a/docs/library/layout/section.md b/docs/library/layout/section.md index 05f818bd0e1..e7ed0779051 100644 --- a/docs/library/layout/section.md +++ b/docs/library/layout/section.md @@ -1,6 +1,20 @@ --- components: - rx.section + +Section: | + lambda **props: rx.section( + rx.text("Section content", color="black"), + background="white", + border_radius="0.5rem", + width="100%", + justify="center", + border="1px solid var(--gray-3)", + align="center", + display="flex", + padding="4rem", + **props, + ) --- ```python exec diff --git a/docs/library/layout/stack.md b/docs/library/layout/stack.md index 27a6f905513..5fd55d2be8c 100644 --- a/docs/library/layout/stack.md +++ b/docs/library/layout/stack.md @@ -10,6 +10,26 @@ Stack: | height="20vh", **props, ) + +VStack: | + lambda **props: rx.vstack( + rx.card("Card 1", size="2"), + rx.card("Card 2", size="2"), + rx.card("Card 3", size="2"), + width="100%", + border_radius="0.5rem", + **props, + ) + +HStack: | + lambda **props: rx.hstack( + rx.card("Card 1", size="2"), + rx.card("Card 2", size="2"), + rx.card("Card 3", size="2"), + width="100%", + border_radius="0.5rem", + **props, + ) --- ```python exec diff --git a/docs/library/overlay/context_menu.md b/docs/library/overlay/context_menu.md index c25a4c202bd..16e26c99fae 100644 --- a/docs/library/overlay/context_menu.md +++ b/docs/library/overlay/context_menu.md @@ -15,7 +15,11 @@ only_low_level: ContextMenuRoot: | lambda **props: rx.context_menu.root( rx.context_menu.trigger( - rx.text("Context Menu (right click)") + rx.button( + "Context Menu (right click)", + color_scheme=props.get("color_scheme"), + variant=props.get("variant"), + ), ), rx.context_menu.content( rx.context_menu.item("Copy", shortcut="⌘ C"), @@ -36,8 +40,12 @@ ContextMenuRoot: | ContextMenuTrigger: | lambda **props: rx.context_menu.root( rx.context_menu.trigger( - rx.text("Context Menu (right click)"), - **props + rx.button( + "Context Menu (right click)", + color_scheme=props.get("color_scheme"), + variant=props.get("variant"), + ), + **props, ), rx.context_menu.content( rx.context_menu.item("Copy", shortcut="⌘ C"), @@ -57,7 +65,11 @@ ContextMenuTrigger: | ContextMenuContent: | lambda **props: rx.context_menu.root( rx.context_menu.trigger( - rx.text("Context Menu (right click)") + rx.button( + "Context Menu (right click)", + color_scheme=props.get("color_scheme"), + variant=props.get("variant"), + ), ), rx.context_menu.content( rx.context_menu.item("Copy", shortcut="⌘ C"), @@ -78,7 +90,11 @@ ContextMenuContent: | ContextMenuSub: | lambda **props: rx.context_menu.root( rx.context_menu.trigger( - rx.text("Context Menu (right click)") + rx.button( + "Context Menu (right click)", + color_scheme=props.get("color_scheme"), + variant=props.get("variant"), + ), ), rx.context_menu.content( rx.context_menu.item("Copy", shortcut="⌘ C"), @@ -99,7 +115,11 @@ ContextMenuSub: | ContextMenuSubTrigger: | lambda **props: rx.context_menu.root( rx.context_menu.trigger( - rx.text("Context Menu (right click)") + rx.button( + "Context Menu (right click)", + color_scheme=props.get("color_scheme"), + variant=props.get("variant"), + ), ), rx.context_menu.content( rx.context_menu.item("Copy", shortcut="⌘ C"), @@ -119,7 +139,11 @@ ContextMenuSubTrigger: | ContextMenuSubContent: | lambda **props: rx.context_menu.root( rx.context_menu.trigger( - rx.text("Context Menu (right click)") + rx.button( + "Context Menu (right click)", + color_scheme=props.get("color_scheme"), + variant=props.get("variant"), + ), ), rx.context_menu.content( rx.context_menu.item("Copy", shortcut="⌘ C"), @@ -140,7 +164,11 @@ ContextMenuSubContent: | ContextMenuItem: | lambda **props: rx.context_menu.root( rx.context_menu.trigger( - rx.text("Context Menu (right click)") + rx.button( + "Context Menu (right click)", + color_scheme=props.get("color_scheme"), + variant=props.get("variant"), + ), ), rx.context_menu.content( rx.context_menu.item("Copy", shortcut="⌘ C", **props), diff --git a/docs/library/overlay/dropdown_menu.md b/docs/library/overlay/dropdown_menu.md index 4fb770a4a45..f4213dd8099 100644 --- a/docs/library/overlay/dropdown_menu.md +++ b/docs/library/overlay/dropdown_menu.md @@ -12,7 +12,13 @@ only_low_level: DropdownMenuRoot: | lambda **props: rx.menu.root( - rx.menu.trigger(rx.button("drop down menu")), + rx.menu.trigger( + rx.button( + "Click to open Dropdown Menu", + color_scheme=props.get("color_scheme"), + variant=props.get("variant"), + ), + ), rx.menu.content( rx.menu.item("Edit", shortcut="⌘ E"), rx.menu.item("Share"), @@ -26,12 +32,18 @@ DropdownMenuRoot: | ), ), ), - **props + **props, ) DropdownMenuContent: | lambda **props: rx.menu.root( - rx.menu.trigger(rx.button("drop down menu")), + rx.menu.trigger( + rx.button( + "Click to open Dropdown Menu", + color_scheme=props.get("color_scheme"), + variant=props.get("variant"), + ), + ), rx.menu.content( rx.menu.item("Edit", shortcut="⌘ E"), rx.menu.item("Share"), @@ -50,7 +62,13 @@ DropdownMenuContent: | DropdownMenuItem: | lambda **props: rx.menu.root( - rx.menu.trigger(rx.button("drop down menu")), + rx.menu.trigger( + rx.button( + "Click to open Dropdown Menu", + color_scheme=props.get("color_scheme"), + variant=props.get("variant"), + ), + ), rx.menu.content( rx.menu.item("Edit", shortcut="⌘ E", **props), rx.menu.item("Share", **props), @@ -68,7 +86,13 @@ DropdownMenuItem: | DropdownMenuSub: | lambda **props: rx.menu.root( - rx.menu.trigger(rx.button("drop down menu")), + rx.menu.trigger( + rx.button( + "Dropdown Menu", + color_scheme=props.get("color_scheme"), + variant=props.get("variant"), + ), + ), rx.menu.content( rx.menu.item("Edit", shortcut="⌘ E"), rx.menu.item("Share"), @@ -87,7 +111,13 @@ DropdownMenuSub: | DropdownMenuSubTrigger: | lambda **props: rx.menu.root( - rx.menu.trigger(rx.button("drop down menu")), + rx.menu.trigger( + rx.button( + "Dropdown Menu", + color_scheme=props.get("color_scheme"), + variant=props.get("variant"), + ), + ), rx.menu.content( rx.menu.item("Edit", shortcut="⌘ E"), rx.menu.item("Share"), @@ -105,7 +135,13 @@ DropdownMenuSubTrigger: | DropdownMenuSubContent: | lambda **props: rx.menu.root( - rx.menu.trigger(rx.button("drop down menu")), + rx.menu.trigger( + rx.button( + "Click to open Dropdown Menu", + color_scheme=props.get("color_scheme"), + variant=props.get("variant"), + ), + ), rx.menu.content( rx.menu.item("Edit", shortcut="⌘ E"), rx.menu.item("Share"), diff --git a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py index d9ef24a186b..5289e32cd33 100644 --- a/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py +++ b/packages/reflex-components-radix/src/reflex_components_radix/themes/layout/container.py @@ -16,10 +16,7 @@ class Container(elements.Div, RadixThemesComponent): - """Constrains the maximum width of page content. - - See https://www.radix-ui.com/themes/docs/components/container - """ + """Constrains the maximum width of page content.""" tag = "Container" From a3c264f512b0b446fd116ebf8a71a62a54cd6b8c Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Tue, 28 Apr 2026 19:15:18 -0700 Subject: [PATCH 08/12] Address docs review feedback --- docs/app/reflex_docs/pages/docs/component.py | 6 +- docs/getting_started/introduction.md | 67 ++++---------------- 2 files changed, 18 insertions(+), 55 deletions(-) diff --git a/docs/app/reflex_docs/pages/docs/component.py b/docs/app/reflex_docs/pages/docs/component.py index 33bb993e8a4..84785e2f4e8 100644 --- a/docs/app/reflex_docs/pages/docs/component.py +++ b/docs/app/reflex_docs/pages/docs/component.py @@ -748,7 +748,8 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: comp, class_name=( "flex flex-col items-center justify-center p-6 flex-1 " - "bg-slate-2 border-r border-slate-4 min-w-0" + "bg-slate-2 border-b lg:border-b-0 lg:border-r " + "border-slate-4 min-w-0" ), ), rx.box( @@ -756,7 +757,8 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: class_name="flex-1 p-4 bg-slate-1 min-w-0 overflow-x-auto", ), class_name=( - "flex flex-row w-full rounded-xl border border-slate-4 overflow-hidden" + "flex flex-col lg:flex-row w-full rounded-xl border " + "border-slate-4 overflow-hidden" ), ) else: diff --git a/docs/getting_started/introduction.md b/docs/getting_started/introduction.md index 81e9545ecb2..0d6321b0872 100644 --- a/docs/getting_started/introduction.md +++ b/docs/getting_started/introduction.md @@ -117,14 +117,11 @@ Here is the full code for this example: tabs() ``` -```python demo box -rx.box( - rx.code_block( - """import reflex as rx """, - class_name="code-block !bg-transparent !border-none", - ), - rx.code_block( - """class State(rx.State): +```python +import reflex as rx + + +class State(rx.State): count: int = 0 @rx.event @@ -133,21 +130,10 @@ rx.box( @rx.event def decrement(self): - self.count -= 1""", - background=rx.cond( - IntroTabsState.value == "tab2", - "var(--c-slate-3) !important", - "transparent", - ), - border=rx.cond( - IntroTabsState.value == "tab2", - "1px solid var(--c-slate-5)", - "none !important", - ), - class_name="code-block", - ), - rx.code_block( - """def index(): + self.count -= 1 + + +def index(): return rx.hstack( rx.button( "Decrement", @@ -161,36 +147,11 @@ rx.box( on_click=State.increment, ), spacing="4", - )""", - border=rx.cond( - IntroTabsState.value == "tab1", - "1px solid var(--c-slate-5)", - "none !important", - ), - background=rx.cond( - IntroTabsState.value == "tab1", - "var(--c-slate-3) !important", - "transparent", - ), - class_name="code-block", - ), - rx.code_block( - """app = rx.App() -app.add_page(index)""", - background=rx.cond( - IntroTabsState.value == "tab3", - "var(--c-slate-3) !important", - "transparent", - ), - border=rx.cond( - IntroTabsState.value == "tab3", - "1px solid var(--c-slate-5)", - "none !important", - ), - class_name="code-block", - ), - class_name="w-full flex flex-col", -) + ) + + +app = rx.App() +app.add_page(index) ``` ## The Structure of a Reflex App From 88cc943a4d8584640c0651e0181bff6fff57a96f Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Tue, 28 Apr 2026 19:25:48 -0700 Subject: [PATCH 09/12] Restore counter code section --- docs/getting_started/introduction.md | 67 ++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/docs/getting_started/introduction.md b/docs/getting_started/introduction.md index 0d6321b0872..81e9545ecb2 100644 --- a/docs/getting_started/introduction.md +++ b/docs/getting_started/introduction.md @@ -117,11 +117,14 @@ Here is the full code for this example: tabs() ``` -```python -import reflex as rx - - -class State(rx.State): +```python demo box +rx.box( + rx.code_block( + """import reflex as rx """, + class_name="code-block !bg-transparent !border-none", + ), + rx.code_block( + """class State(rx.State): count: int = 0 @rx.event @@ -130,10 +133,21 @@ class State(rx.State): @rx.event def decrement(self): - self.count -= 1 - - -def index(): + self.count -= 1""", + background=rx.cond( + IntroTabsState.value == "tab2", + "var(--c-slate-3) !important", + "transparent", + ), + border=rx.cond( + IntroTabsState.value == "tab2", + "1px solid var(--c-slate-5)", + "none !important", + ), + class_name="code-block", + ), + rx.code_block( + """def index(): return rx.hstack( rx.button( "Decrement", @@ -147,11 +161,36 @@ def index(): on_click=State.increment, ), spacing="4", - ) - - -app = rx.App() -app.add_page(index) + )""", + border=rx.cond( + IntroTabsState.value == "tab1", + "1px solid var(--c-slate-5)", + "none !important", + ), + background=rx.cond( + IntroTabsState.value == "tab1", + "var(--c-slate-3) !important", + "transparent", + ), + class_name="code-block", + ), + rx.code_block( + """app = rx.App() +app.add_page(index)""", + background=rx.cond( + IntroTabsState.value == "tab3", + "var(--c-slate-3) !important", + "transparent", + ), + border=rx.cond( + IntroTabsState.value == "tab3", + "1px solid var(--c-slate-5)", + "none !important", + ), + class_name="code-block", + ), + class_name="w-full flex flex-col", +) ``` ## The Structure of a Reflex App From 67fd63fe09ad61d06323fc3fec63970780c2361f Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Tue, 28 Apr 2026 21:33:31 -0700 Subject: [PATCH 10/12] docs: address api reference review feedback --- docs/app/assets/tailwind-theme.css | 9 +- docs/app/reflex_docs/pages/docs/__init__.py | 8 ++ docs/app/reflex_docs/pages/docs/component.py | 4 +- docs/app/reflex_docs/pages/docs/library.py | 39 +++++- .../app/reflex_docs/pages/library_previews.py | 62 ++++++++- .../reflex_docs/templates/docpage/docpage.py | 72 ++++++----- .../templates/docpage/sidebar/sidebar.py | 13 +- .../sidebar/sidebar_items/component_lib.py | 62 ++++++++- docs/getting_started/basics.md | 2 +- docs/getting_started/introduction.md | 79 ++++++------ docs/library/dynamic-rendering/auto_scroll.md | 5 + docs/library/dynamic-rendering/cond.md | 5 + docs/library/dynamic-rendering/foreach.md | 5 + docs/library/dynamic-rendering/match.md | 5 + docs/library/html/forms.md | 18 +++ docs/library/html/html.md | 22 ++++ docs/library/html/layout.md | 43 +++++++ docs/library/html/media.md | 20 +++ docs/library/html/svg.md | 14 +++ docs/library/html/tables.md | 17 +++ docs/library/html/text.md | 33 +++++ docs/library/other/html.md | 119 ------------------ docs/library/other/memo.md | 14 +++ docs/styling/tailwind.md | 2 +- 24 files changed, 462 insertions(+), 210 deletions(-) create mode 100644 docs/library/html/forms.md create mode 100644 docs/library/html/html.md create mode 100644 docs/library/html/layout.md create mode 100644 docs/library/html/media.md create mode 100644 docs/library/html/svg.md create mode 100644 docs/library/html/tables.md create mode 100644 docs/library/html/text.md delete mode 100644 docs/library/other/html.md diff --git a/docs/app/assets/tailwind-theme.css b/docs/app/assets/tailwind-theme.css index 8fde35c2b36..453a3fbcf74 100644 --- a/docs/app/assets/tailwind-theme.css +++ b/docs/app/assets/tailwind-theme.css @@ -1766,6 +1766,13 @@ font-family: var(--font-jetbrains) !important; } + .code-block.counter-code-block { + padding: 0 !important; + border: 0 !important; + border-radius: 0 !important; + background: transparent !important; + } + .code-block>* { background: transparent !important; font-family: var(--font-jetbrains) !important; @@ -2169,4 +2176,4 @@ body { @apply isolate bg-slate-1 font-sans antialiased; } -} \ No newline at end of file +} diff --git a/docs/app/reflex_docs/pages/docs/__init__.py b/docs/app/reflex_docs/pages/docs/__init__.py index 45e3a99fdea..6ac16a0b29d 100644 --- a/docs/app/reflex_docs/pages/docs/__init__.py +++ b/docs/app/reflex_docs/pages/docs/__init__.py @@ -4,6 +4,7 @@ from types import SimpleNamespace import reflex as rx +from reflex_components_core.core.cond import Cond from reflex_docgen.markdown import parse_document # External Components @@ -23,6 +24,10 @@ from .library import library from .recipes_overview import overview +SPECIAL_COMPONENT_DOCS = { + "rx.cond": Cond, +} + def to_title_case(text: str) -> str: return " ".join(word.capitalize() for word in text.split("_")) @@ -65,6 +70,9 @@ def get_components_from_frontmatter(filepath: str) -> list: return [] components = [] for comp_str in doc.frontmatter.components: + if component := SPECIAL_COMPONENT_DOCS.get(comp_str): + components.append((component, comp_str)) + continue component = eval(comp_str) if isinstance(component, type): components.append((component, comp_str)) diff --git a/docs/app/reflex_docs/pages/docs/component.py b/docs/app/reflex_docs/pages/docs/component.py index 84785e2f4e8..07884e9a51c 100644 --- a/docs/app/reflex_docs/pages/docs/component.py +++ b/docs/app/reflex_docs/pages/docs/component.py @@ -81,6 +81,8 @@ class PropDocsState(rx.State): "Container", "Spacer", "Skeleton", + "Section", + "Tooltip", ] @@ -765,7 +767,7 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component: interactive_component = rx.fragment() controls_panel: rx.Component = rx.fragment() - if interactive_controls: + if not isinstance(comp, Fragment) and interactive_controls: controls_panel = rx.box( *[ rx.box( diff --git a/docs/app/reflex_docs/pages/docs/library.py b/docs/app/reflex_docs/pages/docs/library.py index a513527c634..e0991cb140b 100644 --- a/docs/app/reflex_docs/pages/docs/library.py +++ b/docs/app/reflex_docs/pages/docs/library.py @@ -5,6 +5,37 @@ from reflex_docs.templates.docpage import docpage, h1_comp, text_comp_2 +def get_display_name(name: str) -> str: + normalized = to_snake_case(name) + if normalized == "html": + return "HTML" + if normalized == "svg": + return "SVG" + return to_title_case(normalized, sep=" ") + + +HTML_COMPONENT_ORDER = { + "html": 0, + "text": 1, + "layout": 2, + "forms": 3, + "media": 4, + "tables": 5, + "svg": 6, +} + + +def get_components_for_category(category: str, components: list) -> list: + if to_snake_case(category) != "html": + return components + return sorted( + components, + key=lambda component: HTML_COMPONENT_ORDER.get( + to_snake_case(component[0]), len(HTML_COMPONENT_ORDER) + ), + ) + + def component_grid(): from reflex_docs.pages.docs import component_list, graphing_components from reflex_docs.templates.docpage.sidebar.sidebar_items import get_component_link @@ -17,7 +48,7 @@ def generate_gallery( rx.box( rx.link( rx.el.h1( - to_title_case(to_snake_case(category), sep=" "), + get_display_name(category), class_name="font-large text-slate-12", ), get_icon("new_tab", class_name="text-slate-11 [&>svg]:size-4"), @@ -28,7 +59,7 @@ def generate_gallery( rx.box( *[ rx.link( - to_title_case(to_snake_case(c[0]), sep=" "), + get_display_name(c[0]), href=get_component_link( category=category, clist=c, @@ -36,7 +67,9 @@ def generate_gallery( ), class_name="font-small text-slate-11 hover:!text-violet-9 transition-color w-fit", ) - for c in components[category] + for c in get_components_for_category( + category, components[category] + ) ], class_name="flex flex-col gap-2.5 px-4 py-2 border-t border-slate-5", ), diff --git a/docs/app/reflex_docs/pages/library_previews.py b/docs/app/reflex_docs/pages/library_previews.py index 654d4d680aa..30a7421188b 100644 --- a/docs/app/reflex_docs/pages/library_previews.py +++ b/docs/app/reflex_docs/pages/library_previews.py @@ -5,24 +5,72 @@ from reflex_docs.templates.docpage import docpage, h1_comp, text_comp_2 +def get_display_name(name: str) -> str: + normalized = to_snake_case(name) + if normalized == "html": + return "HTML" + if normalized == "svg": + return "SVG" + return to_title_case(normalized, sep=" ") + + +HTML_COMPONENT_ORDER = { + "html": 0, + "text": 1, + "layout": 2, + "forms": 3, + "media": 4, + "tables": 5, + "svg": 6, +} + +HTML_CARD_PREVIEWS = { + "html": ("layout", "box"), + "text": ("typography", "text"), + "layout": ("layout", "grid"), + "forms": ("forms", "input"), + "media": ("media", "image"), + "tables": ("tables-and-data-grids", "table"), + "svg": ("data-display", "icon"), +} + + +def get_components_for_category(category: str, components: list) -> list: + if to_snake_case(category) != "html": + return components + return sorted( + components, + key=lambda component: HTML_COMPONENT_ORDER.get( + to_snake_case(component[0]), len(HTML_COMPONENT_ORDER) + ), + ) + + +def get_preview_asset(name: str, section: str) -> tuple[str, str]: + if to_snake_case(section) == "html": + return HTML_CARD_PREVIEWS.get(to_snake_case(name), ("layout", "box")) + return section.lower(), name.lower() + + def component_card(name: str, link: str, section: str) -> rx.Component: + preview_section, preview_name = get_preview_asset(name, section) return rx.link( rx.box( rx.image( - src=f"{REFLEX_ASSETS_CDN}components_previews/{section.lower()}/light/{name.lower()}.svg", + src=f"{REFLEX_ASSETS_CDN}components_previews/{preview_section}/light/{preview_name}.svg", loading="lazy", alt=f"Image preview of {name}", class_name="object-contain object-center h-full w-full dark:hidden", ), rx.image( - src=f"{REFLEX_ASSETS_CDN}components_previews/{section.lower()}/dark/{name.lower()}.svg", + src=f"{REFLEX_ASSETS_CDN}components_previews/{preview_section}/dark/{preview_name}.svg", loading="lazy", alt=f"Image preview of {name}", class_name="object-contain object-center h-full w-full dark:block hidden", ), rx.box( rx.text( - to_title_case(to_snake_case(name), sep=" "), + get_display_name(name), class_name="truncate font-base text-slate-12", ), rx.icon("chevron-right", size=14, class_name="!text-slate-9"), @@ -65,7 +113,7 @@ def page() -> rx.Component: component_list = get_component_list(type) return rx.box( rx.box( - h1_comp(text=to_title_case(to_snake_case(component_category), sep=" ")), + h1_comp(text=get_display_name(component_category)), text_comp_2( text=description, ), @@ -82,7 +130,9 @@ def page() -> rx.Component: ), section=component_category, ) - for component in component_list[component_category] + for component in get_components_for_category( + component_category, component_list[component_category] + ) ], class_name="gap-6 grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3", ), @@ -120,7 +170,7 @@ def page() -> rx.Component: }, "html": { "path": "html", - "description": "Components that help with dynamic rendering, such as conditional rendering and dynamic components. These are useful for creating responsive and interactive user interfaces.", + "description": "Low-level HTML elements exposed through the rx.el namespace. These are useful when you need native browser elements with direct styling control.", "component_category": "Html", }, "layout": { diff --git a/docs/app/reflex_docs/templates/docpage/docpage.py b/docs/app/reflex_docs/templates/docpage/docpage.py index 8f93a5c2f70..6ca2204cddc 100644 --- a/docs/app/reflex_docs/templates/docpage/docpage.py +++ b/docs/app/reflex_docs/templates/docpage/docpage.py @@ -19,7 +19,6 @@ from reflex_site_shared.components.marketing_button import button as marketing_button from reflex_site_shared.components.server_status import server_status from reflex_site_shared.route import Route, get_path -from reflex_site_shared.styles.colors import c_color from reflex_site_shared.utils.docpage import right_sidebar_item_highlight from reflex_site_shared.views.footer import dark_mode_toggle @@ -56,28 +55,51 @@ def footer_link_flex(heading: str, links): ) -def thumb_card(score: int, icon: str) -> rx.Component: +def thumb_card(score: int, icon: str, label: str) -> rx.Component: return rx.el.button( ui.icon( icon, - color=rx.cond( - FeedbackState.score == score, c_color("slate", 11), c_color("slate", 9) - ), size=16, ), - background_color=rx.cond( - FeedbackState.score == score, c_color("slate", 3), c_color("white", 1) - ), + label, + type="button", on_click=FeedbackState.set_score(score), - class_name="transition-bg hover:bg-slate-3 shadow-medium border border-slate-4 rounded-lg items-center justify-center cursor-pointer p-2 size-9 flex", + class_name=rx.cond( + FeedbackState.score == score, + "flex h-9 items-center justify-center gap-2 rounded-md border border-violet-6 bg-violet-3 px-3 text-sm font-medium text-violet-11 transition-colors", + "flex h-9 items-center justify-center gap-2 rounded-md border border-slate-5 bg-slate-1 px-3 text-sm font-medium text-slate-9 transition-colors hover:bg-slate-3 hover:text-slate-11", + ), ) def thumbs_cards() -> rx.Component: return rx.hstack( - thumb_card(1, "ThumbsUpIcon"), - thumb_card(0, "ThumbsDownIcon"), + thumb_card(1, "ThumbsUpIcon", "Helpful"), + thumb_card(0, "ThumbsDownIcon", "Not helpful"), gap="8px", + wrap="wrap", + ) + + +def feedback_choice_button(label: str, icon: str, score: int, class_name: str): + active = FeedbackState.score == score + return rx.el.button( + ui.icon(icon), + label, + type="button", + class_name=rx.cond( + active, + ui.cn( + "border-violet-6 bg-violet-3 text-violet-11 shadow-none", + class_name, + ), + ui.cn( + "border-slate-5 bg-slate-1 text-slate-9 shadow-large hover:bg-slate-3 hover:text-slate-11", + class_name, + ), + ), + aria_label=label, + on_click=FeedbackState.set_score(score), ) @@ -122,31 +144,21 @@ def feedback_content() -> rx.Component: def feedback_button() -> rx.Component: - thumb_cn = " flex flex-row items-center justify-center gap-2 text-slate-9 whitespace-nowrap border border-slate-5 bg-slate-1 shadow-large cursor-pointer transition-bg hover:bg-slate-3 font-small" + thumb_cn = "w-full gap-2 px-3 py-0.5 flex flex-row items-center justify-center whitespace-nowrap border cursor-pointer transition-colors font-small" return ui.popover.root( ui.popover.trigger( render_=rx.el.div( - rx.el.button( - ui.icon("ThumbsUpIcon"), + feedback_choice_button( "Yes", - type="button", - class_name=ui.cn( - "w-full gap-2 border-r-0 px-3 py-0.5 rounded-[20px_0_0_20px]", - thumb_cn, - ), - aria_label="Yes", - on_click=FeedbackState.set_score(1), + "ThumbsUpIcon", + 1, + ui.cn("rounded-[20px_0_0_20px] border-r-0", thumb_cn), ), - rx.el.button( - ui.icon("ThumbsDownIcon"), + feedback_choice_button( "No", - type="button", - class_name=ui.cn( - "w-full gap-2 border-r-0 px-3 py-0.5 rounded-[0_20px_20px_0]", - thumb_cn, - ), - aria_label="No", - on_click=FeedbackState.set_score(0), + "ThumbsDownIcon", + 0, + ui.cn("rounded-[0_20px_20px_0]", thumb_cn), ), class_name="w-full lg:w-auto items-center flex flex-row", ), diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py index 62bec2e0ccc..8a86bf0d3fa 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py @@ -15,7 +15,7 @@ mcp_items, skills_items, ) -from .sidebar_items.component_lib import component_lib, graphing_libs +from .sidebar_items.component_lib import component_lib, graphing_libs, html_lib from .sidebar_items.enterprise import ( enterprise_component_items, enterprise_items, @@ -280,6 +280,7 @@ def append_to_items(items, flat_items): + hosting + component_lib + graphing_libs + + html_lib + recipes + ai_builder_overview_items + ai_builder_integrations @@ -412,6 +413,7 @@ def sidebar_comp( frontend_index: list[int], backend_index: list[int], hosting_index: list[int], + html_lib_index: list[int], graphing_libs_index: list[int], api_reference_index: list[int], recipes_index: list[int], @@ -658,6 +660,13 @@ def sidebar_comp( graphing_libs_index, url, ), + create_sidebar_section( + "Other", + "/library/html/", + html_lib, + html_lib_index, + url, + ), rx.link( # pyright: ignore [reportCallIssue] rx.box( # pyright: ignore [reportCallIssue] rx.box( # pyright: ignore [reportCallIssue] @@ -737,6 +746,7 @@ def sidebar(url=None, width: str = "100%") -> rx.Component: frontend_index = calculate_index(frontend, url) backend_index = calculate_index(backend, url) hosting_index = calculate_index(hosting, url) + html_lib_index = calculate_index(html_lib, url) graphing_libs_index = calculate_index(graphing_libs, url) api_reference_index = calculate_index(api_reference, url) recipes_index = calculate_index(recipes, url) @@ -758,6 +768,7 @@ def sidebar(url=None, width: str = "100%") -> rx.Component: frontend_index=frontend_index, backend_index=backend_index, hosting_index=hosting_index, + html_lib_index=html_lib_index, graphing_libs_index=graphing_libs_index, api_reference_index=api_reference_index, recipes_index=recipes_index, diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/component_lib.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/component_lib.py index 670eed344fe..7663a6fae47 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/component_lib.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/component_lib.py @@ -4,6 +4,39 @@ from ..state import SideBarItem +def get_display_name(name: str) -> str: + normalized = to_snake_case(name) + if normalized == "html": + return "HTML" + if normalized == "svg": + return "SVG" + return to_title_case(normalized, sep=" ") + + +def is_html_category(category: str) -> bool: + return to_snake_case(category.replace("-", " ")) == "html" + + +HTML_COMPONENT_ORDER = { + "html": 0, + "text": 1, + "layout": 2, + "forms": 3, + "media": 4, + "tables": 5, + "svg": 6, +} + + +def sort_html_components(components: list) -> list: + return sorted( + components, + key=lambda component: HTML_COMPONENT_ORDER.get( + to_snake_case(component[0]), len(HTML_COMPONENT_ORDER) + ), + ) + + def get_component_link(category, clist, prefix="") -> str: component_name = rx.utils.format.to_kebab_case(clist[0]) # construct the component link. The component name points to the name of the md file. @@ -14,7 +47,7 @@ def get_category_children(category, category_list, prefix=""): category = category.replace("-", " ") if isinstance(category_list, dict): return SideBarItem( - names=category, + names=get_display_name(category), children=[ get_category_children(c, category_list[c]) for c in category_list ], @@ -27,14 +60,12 @@ def get_category_children(category, category_list, prefix=""): ) ) for c in category_list: - component_name = to_snake_case(c[0]) - name = to_title_case(component_name, sep=" ") item = SideBarItem( - names=name, + names=get_display_name(c[0]), link=get_component_link(category, c, prefix=prefix), ) category_item_children.append(item) - return SideBarItem(names=category, children=category_item_children) + return SideBarItem(names=get_display_name(category), children=category_item_children) def get_sidebar_items_component_lib(): @@ -43,6 +74,8 @@ def get_sidebar_items_component_lib(): library_item_children = [] for category in component_list: + if is_html_category(category): + continue category_item = get_category_children(category, component_list[category]) library_item_children.append(category_item) @@ -51,6 +84,24 @@ def get_sidebar_items_component_lib(): ] +def get_sidebar_items_html(): + from reflex_docs.pages.docs import component_list + + html_children = [ + SideBarItem(names="Overview", link="/library/html/"), + ] + html_children.extend( + [ + SideBarItem( + names=get_display_name(component[0]), + link=get_component_link("Html", component), + ) + for component in sort_html_components(component_list.get("Html", [])) + ] + ) + return [SideBarItem(names="HTML", children=html_children)] + + def get_sidebar_items_graphings(): from reflex_docs.pages.docs import graphing_components @@ -65,4 +116,5 @@ def get_sidebar_items_graphings(): component_lib = get_sidebar_items_component_lib() +html_lib = get_sidebar_items_html() graphing_libs = get_sidebar_items_graphings() diff --git a/docs/getting_started/basics.md b/docs/getting_started/basics.md index ebe5e6513f0..dd555f1cc2d 100644 --- a/docs/getting_started/basics.md +++ b/docs/getting_started/basics.md @@ -48,7 +48,7 @@ def my_container(): ) ``` -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/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(): diff --git a/docs/getting_started/introduction.md b/docs/getting_started/introduction.md index 81e9545ecb2..d3c4cfdc87f 100644 --- a/docs/getting_started/introduction.md +++ b/docs/getting_started/introduction.md @@ -55,6 +55,28 @@ class IntroTabsState(rx.State): self.value = val +def counter_code_section(code: str, tab: str) -> rx.Component: + active = IntroTabsState.value == tab + return rx.box( + rx.code_block( + code, + class_name="code-block counter-code-block", + ), + background=rx.cond(active, "var(--c-violet-3)", "transparent"), + border_left=rx.cond( + active, + "3px solid var(--c-violet-9)", + "3px solid transparent", + ), + padding="0.875rem 1.5rem", + class_name="w-full transition-colors", + ) + + +def counter_code_gap() -> rx.Component: + return rx.box(height="0.875rem", flex_shrink="0") + + def tabs(): return rx.tabs.root( rx.tabs.list( @@ -117,13 +139,11 @@ Here is the full code for this example: tabs() ``` -```python demo box +```python eval rx.box( - rx.code_block( - """import reflex as rx """, - class_name="code-block !bg-transparent !border-none", - ), - rx.code_block( + counter_code_section("""import reflex as rx """, ""), + counter_code_gap(), + counter_code_section( """class State(rx.State): count: int = 0 @@ -134,19 +154,10 @@ rx.box( @rx.event def decrement(self): self.count -= 1""", - background=rx.cond( - IntroTabsState.value == "tab2", - "var(--c-slate-3) !important", - "transparent", - ), - border=rx.cond( - IntroTabsState.value == "tab2", - "1px solid var(--c-slate-5)", - "none !important", - ), - class_name="code-block", + "tab2", ), - rx.code_block( + counter_code_gap(), + counter_code_section( """def index(): return rx.hstack( rx.button( @@ -162,34 +173,18 @@ rx.box( ), spacing="4", )""", - border=rx.cond( - IntroTabsState.value == "tab1", - "1px solid var(--c-slate-5)", - "none !important", - ), - background=rx.cond( - IntroTabsState.value == "tab1", - "var(--c-slate-3) !important", - "transparent", - ), - class_name="code-block", + "tab1", ), - rx.code_block( + counter_code_gap(), + counter_code_section( """app = rx.App() app.add_page(index)""", - background=rx.cond( - IntroTabsState.value == "tab3", - "var(--c-slate-3) !important", - "transparent", - ), - border=rx.cond( - IntroTabsState.value == "tab3", - "1px solid var(--c-slate-5)", - "none !important", - ), - class_name="code-block", + "tab3", + ), + class_name=( + "w-full flex flex-col overflow-hidden rounded-xl border " + "border-slate-4 bg-slate-2 py-1" ), - class_name="w-full flex flex-col", ) ``` diff --git a/docs/library/dynamic-rendering/auto_scroll.md b/docs/library/dynamic-rendering/auto_scroll.md index 25cdc511c1f..73ff004feb4 100644 --- a/docs/library/dynamic-rendering/auto_scroll.md +++ b/docs/library/dynamic-rendering/auto_scroll.md @@ -1,3 +1,8 @@ +--- +components: + - rx.auto_scroll +--- + ```python exec import reflex as rx ``` diff --git a/docs/library/dynamic-rendering/cond.md b/docs/library/dynamic-rendering/cond.md index 4ba63ac6a70..4cf0d4b2bf6 100644 --- a/docs/library/dynamic-rendering/cond.md +++ b/docs/library/dynamic-rendering/cond.md @@ -1,3 +1,8 @@ +--- +components: + - rx.cond +--- + ```python exec import reflex as rx ``` diff --git a/docs/library/dynamic-rendering/foreach.md b/docs/library/dynamic-rendering/foreach.md index aee53edfc12..e32b38fa9eb 100644 --- a/docs/library/dynamic-rendering/foreach.md +++ b/docs/library/dynamic-rendering/foreach.md @@ -1,3 +1,8 @@ +--- +components: + - rx.foreach +--- + ```python exec import reflex as rx ``` diff --git a/docs/library/dynamic-rendering/match.md b/docs/library/dynamic-rendering/match.md index e9510e0430c..16e05a35aad 100644 --- a/docs/library/dynamic-rendering/match.md +++ b/docs/library/dynamic-rendering/match.md @@ -1,3 +1,8 @@ +--- +components: + - rx.match +--- + ```python exec import reflex as rx ``` diff --git a/docs/library/html/forms.md b/docs/library/html/forms.md new file mode 100644 index 00000000000..cd0a56f1047 --- /dev/null +++ b/docs/library/html/forms.md @@ -0,0 +1,18 @@ +--- +components: + - rx.el.fieldset + - rx.el.form + - rx.el.label + - rx.el.legend + - rx.el.meter + - rx.el.optgroup + - rx.el.option + - rx.el.output + - rx.el.progress + - rx.el.select + - rx.el.textarea +--- + +# HTML Form Elements + +Form-related HTML elements are available through the `rx.el` namespace. diff --git a/docs/library/html/html.md b/docs/library/html/html.md new file mode 100644 index 00000000000..b95278e9499 --- /dev/null +++ b/docs/library/html/html.md @@ -0,0 +1,22 @@ +--- +components: + - rx.el.button + - rx.el.div + - rx.el.input + - rx.el.p + - rx.el.span + - rx.el.a +--- + +# HTML + +Reflex also provides a set of HTML elements that can be used to create web pages. These elements are the same as the HTML elements that are used in web development. These elements come unstyled by default. You can style them using style props or tailwindcss classes. + +The most commonly used HTML elements are listed below. The full API reference is split into smaller pages: + +- [Text elements](/docs/library/html/text) +- [Document and layout elements](/docs/library/html/layout) +- [Form elements](/docs/library/html/forms) +- [Media and embedded elements](/docs/library/html/media) +- [Table elements](/docs/library/html/tables) +- [SVG elements](/docs/library/html/svg) diff --git a/docs/library/html/layout.md b/docs/library/html/layout.md new file mode 100644 index 00000000000..54f68001495 --- /dev/null +++ b/docs/library/html/layout.md @@ -0,0 +1,43 @@ +--- +components: + - rx.el.address + - rx.el.article + - rx.el.aside + - rx.el.blockquote + - rx.el.body + - rx.el.dd + - rx.el.Del + - rx.el.details + - rx.el.dialog + - rx.el.dl + - rx.el.dt + - rx.el.figcaption + - rx.el.footer + - rx.el.h1 + - rx.el.h2 + - rx.el.h3 + - rx.el.h4 + - rx.el.h5 + - rx.el.h6 + - rx.el.head + - rx.el.header + - rx.el.hr + - rx.el.html + - rx.el.li + - rx.el.link + - rx.el.main + - rx.el.nav + - rx.el.noscript + - rx.el.ol + - rx.el.portal + - rx.el.pre + - rx.el.script + - rx.el.section + - rx.el.template + - rx.el.title + - rx.el.ul +--- + +# HTML Layout Elements + +Document structure and layout HTML elements are available through the `rx.el` namespace. diff --git a/docs/library/html/media.md b/docs/library/html/media.md new file mode 100644 index 00000000000..3bc4aa76fb8 --- /dev/null +++ b/docs/library/html/media.md @@ -0,0 +1,20 @@ +--- +components: + - rx.el.area + - rx.el.audio + - rx.el.canvas + - rx.el.embed + - rx.el.iframe + - rx.el.img + - rx.el.math + - rx.el.meta + - rx.el.object + - rx.el.picture + - rx.el.source + - rx.el.track + - rx.el.video +--- + +# HTML Media Elements + +Media and embedded HTML elements are available through the `rx.el` namespace. diff --git a/docs/library/html/svg.md b/docs/library/html/svg.md new file mode 100644 index 00000000000..07717f4bf55 --- /dev/null +++ b/docs/library/html/svg.md @@ -0,0 +1,14 @@ +--- +components: + - rx.el.svg.circle + - rx.el.svg.defs + - rx.el.svg.linear_gradient + - rx.el.svg.polygon + - rx.el.svg.path + - rx.el.svg.rect + - rx.el.svg.stop +--- + +# HTML SVG Elements + +SVG elements are available through the `rx.el.svg` namespace. diff --git a/docs/library/html/tables.md b/docs/library/html/tables.md new file mode 100644 index 00000000000..ead725aa67e --- /dev/null +++ b/docs/library/html/tables.md @@ -0,0 +1,17 @@ +--- +components: + - rx.el.caption + - rx.el.col + - rx.el.colgroup + - rx.el.table + - rx.el.tbody + - rx.el.td + - rx.el.tfoot + - rx.el.th + - rx.el.thead + - rx.el.tr +--- + +# HTML Table Elements + +Table HTML elements are available through the `rx.el` namespace. diff --git a/docs/library/html/text.md b/docs/library/html/text.md new file mode 100644 index 00000000000..c6ec33edc53 --- /dev/null +++ b/docs/library/html/text.md @@ -0,0 +1,33 @@ +--- +components: + - rx.el.abbr + - rx.el.b + - rx.el.bdi + - rx.el.bdo + - rx.el.br + - rx.el.cite + - rx.el.code + - rx.el.data + - rx.el.dfn + - rx.el.em + - rx.el.i + - rx.el.kbd + - rx.el.mark + - rx.el.q + - rx.el.rp + - rx.el.rt + - rx.el.ruby + - rx.el.s + - rx.el.samp + - rx.el.small + - rx.el.strong + - rx.el.sub + - rx.el.sup + - rx.el.time + - rx.el.u + - rx.el.wbr +--- + +# HTML Text Elements + +Text-level HTML elements are available through the `rx.el` namespace. diff --git a/docs/library/other/html.md b/docs/library/other/html.md deleted file mode 100644 index f74ec858c9d..00000000000 --- a/docs/library/other/html.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -components: - - rx.el.a - - rx.el.abbr - - rx.el.address - - rx.el.area - - rx.el.article - - rx.el.aside - - rx.el.audio - - rx.el.b - - rx.el.bdi - - rx.el.bdo - - rx.el.blockquote - - rx.el.body - - rx.el.br - - rx.el.button - - rx.el.canvas - - rx.el.caption - - rx.el.cite - - rx.el.code - - rx.el.col - - rx.el.colgroup - - rx.el.data - - rx.el.dd - - rx.el.Del - - rx.el.details - - rx.el.dfn - - rx.el.dialog - - rx.el.div - - rx.el.dl - - rx.el.dt - - rx.el.em - - rx.el.embed - - rx.el.fieldset - - rx.el.figcaption - - rx.el.footer - - rx.el.form - - rx.el.h1 - - rx.el.h2 - - rx.el.h3 - - rx.el.h4 - - rx.el.h5 - - rx.el.h6 - - rx.el.head - - rx.el.header - - rx.el.hr - - rx.el.html - - rx.el.i - - rx.el.iframe - - rx.el.img - - rx.el.input - - rx.el.ins - - rx.el.kbd - - rx.el.label - - rx.el.legend - - rx.el.li - - rx.el.link - - rx.el.main - - rx.el.mark - - rx.el.math - - rx.el.meta - - rx.el.meter - - rx.el.nav - - rx.el.noscript - - rx.el.object - - rx.el.ol - - rx.el.optgroup - - rx.el.option - - rx.el.output - - rx.el.p - - rx.el.picture - - rx.el.portal - - rx.el.pre - - rx.el.progress - - rx.el.q - - rx.el.rp - - rx.el.rt - - rx.el.ruby - - rx.el.s - - rx.el.samp - - rx.el.script - - rx.el.section - - rx.el.select - - rx.el.small - - rx.el.source - - rx.el.span - - rx.el.strong - - rx.el.sub - - rx.el.sup - - rx.el.svg.circle - - rx.el.svg.defs - - rx.el.svg.linear_gradient - - rx.el.svg.polygon - - rx.el.svg.path - - rx.el.svg.rect - - rx.el.svg.stop - - rx.el.table - - rx.el.tbody - - rx.el.td - - rx.el.template - - rx.el.textarea - - rx.el.tfoot - - rx.el.th - - rx.el.thead - - rx.el.time - - rx.el.title - - rx.el.tr - - rx.el.track - - rx.el.u - - rx.el.ul - - rx.el.video - - rx.el.wbr ---- - -# HTML - -Reflex also provides a set of HTML elements that can be used to create web pages. These elements are the same as the HTML elements that are used in web development. These elements come unstyled bhy default. You can style them using style props or tailwindcss classes. - -The following is a list of the HTML elements that are available in Reflex: diff --git a/docs/library/other/memo.md b/docs/library/other/memo.md index ba61137957a..f108d34a6a2 100644 --- a/docs/library/other/memo.md +++ b/docs/library/other/memo.md @@ -171,3 +171,17 @@ Avoid using `rx.memo` for: - Simple components where the memoization overhead might exceed the performance gain - Components that almost always receive different props on re-render + +## API Reference + +### `rx.memo` + +```python +rx.memo(component_fn) +``` + +Decorates a function that returns a Reflex component so it can be reused as a memoized component. The function arguments must be type annotated, and memoized components should be called with keyword arguments. + +| Argument | Type | Description | +| --- | --- | --- | +| `component_fn` | `Callable[..., rx.Component]` | Function that returns the component to memoize. | diff --git a/docs/styling/tailwind.md b/docs/styling/tailwind.md index ff9974c3080..12158903442 100644 --- a/docs/styling/tailwind.md +++ b/docs/styling/tailwind.md @@ -254,4 +254,4 @@ def tailwind_dynamic_class_demo(): Reflex core components are built on Radix Themes, which means they come with pre-defined styling. When you apply Tailwind classes to these components, you may encounter styling conflicts or unexpected behavior as the Tailwind styles compete with the built-in Radix styles. -For the best experience when using Tailwind CSS in your Reflex application, we recommend using the lower-level `rx.el` components. These components don't have pre-applied styles, giving you complete control over styling with Tailwind classes without any conflicts. Check the list of HTML components [here](/docs/library/other/html). +For the best experience when using Tailwind CSS in your Reflex application, we recommend using the lower-level `rx.el` components. These components don't have pre-applied styles, giving you complete control over styling with Tailwind classes without any conflicts. Check the list of HTML components [here](/docs/library/html/). From 5e359d515ffbe0ad8562d5fd8fbd29d57ef3ac77 Mon Sep 17 00:00:00 2001 From: Alek Petuskey Date: Tue, 28 Apr 2026 21:57:48 -0700 Subject: [PATCH 11/12] Format component library sidebar --- .../sidebar/sidebar_items/component_lib.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/component_lib.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/component_lib.py index 7663a6fae47..10260aaa5f0 100644 --- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/component_lib.py +++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/component_lib.py @@ -65,7 +65,9 @@ def get_category_children(category, category_list, prefix=""): link=get_component_link(category, c, prefix=prefix), ) category_item_children.append(item) - return SideBarItem(names=get_display_name(category), children=category_item_children) + return SideBarItem( + names=get_display_name(category), children=category_item_children + ) def get_sidebar_items_component_lib(): @@ -90,15 +92,13 @@ def get_sidebar_items_html(): html_children = [ SideBarItem(names="Overview", link="/library/html/"), ] - html_children.extend( - [ - SideBarItem( - names=get_display_name(component[0]), - link=get_component_link("Html", component), - ) - for component in sort_html_components(component_list.get("Html", [])) - ] - ) + html_children.extend([ + SideBarItem( + names=get_display_name(component[0]), + link=get_component_link("Html", component), + ) + for component in sort_html_components(component_list.get("Html", [])) + ]) return [SideBarItem(names="HTML", children=html_children)] From a647998803ebf7800202d3c1dbbf2f60f1897080 Mon Sep 17 00:00:00 2001 From: Carlos Date: Wed, 29 Apr 2026 12:10:01 +0200 Subject: [PATCH 12/12] fix double /docs links --- docs/getting_started/introduction.md | 10 +++++----- .../reflex_site_shared/components/blocks/typography.py | 10 ++++------ 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/getting_started/introduction.md b/docs/getting_started/introduction.md index d3c4cfdc87f..f85b8715ee7 100644 --- a/docs/getting_started/introduction.md +++ b/docs/getting_started/introduction.md @@ -272,16 +272,16 @@ Create the app and register the page at the base route. ```md alert info # Keep learning -- [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. +- [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. ``` ```md alert info # Ship faster with AI - [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. +- [Reflex Cloud](/docs/hosting/deploy-quick-start/) — one-command deploy. ``` -Browse our [open-source templates](/docs/getting_started/open_source_templates), or press `Cmd+K` / `Ctrl+K` to search the docs. +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-site-shared/src/reflex_site_shared/components/blocks/typography.py b/packages/reflex-site-shared/src/reflex_site_shared/components/blocks/typography.py index ccf0ed8edb6..a0928d7072f 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 @@ -110,12 +110,11 @@ def doclink(text: str, href: str, **props) -> rx.Component: Returns: The styled link. """ - return rx.link( + return rx.el.elements.a( text, - underline="always", href=href, **props, - class_name="!text-m-slate-12 dark:!text-m-slate-3 !decoration-m-slate-12 dark:!decoration-m-slate-3", + class_name="text-m-slate-12 dark:text-m-slate-3 decoration-m-slate-12 dark:decoration-m-slate-3 underline", ) @@ -130,10 +129,9 @@ def doclink2(text: str, **props) -> rx.Component: Returns: The styled link. """ - return rx.link( + return rx.el.elements.a( text, - underline="always", **props, style=fonts.base, - class_name="!text-m-slate-12 dark:!text-m-slate-3 !decoration-m-slate-12 dark:!decoration-m-slate-3", + class_name="text-m-slate-12 dark:text-m-slate-3 decoration-m-slate-12 dark:decoration-m-slate-3 underline", )