diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml
index e02b3bf921f..b79fe5c40db 100644
--- a/.github/workflows/integration_tests.yml
+++ b/.github/workflows/integration_tests.yml
@@ -164,9 +164,9 @@ jobs:
which npm && npm -v
uv run --active --no-sync bash scripts/integration.sh ./docs/app prod
- - name: Validate /docs links against generated sitemap
+ - name: Validate docs links and published SEO metadata
working-directory: ./docs/app
- run: uv run --active --no-sync pytest --runxfail tests/test_doc_links.py -v
+ run: uv run --active --no-sync pytest --runxfail tests/test_doc_links.py tests/test_published_seo.py -v
- name: Run docs frontend regression tests
working-directory: ./docs/app
diff --git a/docs/advanced_onboarding/code_structure.md b/docs/advanced_onboarding/code_structure.md
index 2e6f103a60d..e9877955256 100644
--- a/docs/advanced_onboarding/code_structure.md
+++ b/docs/advanced_onboarding/code_structure.md
@@ -259,7 +259,7 @@ component.
### External Components
-Reflex 0.4.3 introduced support for the [`reflex component` CLI commands](/docs/custom-components/overview), which makes it easy
+Reflex 0.4.3 introduced support for the [`reflex component` CLI commands](/docs/custom-components/overview/), which makes it easy
to bundle up common functionality to publish on PyPI as a standalone Python package
that can be installed and used in any Reflex app.
diff --git a/docs/advanced_onboarding/configuration.md b/docs/advanced_onboarding/configuration.md
index 68f2eb5306a..f8b09d2848b 100644
--- a/docs/advanced_onboarding/configuration.md
+++ b/docs/advanced_onboarding/configuration.md
@@ -45,7 +45,7 @@ Finally, you can override the configuration file and environment variables by pa
uv run reflex run --frontend-port 3001
```
-See the [CLI reference](/docs/api-reference/cli) for all the arguments available.
+See the [CLI reference](/docs/api-reference/cli/) for all the arguments available.
## Loading a .env File
diff --git a/docs/advanced_onboarding/how-reflex-works.md b/docs/advanced_onboarding/how-reflex-works.md
index 5ebd52537f7..4d9f5f28d19 100644
--- a/docs/advanced_onboarding/how-reflex-works.md
+++ b/docs/advanced_onboarding/how-reflex-works.md
@@ -117,15 +117,15 @@ Many of our core components are based on [Radix](https://radix-ui.com/), a popul
We chose React because it is a popular library with a huge ecosystem. Our goal isn't to recreate the web ecosystem, but to make it accessible to Python developers.
-This also lets our users bring their own components if we don't have a component they need. Users can [wrap their own React components](/docs/wrapping-react/overview) and then [publish them](/docs/custom-components/overview) for others to use. Over time we will build out our [third party component ecosystem](/docs/custom-components/overview) so that users can easily find and use components that others have built.
+This also lets our users bring their own components if we don't have a component they need. Users can [wrap their own React components](/docs/wrapping-react/overview/) and then [publish them](/docs/custom-components/overview/) for others to use. Over time we will build out our [third party component ecosystem](/docs/custom-components/overview/) so that users can easily find and use components that others have built.
### Styling
We wanted to make sure Reflex apps look good out of the box, while still giving developers full control over the appearance of their app.
-We have a core [theming system](/docs/styling/theming) that lets you set high level styling options such as dark mode and accent color throughout your app to give it a unified look and feel.
+We have a core [theming system](/docs/styling/theming/) that lets you set high level styling options such as dark mode and accent color throughout your app to give it a unified look and feel.
-Beyond this, Reflex components can be styled using the full power of CSS. We leverage the [Emotion](https://emotion.sh/docs/introduction) library to allow "CSS-in-Python" styling, so you can pass any CSS prop as a keyword argument to a component. This includes [responsive props](/docs/styling/responsive) by passing a list of values.
+Beyond this, Reflex components can be styled using the full power of CSS. We leverage the [Emotion](https://emotion.sh/docs/introduction) library to allow "CSS-in-Python" styling, so you can pass any CSS prop as a keyword argument to a component. This includes [responsive props](/docs/styling/responsive/) by passing a list of values.
## Backend
@@ -200,7 +200,7 @@ On the frontend, we maintain an event queue of all pending events.
When an event is triggered, it is added to the queue. We have a `processing` flag to make sure only one event is processed at a time. This ensures that the state is always consistent and there aren't any race conditions with two event handlers modifying the state at the same time.
```md alert info
-# There are exceptions to this, such as [background events](/docs/events/background-events) which allow you to run events in the background without blocking the UI.
+# There are exceptions to this, such as [background events](/docs/events/background-events/) which allow you to run events in the background without blocking the UI.
```
Once the event is ready to be processed, it is sent to the backend through a WebSocket connection.
@@ -228,7 +228,7 @@ In our example, the `set_profile` event handler is run on the user's state. This
### State Updates
-Every time an event handler returns (or [yields](/docs/events/yield-events)), we save the state in the state manager and send the **state updates** to the frontend to update the UI.
+Every time an event handler returns (or [yields](/docs/events/yield-events/)), we save the state in the state manager and send the **state updates** to the frontend to update the UI.
To maintain performance as your state grows, internally Reflex keeps track of vars that were updated during the event handler (**dirty vars**). When the event handler is done processing, we find all the dirty vars and create a state update to send to the frontend.
diff --git a/docs/ai_builder/figma.md b/docs/ai_builder/figma.md
index efb67caf154..76242f3fe0d 100644
--- a/docs/ai_builder/figma.md
+++ b/docs/ai_builder/figma.md
@@ -1,3 +1,21 @@
+---
+meta_description: Check Figma integration availability in Reflex Build and learn how to use design references and the existing design-system workflow today.
+---
+
# Figma Integration
-Integration Coming Soon!
+A direct Figma integration is coming soon. In the meantime, use the design-system workflow below to establish your app's visual direction.
+
+## Work with design references today
+
+Use [design systems](/docs/ai/features/design-systems/) to guide the visual style of your Reflex Build app. Describe the layout, spacing, typography, and interactions you want, then review the generated result in the app preview.
+
+For a consistent result, identify the page you are building, describe its main sections, and make focused changes one at a time. The [best practices guide](/docs/ai/overview/best-practices/) explains how to plan work and iterate with the agent.
+
+## Choose your next step
+
+- [Reflex Build overview](/docs/ai/overview/what-is-reflex-build/): understand the integrated app-building workflow.
+- [Design systems](/docs/ai/features/design-systems/): configure the visual direction of an app.
+- [Build with AI](/docs/ai/): compare Reflex Build with using your own coding agent.
+
+This page will document setup and supported import behavior when the integration is available.
diff --git a/docs/ai_builder/integrations/agent_toolkit.md b/docs/ai_builder/integrations/agent_toolkit.md
index e6268b4984e..632818e8c7a 100644
--- a/docs/ai_builder/integrations/agent_toolkit.md
+++ b/docs/ai_builder/integrations/agent_toolkit.md
@@ -9,14 +9,16 @@ def _resource_card(
) -> rx.Component:
return rx.el.a(
rx.el.div(
- rx.el.h3(title, class_name="text-base font-semibold text-secondary-12"),
- rx.el.p(body, class_name="text-sm leading-6 text-secondary-11"),
- rx.el.div(action, class_name="text-sm font-semibold text-primary-10"),
- class_name="flex h-full flex-col gap-2 rounded-lg border border-secondary-a4 bg-white-1 p-4 transition-colors hover:bg-secondary-2 shadow-xs",
+ rx.el.h3(title, class_name="text-base font-book text-foreground"),
+ rx.el.p(
+ body, class_name="text-sm font-normal leading-6 text-muted-foreground"
+ ),
+ rx.el.div(action, class_name="text-sm font-book text-foreground"),
+ class_name="docs-catalog-card flex h-full flex-col gap-2 rounded-card border border-border-subtle bg-background p-5 transition-colors hover:bg-muted",
),
href=href,
target=target,
- class_name="no-underline",
+ class_name="no-underline rounded-card focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
)
diff --git a/docs/ai_builder/integrations/agents_md.md b/docs/ai_builder/integrations/agents_md.md
index e5c271d775d..47f1cef8d00 100644
--- a/docs/ai_builder/integrations/agents_md.md
+++ b/docs/ai_builder/integrations/agents_md.md
@@ -93,4 +93,4 @@ cp AGENTS.md /path/to/your/reflex-project/AGENTS.md
- [Reflex Agent Skills](/docs/ai/integrations/skills/) provide reusable workflows that the file references by name.
- [Reflex MCP](/docs/ai/integrations/mcp-overview/) provides structured documentation lookup at runtime.
-- The [llms.txt index](/llms.txt) gives a broad map of the documentation in one file.
+- The [llms.txt index](https://reflex.dev/docs/llms.txt) gives a broad map of the documentation in one file.
diff --git a/docs/ai_builder/integrations/skills.md b/docs/ai_builder/integrations/skills.md
index 8c1e4ab707f..6bf1561cf34 100644
--- a/docs/ai_builder/integrations/skills.md
+++ b/docs/ai_builder/integrations/skills.md
@@ -6,10 +6,10 @@ import reflex as rx
def _summary_card(kicker: str, title: str, body: str) -> rx.Component:
return rx.el.div(
- rx.el.div(kicker, class_name="text-xs font-semibold uppercase text-primary-10"),
- rx.el.h3(title, class_name="text-base font-semibold text-secondary-12"),
- rx.el.p(body, class_name="text-sm leading-6 text-secondary-11"),
- class_name="flex flex-col gap-2 rounded-lg border border-secondary-a4 bg-white-1 p-4",
+ rx.el.div(kicker, class_name="text-xs font-book text-muted-foreground"),
+ rx.el.h3(title, class_name="text-base font-book text-foreground"),
+ rx.el.p(body, class_name="text-sm font-normal leading-6 text-muted-foreground"),
+ class_name="flex flex-col gap-2 rounded-card border border-border-subtle bg-background p-5",
)
diff --git a/docs/ai_builder/overview/best_practices.md b/docs/ai_builder/overview/best_practices.md
index f902d64e7fa..fbf8b1d59f4 100644
--- a/docs/ai_builder/overview/best_practices.md
+++ b/docs/ai_builder/overview/best_practices.md
@@ -2,6 +2,14 @@
Reliable results come from clear context, focused prompts, and short review cycles. Start with the smallest useful version of your app, then add one workflow at a time.
+```python exec
+from reflex_docs.components.ai_guide_graphic import ai_guide_graphic
+```
+
+```python eval
+ai_guide_graphic()
+```
+
## Plan the First Version
Before generating, write down:
diff --git a/docs/api-reference/browser_javascript.md b/docs/api-reference/browser_javascript.md
index bd719855b60..f3cd32dcc74 100644
--- a/docs/api-reference/browser_javascript.md
+++ b/docs/api-reference/browser_javascript.md
@@ -23,7 +23,7 @@ Prefer to use the Python API whenever possible and file an issue if you need add
There are four ways to execute custom Javascript code into your Reflex app:
-- `rx.script` - Injects the script via `next/script` for efficient loading of inline and external Javascript code. Described further in the [component library](/docs/library/other/script).
+- `rx.script` - Injects the script via `next/script` for efficient loading of inline and external Javascript code. Described further in the [component library](/docs/library/other/script/).
- These components can be directly included in the body of a page, or they may
be passed to `rx.App(head_components=[rx.script(...)])` to be included in
the `
` tag of all pages.
@@ -34,7 +34,7 @@ These previous two methods can work in tandem to load external scripts and then
call functions defined within them in response to user events.
The following two methods are geared towards wrapping components and are
-described with examples in the [Wrapping React](/docs/wrapping-react/overview)
+described with examples in the [Wrapping React](/docs/wrapping-react/overview/)
section.
- `_get_hooks` and `_get_custom_code` in an `rx.Component` subclass
diff --git a/docs/api-reference/event_triggers.md b/docs/api-reference/event_triggers.md
index 4fc0ad9ed78..16320b1be3a 100644
--- a/docs/api-reference/event_triggers.md
+++ b/docs/api-reference/event_triggers.md
@@ -73,7 +73,7 @@ def protected_page():
return rx.text("Protected content")
```
-For more details on page load events, see the [page load events documentation](/docs/events/page-load-events).
+For more details on page load events, see the [page load events documentation](/docs/events/page-load-events/).
## Event Reference
diff --git a/docs/app/README.md b/docs/app/README.md
index 95f3d5eac42..cb311a98bca 100644
--- a/docs/app/README.md
+++ b/docs/app/README.md
@@ -55,10 +55,48 @@ If the build uses a custom `REFLEX_WEB_WORKDIR`, pass that environment variable
The `reflex-docs` integration CI jobs run the frontend tests after building the production site, using the installed React and bundler dependencies.
-Breadcrumbs and canonical URLs use `deploy_url` and `frontend_path` from the app config. Local runs use the framework's localhost default. Deployment jobs must set `REFLEX_DEPLOY_URL` to the origin serving that build (for example, `https://reflex.dev` in production or the staging origin).
+Breadcrumbs, canonicals, sharing URLs, and sitemap locations default to `https://reflex.dev/docs/`, including in a local preview. Staging builds may override `REFLEX_DEPLOY_URL` with their public origin. Supply only the origin (for example, `https://staging.example.com`), without `/docs`; `frontend_path` supplies that prefix. Local serving ports remain independent of the public metadata URL.
+
+After a production build, run `uv run pytest --runxfail tests/test_published_seo.py` to check every sitemap entry against the generated HTML canonical and sharing URLs. Publish the generated sitemap unchanged; do not prepend `/docs` to its absolute URLs. Recheck the deployed sitemap and HTML after deployment, since local checks cannot validate CDN rewriting or stale cached files.
The docs app serves permanent HTTP 301 redirects for its legacy URLs when the Reflex backend serves the frontend. In development or when HTML is hosted separately, requests reach the frontend instead: the redirect pages retain client navigation and prerendered HTML includes an immediate refresh, canonical link, noindex directive, and a usable destination link. That fallback navigates readers but returns HTTP 200. For HTTP 301 semantics on a separate frontend/CDN, configure redirects at that host's edge using the `redirects` list in `reflex_docs/reflex_docs.py`; backend middleware alone cannot redirect requests it never receives.
Docs pages intentionally omit the marketing site's pixels and session recording scripts. Search, examples, newsletter signup, and status information remain available.
The docs config enables `frontend_lazy_bundled_libraries`. Optional libraries registered for dynamic components load on the first dynamic-component evaluation, while React and the shared runtime stay available immediately. This prevents the full Radix namespace from being imported on every page. The framework default remains `False`; custom scripts that read optional libraries from `window.__reflex` directly should retain that default or await `window.__reflex_load()` first.
+
+## Editorial theme
+
+The docs app opts into `SharedSiteStylesPlugin(editorial=True)`, ported from
+[marketing PR #102](https://github.com/reflex-dev/marketing/pull/102). Its neutral
+palette and semantic roles live in `reflex_site_shared/styles/assets/editorial.css`;
+its button uses the marketing variant and size contract (36px compact, 44px
+standard, 48px large). The default shared theme remains available to other sites.
+Dark mode adapts the neutral ramp; Radix example accent colors remain available.
+The header, sidebar, breadcrumbs, and heading anchors share `--docs-header-height`,
+which follows the rendered announcement so dismissal leaves no empty strip.
+
+With a production preview running, verify responsive layout and interactions:
+
+```bash
+REFLEX_DOCS_PREVIEW_URL=http://localhost:3035 uv run pytest tests/test_editorial_browser.py
+```
+
+## Agent-readable exports
+
+The deployed app is mounted at `/docs/`. Its indexes are `/docs/llms.txt` and
+`/docs/llms-full.txt`; the docs home is `/docs/index.md`. Other canonical pages
+use the page URL with the trailing slash removed and `.md` appended. Each page
+advertises its exact Markdown destination with a `rel="alternate"` link.
+
+`AgentFilesPlugin` preserves authored guides and generated API source. After the
+production frontend is prerendered, it exports missing programmatic pages and
+eval-only catalogs from their main HTML content, then rebuilds both indexes from
+the canonical sitemap. This runs before compression. Navigation, decorative
+artwork, and footer controls are excluded; headings, destinations, examples,
+and tables remain readable. The complete exports require a production build.
+
+`tests/test_published_seo.py` checks every canonical page's Markdown asset and
+flags links that would redirect only to add a trailing slash. Component catalogs
+share identical inherited HTML props once per page; overrides and specific props
+stay beside their component, and the full API remains in Markdown.
diff --git a/docs/app/agent_files/_plugin.py b/docs/app/agent_files/_plugin.py
index 4a11adb4c72..4f6360f300c 100644
--- a/docs/app/agent_files/_plugin.py
+++ b/docs/app/agent_files/_plugin.py
@@ -49,8 +49,9 @@
MARKDOWN_DIRECTIVE = (
"> For AI agents: the complete documentation index is at "
- "[llms.txt]({llms_txt_url}). Markdown versions are available by appending "
- "`.md` or sending `Accept: text/markdown`."
+ "[llms.txt]({llms_txt_url}). For a Markdown version, remove the trailing slash "
+ "from the page URL and append `.md`. The docs home is available at "
+ "[index.md]({docs_home_markdown_url})."
)
PUBLIC_LLMS_TXT_URL = "https://reflex.dev/docs/llms.txt"
PUBLIC_EVENT_TRIGGERS_URL = "https://reflex.dev/docs/api-reference/event-triggers/"
@@ -156,31 +157,11 @@ def _strip_markdown_directive(source: str) -> str:
Returns:
The markdown content without the generated directive.
"""
- directive = _markdown_directive()
- if source.startswith(directive):
- return source.removeprefix(directive).lstrip()
+ if source.startswith("> For AI agents: the complete documentation index is at "):
+ return source.partition("\n")[2].lstrip()
return source
-def _include_index_entry_in_llms_txt(markdown_file: MarkdownIndexEntry) -> bool:
- """Return whether an index entry should appear in llms.txt.
-
- Args:
- markdown_file: The markdown index entry.
-
- Returns:
- Whether the entry should be included in llms.txt.
- """
- path = markdown_file.url_path.as_posix()
- return (
- path in MCP_DOC_PATHS
- or path in AGENT_TOOLKIT_DOC_PATHS
- or path in SKILLS_DOC_PATHS
- or not path.startswith("ai/")
- or path.startswith("ai/overview/")
- )
-
-
def _section_for_path(url_path: Path) -> str:
"""Return the llms.txt section for a generated markdown asset."""
path = url_path.as_posix()
@@ -271,7 +252,10 @@ def _markdown_directive() -> str:
Returns:
The markdown blockquote directive.
"""
- return MARKDOWN_DIRECTIVE.format(llms_txt_url=PUBLIC_LLMS_TXT_URL).strip()
+ return MARKDOWN_DIRECTIVE.format(
+ llms_txt_url=PUBLIC_LLMS_TXT_URL,
+ docs_home_markdown_url=public_url("/index.md"),
+ ).strip()
def generate_markdown_file_content(entry: MarkdownFileEntry) -> str:
@@ -707,6 +691,19 @@ def generate_dynamic_api_reference_files() -> tuple[tuple[Path, str], ...]:
return tuple(files)
+def generate_cloud_cli_markdown_files() -> tuple[tuple[Path, str], ...]:
+ """Export the same command Markdown used to render the Cloud CLI pages."""
+ from reflex_docs.pages.docs.cloud_cliref import modules
+
+ return tuple(
+ (
+ Path(f"hosting/cli/{name}.md"),
+ f"{_markdown_directive()}\n\n# {name.title()} ยท Reflex Cloud CLI\n\n{source.strip()}\n",
+ )
+ for name, source in modules.items()
+ )
+
+
def dynamic_api_reference_index_entries(
files: Sequence[tuple[Path, str]],
) -> tuple[MarkdownIndexEntry, ...]:
@@ -722,7 +719,7 @@ def dynamic_api_reference_index_entries(
MarkdownIndexEntry(
url_path=path,
title=_format_title(path.stem),
- section="API Reference",
+ section=_section_for_path(path),
)
for path, _content in files
)
@@ -734,8 +731,6 @@ def generate_llms_txt(
"""Generate an llms.txt index grouped by docs section."""
sections: OrderedDict[str, list[MarkdownIndexEntry]] = OrderedDict()
for markdown_file in markdown_files:
- if not _include_index_entry_in_llms_txt(markdown_file):
- continue
sections.setdefault(markdown_file.section, []).append(markdown_file)
lines = [
@@ -831,7 +826,13 @@ def generate_agent_files() -> tuple[tuple[Path, str | bytes], ...]:
)
for entry in markdown_file_entries
)
- dynamic_api_reference_files = generate_dynamic_api_reference_files()
+ from reflex_docs.pages.docs.cloud import CLOUD_OVERVIEW_MARKDOWN
+
+ dynamic_api_reference_files = (
+ *generate_dynamic_api_reference_files(),
+ *generate_cloud_cli_markdown_files(),
+ (Path("overview.md"), f"{_markdown_directive()}\n\n{CLOUD_OVERVIEW_MARKDOWN}"),
+ )
dynamic_api_reference_entries = dynamic_api_reference_index_entries(
dynamic_api_reference_files
)
@@ -878,6 +879,12 @@ def generate_agent_files() -> tuple[tuple[Path, str | bytes], ...]:
class AgentFilesPlugin(Plugin):
+ def post_build(self, **context):
+ """Complete agent exports from the final canonical page content."""
+ from ._rendered import export_rendered_pages
+
+ export_rendered_pages(context["static_dir"], get_config().frontend_path)
+
def get_static_assets(
self, **context: Unpack[CommonContext]
) -> Sequence[tuple[Path, str | bytes]]:
diff --git a/docs/app/agent_files/_rendered.py b/docs/app/agent_files/_rendered.py
new file mode 100644
index 00000000000..3c9a81e0819
--- /dev/null
+++ b/docs/app/agent_files/_rendered.py
@@ -0,0 +1,219 @@
+"""Export navigation and generated page content from prerendered HTML."""
+
+from __future__ import annotations
+
+import re
+from dataclasses import dataclass, field
+from html.parser import HTMLParser
+from pathlib import Path
+from urllib.parse import urljoin, urlsplit
+from xml.etree import ElementTree
+
+VOID_TAGS = {
+ "area",
+ "base",
+ "br",
+ "col",
+ "embed",
+ "hr",
+ "img",
+ "input",
+ "link",
+ "meta",
+ "param",
+ "source",
+ "track",
+ "wbr",
+}
+SKIP_TAGS = {"script", "style", "nav", "footer", "button", "svg", "form"}
+
+
+@dataclass
+class Element:
+ """A small HTML tree retaining semantic elements and text."""
+
+ tag: str
+ attrs: dict[str, str | None] = field(default_factory=dict)
+ children: list[Element | str] = field(default_factory=list)
+
+ def find_all(self, tag: str) -> list[Element]:
+ """Return descendants matching a tag in document order."""
+ return ([self] if self.tag == tag else []) + [
+ node
+ for child in self.children
+ if isinstance(child, Element)
+ for node in child.find_all(tag)
+ ]
+
+ def text(self) -> str:
+ """Return plain text without losing whitespace inside code."""
+ return "".join(
+ child if isinstance(child, str) else child.text() for child in self.children
+ )
+
+
+class Document(HTMLParser):
+ """Parse prerendered HTML without executing scripts or loading resources."""
+
+ def __init__(self, html: str):
+ super().__init__(convert_charrefs=True)
+ self.root = Element("document")
+ self.stack = [self.root]
+ self.feed(html)
+
+ def handle_starttag(self, tag, attrs):
+ """Append an element and track non-void parents."""
+ element = Element(tag, dict(attrs))
+ self.stack[-1].children.append(element)
+ if tag not in VOID_TAGS:
+ self.stack.append(element)
+
+ def handle_endtag(self, tag):
+ """Close the nearest matching parent."""
+ for index in range(len(self.stack) - 1, 0, -1):
+ if self.stack[index].tag == tag:
+ del self.stack[index:]
+ break
+
+ def handle_data(self, data):
+ """Preserve text for conversion."""
+ self.stack[-1].children.append(data)
+
+
+def to_markdown(node: Element | str, page_url: str) -> str:
+ """Convert semantic HTML to Markdown, retaining code, tables and destinations."""
+ if isinstance(node, str):
+ return re.sub(r"\s+", " ", node)
+ if (
+ node.tag in SKIP_TAGS
+ or node.attrs.get("aria-hidden") == "true"
+ or "hidden" in node.attrs
+ or node.attrs.get("data-slot") == "avatar"
+ ):
+ return ""
+ if "sr-only" in (node.attrs.get("class") or "").split():
+ return ""
+ if node.tag == "pre":
+ code = node.text().strip("\n")
+ fence = "`" * max(
+ 3, max((len(run) + 1 for run in re.findall(r"`+", code)), default=0)
+ )
+ return f"\n\n{fence}\n{code}\n{fence}\n\n"
+ content = "".join(to_markdown(child, page_url) for child in node.children)
+ if node.tag in {"h1", "h2", "h3", "h4", "h5", "h6"}:
+ return f"\n\n{'#' * int(node.tag[1])} {content.strip()}\n\n"
+ if node.tag == "a" and (href := node.attrs.get("href")):
+ destination = urljoin(page_url, href)
+ headings = [
+ heading for level in range(1, 7) for heading in node.find_all(f"h{level}")
+ ]
+ if headings:
+ # Keep heading permalink wrappers and linked workflow cards readable.
+ if destination.split("#")[0] == page_url and "#" in destination:
+ return content
+ label = node.attrs.get("aria-label") or headings[0].text().strip()
+ return f"{content}\n\n[{label}]({destination})\n\n"
+ label = " ".join(content.split()) or node.attrs.get("aria-label")
+ link = f"[{label}]({destination})" if label else ""
+ return f"\n\n{link}\n\n" if "\n" in content else link + " "
+ if node.tag == "img" and (src := node.attrs.get("src")):
+ alt = node.attrs.get("alt") or ""
+ return (
+ f"})"
+ if alt and not alt.startswith("Image preview of")
+ else ""
+ )
+ if node.tag == "code":
+ delimiter = "`" * max(
+ 1, max((len(run) + 1 for run in re.findall(r"`+", content)), default=0)
+ )
+ return f"{delimiter}{content}{delimiter}"
+ if node.tag == "li":
+ return f"\n- {content.strip()}\n"
+ if node.tag == "br":
+ return "\n"
+ if node.tag == "table":
+ rows = [
+ [
+ " ".join(to_markdown(cell, page_url).split()).replace("|", r"\|")
+ for cell in row.children
+ if isinstance(cell, Element) and cell.tag in {"th", "td"}
+ ]
+ for row in node.find_all("tr")
+ ]
+ rows = [row for row in rows if row]
+ if not rows:
+ return ""
+ width = max(map(len, rows))
+ rows = [row + [""] * (width - len(row)) for row in rows]
+ lines = ["| " + " | ".join(row) + " |" for row in rows]
+ lines.insert(1, "| " + " | ".join(["---"] * width) + " |")
+ return "\n\n" + "\n".join(lines) + "\n\n"
+ if node.tag in {"p", "div", "section", "article", "main", "ul", "ol", "blockquote"}:
+ return f"\n\n{content.strip()}\n\n" if content.strip() else ""
+ return content
+
+
+def rendered_content(html: str, page_url: str) -> tuple[str, str]:
+ """Extract the page answer while excluding shared navigation and controls."""
+ document = Document(html).root
+ roots = document.find_all("article") or document.find_all("main")
+ if not roots or not (headings := roots[0].find_all("h1")):
+ raise ValueError(f"Missing main content or heading: {page_url}")
+ content = to_markdown(roots[0], page_url)
+ return headings[0].text().strip(), re.sub(r"\n{3,}", "\n\n", content).strip() + "\n"
+
+
+def export_rendered_pages(static_dir: Path, frontend_path: str) -> None:
+ """Complete canonical-page exports and discovery files after prerendering."""
+ from ._plugin import (
+ MarkdownIndexEntry,
+ _extract_markdown_title,
+ _markdown_directive,
+ _section_for_path,
+ generate_llms_full_txt,
+ generate_llms_txt,
+ markdown_path_for_trailing_slash_url,
+ )
+
+ base = static_dir / frontend_path.strip("/")
+ sitemap = static_dir / "sitemap.xml"
+ if not sitemap.exists():
+ sitemap = base / "sitemap.xml"
+ entries = []
+ for loc in ElementTree.parse(sitemap).iter():
+ if not loc.tag.endswith("loc") or not loc.text:
+ continue
+ url = loc.text
+ relative = urlsplit(url).path.removeprefix(frontend_path).strip("/")
+ page = base / relative / "index.html"
+ html = page.read_text()
+ path = Path(relative + ".md") if relative else Path("index.md")
+ target = base / path
+ existing = target.read_text() if target.exists() else ""
+ # Eval-only source files expose a rendering function, not their answer.
+ prose = re.sub(
+ r"^```python (?:exec|eval)[^\n]*\n.*?^```\s*$",
+ "",
+ existing,
+ flags=re.MULTILINE | re.DOTALL,
+ )
+ prose = re.sub(r"^> For AI agents:.*$", "", prose, flags=re.MULTILINE).strip()
+ title = None
+ if not prose:
+ title, content = rendered_content(html, url)
+ existing = f"{_markdown_directive()}\n\n{content}"
+ target.parent.mkdir(parents=True, exist_ok=True)
+ target.write_text(existing)
+ if title is None:
+ title = _extract_markdown_title(existing) or relative.rsplit("/", 1)[-1]
+ twin = base / markdown_path_for_trailing_slash_url(path)
+ twin.parent.mkdir(parents=True, exist_ok=True)
+ twin.write_text(existing)
+ entry = MarkdownIndexEntry(path, title, _section_for_path(path))
+ entries.append((entry, existing))
+ for name, content in (
+ generate_llms_txt([entry for entry, _ in entries]),
+ generate_llms_full_txt((), entries),
+ ):
+ (base / name).write_text(content)
diff --git a/docs/app/assets/agent-logos/LICENSE.txt b/docs/app/assets/agent-logos/LICENSE.txt
new file mode 100644
index 00000000000..6368063c787
--- /dev/null
+++ b/docs/app/assets/agent-logos/LICENSE.txt
@@ -0,0 +1,24 @@
+Icons from https://github.com/lobehub/lobe-icons (claudecode-color, codex, cursor).
+Brand names and logos belong to their respective owners.
+
+MIT License
+
+Copyright (c) 2023 LobeHub
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/docs/app/assets/agent-logos/claude-code.svg b/docs/app/assets/agent-logos/claude-code.svg
new file mode 100644
index 00000000000..ed1293c473d
--- /dev/null
+++ b/docs/app/assets/agent-logos/claude-code.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/app/assets/agent-logos/codex.svg b/docs/app/assets/agent-logos/codex.svg
new file mode 100644
index 00000000000..4c648aff4a9
--- /dev/null
+++ b/docs/app/assets/agent-logos/codex.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/app/assets/agent-logos/cursor.svg b/docs/app/assets/agent-logos/cursor.svg
new file mode 100644
index 00000000000..a5b2ee3b1ed
--- /dev/null
+++ b/docs/app/assets/agent-logos/cursor.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/app/assets/components_previews/charts/dark/treemap.svg b/docs/app/assets/components_previews/charts/dark/treemap.svg
new file mode 100644
index 00000000000..5e9abb9e0cd
--- /dev/null
+++ b/docs/app/assets/components_previews/charts/dark/treemap.svg
@@ -0,0 +1,8 @@
+
diff --git a/docs/app/assets/components_previews/charts/light/treemap.svg b/docs/app/assets/components_previews/charts/light/treemap.svg
new file mode 100644
index 00000000000..118cfc3f61a
--- /dev/null
+++ b/docs/app/assets/components_previews/charts/light/treemap.svg
@@ -0,0 +1,8 @@
+
diff --git a/docs/app/assets/favicon.ico b/docs/app/assets/favicon.ico
index cf95e07d81b..2545bd24ff0 100644
Binary files a/docs/app/assets/favicon.ico and b/docs/app/assets/favicon.ico differ
diff --git a/docs/app/assets/favicon.svg b/docs/app/assets/favicon.svg
new file mode 100644
index 00000000000..35f49a90141
--- /dev/null
+++ b/docs/app/assets/favicon.svg
@@ -0,0 +1,77 @@
+
diff --git a/docs/app/assets/framework-diagrams/blue.avif b/docs/app/assets/framework-diagrams/blue.avif
new file mode 100644
index 00000000000..3ad6f1114dd
Binary files /dev/null and b/docs/app/assets/framework-diagrams/blue.avif differ
diff --git a/docs/app/assets/framework-diagrams/lavender.avif b/docs/app/assets/framework-diagrams/lavender.avif
new file mode 100644
index 00000000000..061f99ab18d
Binary files /dev/null and b/docs/app/assets/framework-diagrams/lavender.avif differ
diff --git a/docs/app/assets/framework-diagrams/mint.avif b/docs/app/assets/framework-diagrams/mint.avif
new file mode 100644
index 00000000000..087200e20b4
Binary files /dev/null and b/docs/app/assets/framework-diagrams/mint.avif differ
diff --git a/docs/app/assets/framework-diagrams/peach.avif b/docs/app/assets/framework-diagrams/peach.avif
new file mode 100644
index 00000000000..b59ee787826
Binary files /dev/null and b/docs/app/assets/framework-diagrams/peach.avif differ
diff --git a/docs/app/assets/meta/android-chrome-192x192.png b/docs/app/assets/meta/android-chrome-192x192.png
index 6dd5041bb20..e6d3c8cf3e8 100644
Binary files a/docs/app/assets/meta/android-chrome-192x192.png and b/docs/app/assets/meta/android-chrome-192x192.png differ
diff --git a/docs/app/assets/meta/android-chrome-512x512.png b/docs/app/assets/meta/android-chrome-512x512.png
index 235224e04f7..81b98da53f2 100644
Binary files a/docs/app/assets/meta/android-chrome-512x512.png and b/docs/app/assets/meta/android-chrome-512x512.png differ
diff --git a/docs/app/assets/meta/apple-touch-icon.png b/docs/app/assets/meta/apple-touch-icon.png
index ab32c4f0926..2db18bd8fc4 100644
Binary files a/docs/app/assets/meta/apple-touch-icon.png and b/docs/app/assets/meta/apple-touch-icon.png differ
diff --git a/docs/app/assets/meta/favicon-16x16.png b/docs/app/assets/meta/favicon-16x16.png
index 62f8a9eddf7..078d6065879 100644
Binary files a/docs/app/assets/meta/favicon-16x16.png and b/docs/app/assets/meta/favicon-16x16.png differ
diff --git a/docs/app/assets/meta/favicon-32x32.png b/docs/app/assets/meta/favicon-32x32.png
index 40759914a1b..9ae2cb0fcb5 100644
Binary files a/docs/app/assets/meta/favicon-32x32.png and b/docs/app/assets/meta/favicon-32x32.png differ
diff --git a/docs/app/assets/meta/site.webmanifest b/docs/app/assets/meta/site.webmanifest
index 464adc4c06c..771e4451309 100644
--- a/docs/app/assets/meta/site.webmanifest
+++ b/docs/app/assets/meta/site.webmanifest
@@ -1,6 +1,6 @@
{
- "name": "",
- "short_name": "",
+ "name": "Reflex",
+ "short_name": "Reflex",
"icons": [
{
"src": "/meta/android-chrome-192x192.png",
diff --git a/docs/app/news/+docs-discovery.bugfix.md b/docs/app/news/+docs-discovery.bugfix.md
new file mode 100644
index 00000000000..efc21bb5661
--- /dev/null
+++ b/docs/app/news/+docs-discovery.bugfix.md
@@ -0,0 +1 @@
+Fix the agent-guide index link, export Cloud CLI command references as Markdown, replace the Cloud overview placeholder, and reserve preview-image space to prevent catalog layout shifts. Component links now use their canonical trailing-slash paths.
diff --git a/docs/app/news/+editorial-docs.docs.md b/docs/app/news/+editorial-docs.docs.md
new file mode 100644
index 00000000000..2f919b260d3
--- /dev/null
+++ b/docs/app/news/+editorial-docs.docs.md
@@ -0,0 +1,37 @@
+Refresh the documentation with Reflex's neutral editorial design across navigation, articles, catalogs, API tables, illustrations, dialogs, footers, and missing pages in light and dark modes. Mobile navigation now matches the desktop docs destinations, while live examples retain their demonstrated styling.
+
+Fix browser hydration errors in integration cards and the low-level form example.
+
+Correct the header selection on the docs landing page so Overview is highlighted instead of Framework.
+
+Restyle the AI Builder cards with matching tinted illustrations, integration logo tiles, and consistent typography in light and dark modes.
+
+Make the component library easier to scan with baseline-aligned, text-only category headings on a uniform background, fine dividers, and component links arranged in columns with monochrome underlined hover states.
+
+Match both docs footer social menus to the main site's compact icon styling and remove the forum icon.
+
+Align documentation fields, navigation, content surfaces, and icon controls with the shared editorial shape system.
+
+Use a muted gray surface for the Custom Components sidebar card in light and dark modes.
+
+Remove the empty space beneath the MCP illustration on the docs landing page.
+
+Refine the docs navbar with understated text navigation, compact search, and consistent editorial controls.
+
+Explore framework capabilities through accessible tabs and theme-aware product diagrams, with the section heading directly above the tab list.
+
+Use fine solid connectors with rounded corners and clean junctions in framework diagrams.
+
+Explain component composition with a Python example mapped to its rendered interface.
+
+Make the How It Works diagram readable without overlapping windows and let visitors try its Python-backed counter.
+
+Explore Cloud hosting through a connected diagram centered on one unified hosted application with direct links to deployment, secrets, observability, and configuration guides.
+
+Integrate the footer newsletter signup with aligned columns, clearer copy, and a quieter inline email form.
+
+Choose between AI Builder and Python framework guides directly from the documentation introduction.
+
+Illustrate the AI Builder best practices with a focused prompt, an app preview, and a follow-up refinement.
+
+Choose Reflex Build or Agent Toolkit from the new Build with AI overview, within the shared documentation sidebar and article layout.
diff --git a/docs/app/news/+public-docs-urls.bugfix.md b/docs/app/news/+public-docs-urls.bugfix.md
new file mode 100644
index 00000000000..6a65d27a19a
--- /dev/null
+++ b/docs/app/news/+public-docs-urls.bugfix.md
@@ -0,0 +1 @@
+Default documentation canonicals, sharing metadata, and sitemap URLs to the public Reflex domain instead of localhost. Validate published URLs against the generated pages to prevent duplicate docs prefixes and mismatched canonicals.
diff --git a/docs/app/news/+specific-page-metadata.bugfix.md b/docs/app/news/+specific-page-metadata.bugfix.md
new file mode 100644
index 00000000000..8c4d01632e3
--- /dev/null
+++ b/docs/app/news/+specific-page-metadata.bugfix.md
@@ -0,0 +1 @@
+Replace generic docs search descriptions with page-specific summaries and bundle the missing light and dark Treemap preview images.
diff --git a/docs/app/news/7137.docs.md b/docs/app/news/7137.docs.md
new file mode 100644
index 00000000000..683622b35d5
--- /dev/null
+++ b/docs/app/news/7137.docs.md
@@ -0,0 +1 @@
+Complete Markdown exports and agent indexes for all canonical docs pages, add the React wrapping walkthrough and state guides, and clarify Figma integration availability. Reduce repeated HTML reference content and avoid redirects in documentation links.
diff --git a/docs/app/reflex_docs/components/ai_guide_graphic.py b/docs/app/reflex_docs/components/ai_guide_graphic.py
new file mode 100644
index 00000000000..aeba1a39d78
--- /dev/null
+++ b/docs/app/reflex_docs/components/ai_guide_graphic.py
@@ -0,0 +1,116 @@
+"""An illustrated prompt and review cycle for the AI Builder guide."""
+
+import reflex as rx
+
+
+def ai_guide_graphic() -> rx.Component:
+ """Show a focused prompt, its first result, and a specific follow-up."""
+ return rx.el.figure(
+ rx.el.div(
+ rx.el.div(
+ rx.el.div(
+ rx.el.div(
+ rx.icon("sparkles", size=16),
+ rx.el.span("Your prompt"),
+ class_name="flex items-center gap-2 text-xs text-muted-foreground",
+ ),
+ rx.el.div(
+ "Create an employee directory with name, team, and status. Start with sample data.",
+ class_name="mt-5 text-base font-book leading-7 tracking-tight text-foreground",
+ ),
+ class_name="min-w-0 self-center rounded-xl border border-border bg-background p-5 sm:p-6",
+ ),
+ rx.el.div(
+ rx.el.div(
+ rx.el.div(
+ *[
+ rx.el.span(
+ class_name="size-1 rounded-full bg-subtle-foreground"
+ )
+ for _ in range(3)
+ ],
+ class_name="flex gap-1",
+ ),
+ rx.el.span(
+ "Preview", class_name="text-xs text-muted-foreground"
+ ),
+ rx.icon(
+ "panel-top", size=13, class_name="text-subtle-foreground"
+ ),
+ class_name="flex items-center justify-between border-b border-border px-4 py-3",
+ ),
+ rx.el.div(
+ rx.el.div(
+ "Our team",
+ class_name="text-lg font-book tracking-tight text-foreground",
+ ),
+ rx.el.div(
+ "People across your organization",
+ class_name="mt-1 text-xs leading-5 text-muted-foreground",
+ ),
+ rx.el.div(
+ *[
+ rx.el.div(
+ rx.el.span(
+ initials,
+ class_name="flex size-8 shrink-0 items-center justify-center rounded-full bg-[var(--fw-tint)] text-[10px] font-book text-[var(--fw-accent)]",
+ ),
+ rx.el.div(
+ rx.el.div(
+ name,
+ class_name="text-xs font-book text-foreground",
+ ),
+ rx.el.div(
+ team,
+ class_name="mt-1 text-[11px] text-muted-foreground",
+ ),
+ class_name="min-w-0 flex-1",
+ ),
+ rx.el.span(
+ "Active",
+ class_name="text-[10px] text-[var(--fw-accent)]",
+ ),
+ class_name="flex items-center gap-3 border-t border-border py-3",
+ )
+ for initials, name, team in (
+ ("AM", "Alex Morgan", "Engineering"),
+ ("JC", "Jamie Chen", "Design"),
+ ("SR", "Sam Rivera", "Operations"),
+ )
+ ],
+ class_name="mt-5",
+ ),
+ class_name="p-4 sm:p-5",
+ ),
+ class_name="min-w-0 overflow-hidden rounded-xl border border-border bg-background",
+ ),
+ style={
+ "grid_template_columns": "repeat(auto-fit, minmax(min(100%, 220px), 1fr))"
+ },
+ class_name="grid w-full items-center gap-5 sm:gap-6",
+ ),
+ rx.el.div(
+ rx.icon(
+ "corner-up-right",
+ size=16,
+ class_name="shrink-0 text-[var(--fw-accent)]",
+ ),
+ rx.el.div(
+ rx.el.span(
+ "Next prompt", class_name="block text-xs text-muted-foreground"
+ ),
+ rx.el.span(
+ "Keep the layout. Add a filter for each team.",
+ class_name="mt-1 block text-sm leading-6 text-foreground",
+ ),
+ ),
+ class_name="mt-5 flex w-full items-center gap-3 rounded-xl border border-border bg-background px-5 py-4 sm:mt-6",
+ ),
+ aria_hidden=True,
+ class_name="docs-framework-stage flex-col !p-4 sm:!p-7",
+ ),
+ role="img",
+ aria_label="Build in focused steps: ask for an employee directory with sample data, review the first version, then ask to add a team filter while keeping the layout.",
+ custom_attrs={"data-tone": "lavender"},
+ class_name="docs-ai-guide-graphic docs-framework-panel my-8 min-w-0 overflow-hidden rounded-panel border border-border",
+ )
diff --git a/docs/app/reflex_docs/components/component_catalog.py b/docs/app/reflex_docs/components/component_catalog.py
new file mode 100644
index 00000000000..f6f9768f307
--- /dev/null
+++ b/docs/app/reflex_docs/components/component_catalog.py
@@ -0,0 +1,54 @@
+"""Shared category rows for the component catalogs."""
+
+import reflex as rx
+
+
+def component_category(
+ title: str,
+ href: str,
+ description: str,
+ links: list[tuple[str, str]],
+) -> rx.Component:
+ """Render an aligned category heading and a responsive list of guide links.
+
+ Args:
+ title: Category name.
+ href: Category overview route.
+ description: Component count or short category description.
+ links: Display names and documentation routes.
+
+ Returns:
+ A component catalog section.
+ """
+ return rx.el.section(
+ rx.link(
+ rx.box(
+ rx.el.h2(
+ title,
+ class_name="text-lg font-book leading-6 tracking-tight text-foreground transition-colors group-hover:text-muted-foreground",
+ ),
+ rx.text(
+ description,
+ class_name="text-xs font-normal leading-5 text-muted-foreground",
+ ),
+ class_name="flex min-w-0 flex-col gap-2",
+ ),
+ href=href,
+ underline="none",
+ class_name="group block rounded-compact text-foreground focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
+ ),
+ rx.el.ul(
+ *[
+ rx.el.li(
+ rx.el.a(
+ label,
+ href=link,
+ class_name="block rounded-compact px-3 py-2 text-sm font-normal leading-6 text-muted-foreground decoration-border-strong underline-offset-4 hover:text-foreground hover:underline transition-colors focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring",
+ ),
+ )
+ for label, link in links
+ ],
+ class_name="grid min-w-0 grid-cols-2 gap-x-2 gap-y-1 xl:grid-cols-3 list-none m-0 p-0",
+ ),
+ class_name="docs-library-category grid grid-cols-1 items-start gap-5 border-t border-border-subtle -mx-4 px-4 py-7 md:items-baseline md:grid-cols-[minmax(0,1fr)_minmax(0,3fr)] md:gap-8",
+ )
diff --git a/docs/app/reflex_docs/pages/__init__.py b/docs/app/reflex_docs/pages/__init__.py
index 4f3ffac2dae..4418101e605 100644
--- a/docs/app/reflex_docs/pages/__init__.py
+++ b/docs/app/reflex_docs/pages/__init__.py
@@ -1,8 +1,9 @@
-from reflex_site_shared.pages.page404 import page404 # noqa: F401
from reflex_site_shared.route import Route
+from reflex_docs.pages.ai_landing import ai_landing # noqa: F401
from reflex_docs.pages.docs import doc_routes
from reflex_docs.pages.docs_landing import docs_landing # noqa: F401
+from reflex_docs.pages.page404 import page404 # noqa: F401
routes = [
*[r for r in locals().values() if isinstance(r, Route) and r.add_as_page],
diff --git a/docs/app/reflex_docs/pages/ai_landing.py b/docs/app/reflex_docs/pages/ai_landing.py
new file mode 100644
index 00000000000..5dd07734b81
--- /dev/null
+++ b/docs/app/reflex_docs/pages/ai_landing.py
@@ -0,0 +1,161 @@
+"""Choose a workflow for building Reflex apps with AI."""
+
+import reflex as rx
+
+from reflex_docs.pages.docs import ai_builder
+from reflex_docs.pages.docs_landing.views.artwork import artwork
+from reflex_docs.templates.docpage import docpage, h1_comp, text_comp_2
+
+
+def _agent_logos() -> rx.Component:
+ """Group the coding-agent marks at a consistent size."""
+ return rx.el.div(
+ *[
+ rx.image(
+ src=rx.asset(f"agent-logos/{filename}.svg"),
+ alt=label,
+ title=label,
+ class_name="size-5 shrink-0 object-contain"
+ + (" dark:invert" if filename != "claude-code" else ""),
+ )
+ for filename, label in (
+ ("claude-code", "Claude Code"),
+ ("codex", "Codex"),
+ ("cursor", "Cursor"),
+ )
+ ],
+ class_name="docs-agent-logos flex items-center gap-4",
+ )
+
+
+def _workflow_preview(icon: rx.Component, labels: tuple[str, ...]) -> rx.Component:
+ """Illustrate the tools available in each workflow."""
+ return rx.el.div(
+ rx.el.div(
+ rx.el.div(
+ icon,
+ rx.el.div(
+ *[
+ rx.el.span(
+ class_name="size-1 rounded-full bg-subtle-foreground"
+ )
+ for _ in range(3)
+ ],
+ class_name="flex gap-1",
+ ),
+ class_name="flex items-center justify-between border-b border-border px-5 py-4",
+ ),
+ rx.el.div(
+ *[
+ rx.el.div(
+ rx.icon(symbol, size=16, class_name="text-[var(--fw-accent)]"),
+ rx.el.span(label, class_name="text-sm text-foreground"),
+ class_name="flex items-center gap-3 rounded-sm bg-[var(--fw-tint)] px-4 py-3",
+ )
+ for symbol, label in zip(
+ ("message-square", "panels-top-left", "layers"),
+ labels,
+ strict=True,
+ )
+ ],
+ class_name="flex flex-col gap-2 p-4",
+ ),
+ class_name="w-full max-w-xs overflow-hidden rounded-xl border border-border bg-background",
+ ),
+ aria_hidden=True,
+ class_name="docs-framework-stage !p-7 sm:!p-9",
+ )
+
+
+def _workflow_card(
+ title: str,
+ description: str,
+ action: str,
+ href: str,
+ tone: str,
+ preview: rx.Component,
+ recommended: bool = False,
+) -> rx.Component:
+ """Present one native link with a visual summary and clear destination."""
+ return rx.el.a(
+ preview,
+ rx.el.div(
+ rx.el.div(
+ rx.el.h2(
+ title,
+ class_name="text-2xl font-book tracking-tight text-foreground",
+ ),
+ rx.el.span(
+ "Recommended",
+ class_name="rounded-sm bg-foreground px-2.5 py-1 text-xs font-book leading-4 text-background",
+ )
+ if recommended
+ else rx.fragment(),
+ class_name="flex flex-wrap items-center gap-x-3 gap-y-2",
+ ),
+ rx.el.p(
+ description, class_name="mt-3 text-base leading-7 text-muted-foreground"
+ ),
+ rx.el.div(
+ action,
+ rx.icon("arrow-right", size=16, aria_hidden=True),
+ class_name="mt-auto flex items-center gap-2 pt-7 text-sm font-book text-foreground",
+ ),
+ class_name="flex flex-1 flex-col border-t border-border bg-background p-6 sm:p-8",
+ ),
+ href=href,
+ aria_label=title,
+ aria_description="Recommended way to build Reflex apps"
+ if recommended
+ else None,
+ custom_attrs={"data-tone": tone},
+ class_name="docs-ai-workflow-card docs-framework-panel flex min-w-0 flex-col overflow-hidden rounded-panel border border-border transition-colors hover:border-border-strong focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
+ )
+
+
+@docpage(
+ set_path="/ai/",
+ t="Build with AI",
+ right_sidebar=False,
+ description="Build in your browser with Reflex Build, or use Agent Toolkit with your own coding agent.",
+)
+def ai_landing() -> rx.Component:
+ """Offer both AI workflows within the shared documentation layout."""
+ return rx.el.section(
+ h1_comp(text="Build with AI"),
+ text_comp_2(
+ text="Use Reflex's integrated builder or work with your own coding agent.",
+ ),
+ rx.el.div(
+ _workflow_card(
+ "Use Reflex Build",
+ "The most powerful way to build Reflex apps. Bring AI generation, live previews, testing, integrations, and deployment together in one workspace.",
+ "Explore Reflex Build",
+ ai_builder.overview.what_is_reflex_build.path,
+ "lavender",
+ _workflow_preview(
+ artwork("reflex_mark", class_name="w-5"),
+ (
+ "Plan and build with AI",
+ "Preview and test your app",
+ "Connect data and deploy",
+ ),
+ ),
+ recommended=True,
+ ),
+ _workflow_card(
+ "Bring your own agent",
+ "Give your preferred coding agent Reflex documentation, skills, and tools with Agent Toolkit.",
+ "Explore Agent Toolkit",
+ ai_builder.integrations.agent_toolkit.path,
+ "mint",
+ _workflow_preview(
+ _agent_logos(),
+ ("Documentation for agents", "Reflex skills", "MCP tools"),
+ ),
+ ),
+ class_name="mt-10 grid grid-cols-1 gap-6 md:grid-cols-2",
+ ),
+ aria_label="Choose your AI workflow",
+ class_name="docs-ai-overview min-w-0 pb-8",
+ )
diff --git a/docs/app/reflex_docs/pages/docs/cloud.py b/docs/app/reflex_docs/pages/docs/cloud.py
index a3d5db0dbfc..bdc9ba0a37e 100644
--- a/docs/app/reflex_docs/pages/docs/cloud.py
+++ b/docs/app/reflex_docs/pages/docs/cloud.py
@@ -1,17 +1,51 @@
-import reflex as rx
-
-from reflex_docs.templates.docpage import docpage, h1_comp
-
-cloud_overview = docpage("overview/", "Cloud Overview")(
- lambda: rx.box(
- h1_comp(text="Reflex Cloud Overview"),
- rx.text("Cloud overview content from markdown"),
- )
-)
-# Keep the short sidebar/nav label ("Overview"), but emit a descriptive HTML
-# for SEO.
-cloud_overview.title = "Overview"
-cloud_overview.seo_title = "Reflex Cloud Overview ยท Reflex Docs"
+"""Overview of deploying and operating Reflex applications."""
+
+from reflex_docs.docgen_pipeline import render_markdown
+from reflex_docs.templates.docpage import docpage
+
+CLOUD_OVERVIEW_MARKDOWN = """# Reflex Cloud Overview
+
+Reflex Cloud hosts your Reflex application so other people can use it on the web.
+Start with an app that runs locally, deploy it to a project, and use the hosting
+tools to configure and monitor it.
+
+## Deploy your first app
+
+Follow the [deployment quick start](/docs/hosting/deploy-quick-start/) to sign in,
+find your project's deploy command, and publish your app. The guide covers the
+required Python dependencies and the interactive deployment workflow.
+
+For command options and automation, use the [deploy command reference](/docs/hosting/cli/deploy/)
+and the [GitHub Actions deployment guide](/docs/hosting/deploy-with-github-actions/).
+## Configure your deployment
+- [Secrets and environment variables](/docs/hosting/secrets-environment-vars/): configure API keys and other values used by your app.
+- [Custom domains](/docs/hosting/custom-domains/): connect a domain to your deployment.
+- [Regions](/docs/hosting/regions/): choose where to run your application.
+- [Compute](/docs/hosting/compute/): choose resources for your workload.
+- [Cloud configuration](/docs/hosting/config-file/): manage deployment settings in a configuration file.
+
+## Monitor and manage your app
+
+Use [application management](/docs/hosting/app-management/) to manage deployments
+and [logs](/docs/hosting/logs/) to investigate application behavior. Run a
+[security scan](/docs/hosting/security-scan/) to review your app source for
+security and logic issues.
+
+## Choose where to host
+
+If you need to run in your own environment, compare
+[bring your own cloud](/docs/hosting/bring-your-own-cloud/) with
+[self-hosting](/docs/hosting/self-hosting/). These guides explain the available
+workflows and the setup each requires.
+"""
+
+cloud_overview = docpage(
+ "/overview/",
+ "Cloud Overview",
+ description="Deploy a Reflex app, configure secrets and domains, monitor logs, and compare Reflex Cloud with bring-your-own-cloud and self-hosting options.",
+)(lambda: render_markdown(CLOUD_OVERVIEW_MARKDOWN))
+cloud_overview.title = "Overview"
+cloud_overview.seo_title = "Reflex Cloud Overview ยท Reflex Docs"
pages = [cloud_overview]
diff --git a/docs/app/reflex_docs/pages/docs/component.py b/docs/app/reflex_docs/pages/docs/component.py
index 9316f47c354..a6364160e00 100644
--- a/docs/app/reflex_docs/pages/docs/component.py
+++ b/docs/app/reflex_docs/pages/docs/component.py
@@ -14,6 +14,7 @@
from reflex.components.component import Component
from reflex.components.radix.primitives.base import RadixPrimitiveComponent
from reflex.components.radix.themes.base import RadixThemesComponent
+from reflex_components_core.el.elements.base import BaseHTML
from reflex_docgen import (
EventHandlerDocumentation,
PropDocumentation,
@@ -90,16 +91,16 @@ class PropDocsState(rx.State):
_PILL_BTN_CLASS = (
- "inline-flex h-7 cursor-pointer items-center justify-center rounded-md "
- "border border-secondary-5 bg-secondary-1 px-2.5 text-sm font-medium text-secondary-11 "
- "transition-colors hover:border-secondary-6 hover:bg-secondary-2 hover:text-secondary-12 "
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-secondary-7"
+ "inline-flex h-7 cursor-pointer items-center justify-center rounded-full "
+ "border border-border bg-background px-3 text-sm font-book text-muted-foreground "
+ "transition-colors hover:border-border-strong hover:bg-muted hover:text-foreground "
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
)
_PILL_BTN_ACTIVE_CLASS = (
- "inline-flex h-7 cursor-pointer items-center justify-center rounded-md "
- "border border-secondary-8 bg-secondary-3 px-2.5 text-sm font-medium text-secondary-12 "
- "shadow-[inset_0_0_0_1px_var(--secondary-6)] transition-colors "
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-secondary-7"
+ "inline-flex h-7 cursor-pointer items-center justify-center rounded-full "
+ "border border-foreground bg-foreground px-3 text-sm font-book text-background "
+ "transition-colors hover:bg-primary-hover "
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
)
_PROPS_TABLE_COMPACT_CELL_CLASS = (
"cell-content max-h-[4.25rem] overflow-hidden "
@@ -715,6 +716,7 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component:
interactive_component = rx.el.div(
rx.el.div(
comp,
+ data_docs_example=True,
class_name=(
"flex flex-col items-center justify-center p-6 flex-1 "
"bg-secondary-2 border-b lg:border-b-0 lg:border-r "
@@ -726,7 +728,7 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component:
class_name="flex-1 p-4 bg-secondary-1 min-w-0 overflow-x-auto",
),
class_name=(
- "flex flex-col lg:flex-row w-full rounded-xl border "
+ "flex flex-col lg:flex-row w-full rounded-card border "
"border-secondary-4 overflow-hidden"
),
)
@@ -752,8 +754,8 @@ def _render_dynamic_prop(indent: str, p, var) -> rx.Component:
for prop, control in interactive_controls
],
class_name=(
- "mb-4 w-full min-w-0 overflow-hidden rounded-xl border "
- "border-secondary-4 bg-secondary-1 shadow-small"
+ "mb-4 w-full min-w-0 overflow-hidden rounded-card border "
+ "border-border-subtle bg-background"
),
)
@@ -863,8 +865,27 @@ def generate_valid_children(comp: type[Component]) -> rx.Component:
)
+def shared_html_props(
+ components: list[type[Component]],
+) -> tuple[PropDocumentation, ...]:
+ """Find inherited HTML props shared unchanged by at least two components."""
+ if len(components) < 2:
+ return ()
+ props_by_component = [
+ {prop.name: prop for prop in generate_documentation(component).props}
+ for component in components
+ ]
+ return tuple(
+ prop
+ for prop in generate_documentation(BaseHTML).props
+ if sum(props.get(prop.name) == prop for props in props_by_component) >= 2
+ )
+
+
def component_docs(
- component_tuple: tuple[type[Component], str], previews: dict[str, str]
+ component_tuple: tuple[type[Component], str],
+ previews: dict[str, str],
+ shared_props: tuple[PropDocumentation, ...] = (),
) -> rx.Component:
"""Generates documentation for a given component."""
component = component_tuple[0]
@@ -879,7 +900,13 @@ def component_docs(
component_tuple[1], component_tuple[1]
)
- props = generate_props(doc.props, component, previews, comp_display_name)
+ inherited_props = tuple(prop for prop in doc.props if prop in shared_props)
+ props = generate_props(
+ tuple(prop for prop in doc.props if prop not in inherited_props),
+ component,
+ previews,
+ comp_display_name,
+ )
triggers = generate_event_triggers(doc.event_handlers)
children = generate_valid_children(component)
@@ -887,6 +914,18 @@ def component_docs(
h2_comp(text=comp_display_name),
rx.box(render_markdown(doc.description or ""), class_name="pb-2"),
props,
+ rx.el.p(
+ "Also accepts the ",
+ rx.el.a(
+ "shared HTML props",
+ href="#shared-html-props",
+ class_name="docs-text-link underline underline-offset-4",
+ ),
+ ".",
+ class_name="mb-4 text-sm text-muted-foreground",
+ )
+ if inherited_props
+ else rx.fragment(),
children,
triggers,
class_name="pb-8 w-full text-left",
@@ -905,20 +944,37 @@ def multi_docs(
ll_component_list: list | None = None,
source: str | None = None,
):
+ shared = shared_html_props([item[0] for item in component_list[1:]])
components = [
- component_docs(component_tuple, previews)
+ component_docs(component_tuple, previews, shared)
for component_tuple in component_list[1:]
]
ll_actual_path = actual_path.replace(".md", "-ll.md")
ll_doc_exists = os.path.exists(ll_actual_path)
ll_list = ll_component_list if ll_component_list is not None else component_list
+ ll_shared = shared_html_props([item[0] for item in ll_list[1:]])
ll_components = [
- component_docs(component_tuple, previews) for component_tuple in ll_list[1:]
+ component_docs(component_tuple, previews, ll_shared)
+ for component_tuple in ll_list[1:]
]
- active_class_name = "font-small bg-secondary-2 p-2 text-secondary-11 rounded-xl shadow-large w-28 cursor-default border border-secondary-4 text-center"
+ def shared_reference(props):
+ """Render the common reference once, retaining it in prerendered HTML."""
+ if not props:
+ return rx.fragment()
+ return rx.box(
+ h2_comp(text="Shared HTML props"),
+ rx.el.p(
+ "Components linked to this section accept these HTML props. "
+ "Component-specific additions and overrides are listed below.",
+ class_name="mb-4 text-sm leading-6 text-muted-foreground",
+ ),
+ generate_props(props, BaseHTML, {}),
+ )
+
+ active_class_name = "text-sm font-book bg-foreground px-4 py-2 text-background rounded-full w-28 cursor-default text-center"
- non_active_class_name = "font-small w-28 transition-color hover:text-secondary-12 text-secondary-11 p-2 text-center"
+ non_active_class_name = "text-sm font-book w-28 rounded-full transition-colors hover:bg-muted hover:text-foreground text-muted-foreground px-4 py-2 text-center"
def links(current_page, ll_doc_exists, path):
path = str(path).rstrip("/")
@@ -935,10 +991,10 @@ def links(current_page, ll_doc_exists, path):
rx.box(
rx.text("Low Level"), class_name=non_active_class_name
),
- href=path + "/low",
+ href=path + "/low/",
underline="none",
),
- class_name="bg-secondary-3 rounded-[1.125rem] p-2 gap-2 flex items-center justify-center",
+ class_name="docs-api-level-switch bg-background border border-border rounded-full p-1 gap-1 flex items-center justify-center [&_a]:rounded-full [&_a]:focus-visible:outline-2 [&_a]:focus-visible:outline-ring",
),
class_name="flex mb-2",
)
@@ -950,15 +1006,15 @@ def links(current_page, ll_doc_exists, path):
rx.box(
rx.text("High Level"), class_name=non_active_class_name
),
- href=path,
+ href=path + "/",
underline="none",
),
rx.link(
rx.box(rx.text("Low Level"), class_name=active_class_name),
- href=path + "/low",
+ href=path + "/low/",
underline="none",
),
- class_name="bg-secondary-3 rounded-[1.125rem] p-2 gap-2 flex items-center justify-center",
+ class_name="docs-api-level-switch bg-background border border-border rounded-full p-1 gap-1 flex items-center justify-center [&_a]:rounded-full [&_a]:focus-visible:outline-2 [&_a]:focus-visible:outline-ring",
),
class_name="flex mb-2",
)
@@ -982,11 +1038,14 @@ def out():
# Append API Reference headings for the component list
if components:
toc.append((1, "API Reference"))
+ if shared:
+ toc.append((2, "Shared HTML props"))
for component_tuple in component_list[1:]:
toc.append((2, component_tuple[1]))
api_ref_section = (
[
h2_comp(text="API Reference"),
+ shared_reference(shared),
rx.box(*components, class_name="flex flex-col"),
]
if components
@@ -1014,7 +1073,7 @@ def out():
)
@docpage(
- set_path=path + "low",
+ set_path=path.rstrip("/") + "/low/",
t=title + " (Low Level)",
description=ll_description,
image=image,
@@ -1026,11 +1085,14 @@ def ll():
doc_content = Path(ll_actual_path).read_text(encoding="utf-8")
if ll_components:
toc.append((1, "API Reference"))
+ if ll_shared:
+ toc.append((2, "Shared HTML props"))
for component_tuple in ll_list[1:]:
toc.append((2, component_tuple[1]))
api_ref_section = (
[
h2_comp(text="API Reference"),
+ shared_reference(ll_shared),
rx.box(*ll_components, class_name="flex flex-col"),
]
if ll_components
diff --git a/docs/app/reflex_docs/pages/docs/custom_components.py b/docs/app/reflex_docs/pages/docs/custom_components.py
index 4e4ea054682..244f5397431 100644
--- a/docs/app/reflex_docs/pages/docs/custom_components.py
+++ b/docs/app/reflex_docs/pages/docs/custom_components.py
@@ -4,9 +4,7 @@
import httpx
import reflex as rx
from reflex_site_shared.components.icons import get_icon
-from reflex_site_shared.styles.colors import c_color
-from reflex_site_shared.styles.fonts import base
-from reflex_site_shared.styles.shadows import shadows
+from reflex_site_shared.components.marketing_button import button
from reflex_docs.templates.docpage import docpage, h1_comp, text_comp_2
@@ -123,152 +121,48 @@ def set_selected_filter(self, filter_text: str):
self.paginate() # Update paginated data
-def filter_item(
- icon: str, text: str, border: bool = False, on_click=None
-) -> rx.Component:
- is_selected = CustomComponentGalleryState.selected_filter == text
- return rx.box(
- get_icon(icon, class_name="py-[2px]", opacity=rx.cond(is_selected, 0.64, 1)),
- rx.text(text, opacity=rx.cond(is_selected, 0.64, 1), class_name="font-small"),
- rx.spacer(),
- rx.cond(
- is_selected,
- rx.box(
- class_name="size-2 justify-end bg-violet-9 rounded-full",
- ),
- ),
- class_name="flex flex-row gap-[14px] items-center justify-start w-full cursor-pointer hover:bg-secondary-3 transition-bg text-nowrap overflow-hidden p-[8px_14px]",
- border_top="1px solid var(--secondary-4)" if border else "none",
- border_bottom="1px solid var(--secondary-4)" if border else "none",
- on_click=on_click,
- )
-
-
-chips_box_style = {
- "width": ["100%", "100%", "auto"],
- "box-sizing": "border-box",
- "display": "flex",
- "flex-direction": "row",
- "align_items": "center",
- "padding": "6px 12px",
- "cursor": "pointer",
- "box-shadow": shadows["large"],
- "border-radius": "1000px",
- "transition": "background 0.075s ease-out, color 0.075s ease-out, border 0.075s ease-out",
-}
-
-# Sorting
-sorting_box_style = {
- "gap": "12px",
- "outline": "none",
- "_focus": {
- "outline": "none",
- },
- **chips_box_style,
-}
-
-menu_item_style = {
- "box-sizing": "border-box",
- "width": "191px",
- "height": "auto",
- "overflow": "hidden",
- "padding": "0px",
- "cursor": "default",
- "background_color": "var(--secondary-2)",
- "border": "1px solid var(--secondary-4)",
- "box-shadow": "0px 2px 4px rgba(0, 0, 0, 0.05)",
- "border-radius": "12px",
- "color": "var(--secondary-9)",
- **base,
-}
-
-
-def sorting_filters() -> rx.Component:
- return rx.vstack(
- filter_item(
- "history",
- "Recent",
- on_click=lambda: CustomComponentGalleryState.set_selected_filter("Recent"),
- ),
- filter_item(
- "arrow_down_big",
- "Downloads",
- border=True,
- on_click=lambda: CustomComponentGalleryState.set_selected_filter(
- "Downloads"
- ),
- ),
- gap="0px",
- width="100%",
- )
-
-
def sorting_filters_dropdown_menu() -> rx.Component:
+ """Render the gallery sort menu using the shared neutral controls.
+
+ Returns:
+ Keyboard-accessible sort menu.
+ """
condition = CustomComponentGalleryState.selected_filter != ""
- conditional_style = {
- "background": rx.cond(
- condition,
- c_color("violet", 9),
- c_color("slate", 1),
- ),
- "color": rx.cond(
- condition,
- "white",
- c_color("slate", 9),
- ),
- "border": rx.cond(
- condition,
- f"1px solid {c_color('violet', 9)}",
- f"1px solid {c_color('slate', 5)}",
- ),
- "&[data-state='open']": {
- "background": rx.cond(
- condition,
- c_color("violet", 9),
- c_color("slate", 3),
- ),
- },
- "_hover": {
- "background": rx.cond(
- condition,
- c_color("violet", 9),
- c_color("slate", 3),
- ),
- },
- }
return rx.menu.root(
rx.menu.trigger(
- rx.el.button(
- rx.text(
+ button(
+ rx.cond(
+ condition,
+ "Sort: " + CustomComponentGalleryState.selected_filter,
"Sort",
- rx.cond(
- condition,
- rx.text(
- f": {CustomComponentGalleryState.selected_filter}",
- as_="span",
- class_name="text-nowrap",
- ),
- ),
- as_="span",
- class_name="font-small",
- ),
- get_icon(
- icon="select",
),
- justify_content="space-between",
+ get_icon("select"),
+ variant="outline",
+ size="sm",
),
- style=sorting_box_style | conditional_style,
+ as_child=True,
),
rx.menu.content(
- rx.menu.item(sorting_filters(), style=menu_item_style),
- bg="transparent",
- box_shadow="None",
- padding="0px",
- overflow="visible",
- border="none",
- align="center",
+ *[
+ rx.menu.item(
+ get_icon(icon),
+ label,
+ rx.cond(
+ CustomComponentGalleryState.selected_filter == label,
+ rx.icon("check", size=14),
+ rx.fragment(),
+ ),
+ on_select=CustomComponentGalleryState.set_selected_filter(label),
+ class_name="flex items-center gap-2 rounded-compact px-3 py-2 text-sm font-book text-foreground data-[highlighted]:bg-muted data-[highlighted]:text-foreground",
+ )
+ for label, icon in (
+ ("Recent", "history"),
+ ("Downloads", "arrow_down_big"),
+ )
+ ],
+ align="end",
+ class_name="min-w-44 rounded-panel border border-border bg-background p-1 shadow-medium",
),
- width="100%",
)
@@ -282,7 +176,7 @@ def download(download_url: str) -> rx.Component:
underline="none",
href=download_url,
is_external=True,
- class_name="text-secondary-9 hover:!text-secondary-9 bg-secondary-1 hover:bg-secondary-3 transition-bg cursor-pointer rounded-[6px]",
+ class_name="text-secondary-9 hover:!text-secondary-9 bg-secondary-1 hover:bg-secondary-3 transition-bg cursor-pointer rounded-compact",
title="Documentation",
)
@@ -301,15 +195,17 @@ def table_rows(category: dict):
rx.table.cell(name),
rx.table.cell(updated_at),
rx.table.cell(
- rx.box(
+ rx.el.button(
rx.text(
"pip install " + category["package_name"],
as_="p",
- class_name="font-small truncate flex-1 min-w-0",
+ class_name="font-mono text-xs truncate flex-1 min-w-0",
),
get_icon(icon="copy", class_name="p-[5px]"),
on_click=rx.set_clipboard("pip install " + category["package_name"]),
- class_name="flex flex-row gap-1.5 text-secondary-9 w-full items-center overflow-hidden border border-secondary-5 bg-secondary-1 hover:bg-secondary-3 transition-bg cursor-pointer shadow-small rounded-[6px] px-1.5 max-w-[20rem]",
+ type="button",
+ aria_label="Copy install command for " + category["package_name"],
+ class_name="flex flex-row gap-1.5 text-muted-foreground w-full items-center overflow-hidden border border-border bg-background hover:bg-muted transition-colors cursor-pointer rounded-compact px-3 min-h-9 max-w-[20rem] focus-visible:outline-2 focus-visible:outline-ring",
)
),
rx.table.cell(download(category["download_url"])),
@@ -352,7 +248,9 @@ def component_grid():
def create_pagination():
return rx.hstack(
rx.hstack(
- rx.text("Rows per page", weight="bold", font_size="12px"),
+ rx.text(
+ "Rows per page", class_name="text-xs font-normal text-muted-foreground"
+ ),
rx.select(
CustomComponentGalleryState.limits,
default_value="50",
@@ -365,34 +263,27 @@ def create_pagination():
rx.text(
f"Page {CustomComponentGalleryState.current_page} of {CustomComponentGalleryState.total_pages}",
width="100px",
- weight="bold",
- font_size="12px",
+ class_name="text-xs font-normal text-muted-foreground",
),
- rx.button(
- rx.icon(
- tag="chevron-left",
- on_click=CustomComponentGalleryState.previous,
- size=25,
- cursor="pointer",
- ),
- color_scheme="gray",
- variant="surface",
- size="1",
- width="32px",
- height="32px",
+ button(
+ rx.icon("chevron-left", size=16),
+ on_click=CustomComponentGalleryState.previous,
+ disabled=CustomComponentGalleryState.offset == 0,
+ aria_label="Previous page",
+ variant="outline",
+ size="icon-sm",
),
- rx.button(
- rx.icon(
- tag="chevron-right",
- on_click=CustomComponentGalleryState.next,
- size=25,
- cursor="pointer",
+ button(
+ rx.icon("chevron-right", size=16),
+ on_click=CustomComponentGalleryState.next,
+ disabled=(
+ CustomComponentGalleryState.offset
+ + CustomComponentGalleryState.current_limit
+ >= CustomComponentGalleryState.number_of_rows
),
- color_scheme="gray",
- variant="surface",
- size="1",
- width="32px",
- height="32px",
+ aria_label="Next page",
+ variant="outline",
+ size="icon-sm",
),
align_items="center",
spacing="1",
@@ -413,9 +304,9 @@ def custom_components() -> rx.Component:
text="Reflex has a growing ecosystem of custom components that you can use to build your apps. Below is a list of some of the custom components available for Reflex.",
),
sorting_filters_dropdown_menu(),
- class_name="flex flex-row w-full gap-12 justify-between items-center",
+ class_name="flex flex-col w-full gap-4 justify-between items-start sm:flex-row sm:items-start [&>p]:mb-0",
),
- class_name="flex flex-col w-full",
+ class_name="flex flex-col w-full gap-6",
),
component_grid(),
create_pagination(),
diff --git a/docs/app/reflex_docs/pages/docs/library.py b/docs/app/reflex_docs/pages/docs/library.py
index d76c9b5873b..dd1a7dac423 100644
--- a/docs/app/reflex_docs/pages/docs/library.py
+++ b/docs/app/reflex_docs/pages/docs/library.py
@@ -1,7 +1,7 @@
import reflex as rx
from reflex.utils.format import to_snake_case, to_title_case
-from reflex_site_shared.components.icons import get_icon
+from reflex_docs.components.component_catalog import component_category
from reflex_docs.templates.docpage import docpage, h1_comp, h2_comp, text_comp_2
@@ -44,41 +44,23 @@ def generate_gallery(
components,
prefix: str = "",
):
- sidebar = [
- rx.box(
- rx.link(
- rx.el.h2(
- get_display_name(category),
- class_name="font-large text-secondary-12",
- ),
- get_icon("new_tab", class_name="text-secondary-11 [&>svg]:size-4"),
- href=f"/library/{prefix.strip('/') + '/' if prefix.strip('/') else ''}{category.lower()}",
- underline="none",
- class_name="px-4 py-2 bg-secondary-1 hover:bg-secondary-3 transition-bg flex flex-row justify-between items-center !text-secondary-12",
- ),
- rx.box(
- *[
- rx.link(
- get_display_name(c[0]),
- href=get_component_link(
- category=category,
- clist=c,
- prefix=prefix,
- ),
- class_name="font-small text-secondary-11 hover:!text-primary-9 transition-color w-fit",
- )
- 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-secondary-5",
- ),
- class_name="flex flex-col border border-secondary-5 rounded-xl bg-secondary-2 shadow-large overflow-hidden",
+ categories = [
+ component_category(
+ title=get_display_name(category),
+ href=f"/library/{prefix.strip('/') + '/' if prefix.strip('/') else ''}{category.lower()}/",
+ description=f"{len(components[category])} components",
+ links=[
+ (
+ get_display_name(c[0]),
+ get_component_link(category=category, clist=c, prefix=prefix),
+ )
+ for c in get_components_for_category(category, components[category])
+ ],
)
for category in components
]
- return sidebar
+ return categories
core = generate_gallery(
components=component_list,
@@ -91,7 +73,7 @@ def generate_gallery(
return rx.box(
rx.box(
*core,
- class_name="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6",
+ class_name="flex flex-col",
),
rx.box(
h2_comp(
@@ -102,7 +84,7 @@ def generate_gallery(
),
rx.box(
*graphs,
- class_name="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-6",
+ class_name="flex flex-col",
),
class_name="flex flex-col",
),
@@ -124,5 +106,15 @@ def library():
text="Components let you split the UI into independent, reusable pieces, and think about each piece in isolation. This page contains a list of all builtin components.",
),
component_grid(),
+ rx.el.p(
+ "Connect your components to data and events with the ",
+ rx.el.a(
+ "state guides",
+ href="/library/state/",
+ class_name="docs-text-link underline underline-offset-4",
+ ),
+ ".",
+ class_name="mt-8 text-sm leading-6 text-muted-foreground",
+ ),
class_name="flex flex-col h-full mb-12",
)
diff --git a/docs/app/reflex_docs/pages/docs/metadata.py b/docs/app/reflex_docs/pages/docs/metadata.py
index 401ab5b1dc3..b4b24310f46 100644
--- a/docs/app/reflex_docs/pages/docs/metadata.py
+++ b/docs/app/reflex_docs/pages/docs/metadata.py
@@ -1,5 +1,41 @@
"""SEO metadata helpers for docs routes."""
+# Summaries for generated catalogs, reference pages, and pages without usable prose.
+PAGE_DESCRIPTIONS = {
+ "library": "Browse Reflex UI components for forms, layouts, data display, charts, and media, with Python examples and prop references.",
+ "custom-components": "Explore community-built Reflex components and discover reusable Python interfaces for React libraries and custom UI elements.",
+ "recipes": "Find reusable Reflex recipes for page layouts, content, and authentication, with examples you can adapt for your Python app.",
+ "api-reference/app": "Configure rx.App, register pages, and customize application behavior with the App class attributes and methods reference.",
+ "api-reference/component": "Explore the Component API for creating Reflex UI elements, managing props, and customizing component rendering and behavior.",
+ "api-reference/componentstate": "Use ComponentState to pair reusable Reflex components with their own state. Explore its class attributes and methods.",
+ "api-reference/event": "Explore the Event API for Reflex event instances, including event names, payloads, and client tokens.",
+ "api-reference/eventspec": "Explore EventSpec, the representation of a Reflex event handler invocation, including its arguments and event actions.",
+ "api-reference/eventhandler": "Explore EventHandler attributes and methods for wrapping Python functions as Reflex event handlers.",
+ "api-reference/statemanager": "Explore StateManager methods for retrieving and managing Reflex state associated with client sessions.",
+ "api-reference/config": "Reference Reflex configuration fields, defaults, and environment variable overrides for running and deploying your app.",
+ "api-reference/importvar": "Explore ImportVar fields for describing JavaScript imports used by Reflex components, including aliases and default imports.",
+ "api-reference/state": "Explore the State class API for managing reactive app data, computed values, event handlers, and state updates in Reflex.",
+ "api-reference/var": "Explore the Var API for representing frontend expressions and working with typed reactive values in Reflex.",
+ "api-reference/environment-variables": "Look up Reflex environment variables, their types, and defaults to configure development, builds, and application runtime behavior.",
+ "hosting/cli/deploy": "Reference the Reflex Cloud deploy command and its options for publishing an application from your terminal.",
+ "hosting/cli/projects": "Reference Reflex Cloud CLI project commands and options for organizing hosted applications into projects.",
+ "hosting/cli/scan": "Run a Reflex-aware security review of your app source with the Cloud CLI scan command. Reference its options and output controls.",
+ "hosting/cli/apps": "Reference Reflex Cloud CLI app commands and options for managing your hosted applications from the terminal.",
+ "hosting/cli/config": "Reference Reflex Cloud CLI configuration commands and options for managing your local hosting configuration.",
+ "hosting/cli/secrets": "Reference Reflex Cloud CLI secret commands and options for managing sensitive values used by hosted applications.",
+ "hosting/cli/regions": "Reference Reflex Cloud CLI region commands to explore available deployment locations for your applications.",
+ "hosting/cli/providers": "Reference Reflex Cloud CLI provider commands and options for working with supported cloud providers.",
+ "hosting/cli/login": "Reference Reflex Cloud CLI login options for authenticating your terminal with your hosting account.",
+ "hosting/cli/vmtypes": "Reference Reflex Cloud CLI VM type commands to explore compute options for hosted applications.",
+ "overview": "Explore Reflex Cloud hosting and find guides for deploying applications, managing secrets, and monitoring your apps.",
+ "ai/figma": "Check the status of the planned Figma integration for Reflex Build. This integration is coming soon.",
+ "ai/integrations/overview": "Browse Reflex Build integrations for databases, AI services, and external tools, and find setup guides for connecting your app.",
+ "enterprise/components": "Explore enterprise Reflex components for advanced data grids, charts, editors, and interactive workflows, with guides and examples.",
+ "library/typography/em": "Add semantic emphasis to inline text with rx.text.em. See a Python example of emphasizing words within a Reflex text component.",
+ "library/typography/quote": "Mark short inline quotations with rx.text.quote. See how to include quoted text within a Reflex text component.",
+ "wrapping-react/step-by-step": "The Wrapping React Step by Step documentation page is a placeholder; a detailed component-wrapping walkthrough is not yet available.",
+}
+
def truncate_meta_description(description: str, max_len: int = 155) -> str:
"""Return a search-snippet-safe meta description.
@@ -55,8 +91,15 @@ def docs_metadata(path: str, title: str, description: str | None) -> tuple[str,
parent for parent in reversed(parents) if parent.lower() != title.lower()
]
subject = " ยท ".join([title, *context])
+ route = path.strip("/")
+ if route == "changelog" or route.startswith("changelog/"):
+ package = route.removeprefix("changelog/") if "/" in route else "reflex"
+ fallback = f"Read {package} release notes, including new features, bug fixes, and breaking changes, before upgrading your project."
+ else:
+ fallback = PAGE_DESCRIPTIONS.get(route)
summary = (
description
+ or fallback
or f"{subject}: documentation, examples, and reference for building Python web applications with Reflex."
)
return f"{subject} ยท Reflex Docs", truncate_meta_description(summary)
diff --git a/docs/app/reflex_docs/pages/docs/recipes_overview.py b/docs/app/reflex_docs/pages/docs/recipes_overview.py
index 693b9b2a36b..45f12b5a2e7 100644
--- a/docs/app/reflex_docs/pages/docs/recipes_overview.py
+++ b/docs/app/reflex_docs/pages/docs/recipes_overview.py
@@ -5,7 +5,7 @@
def get_component_link(category, clist) -> str:
file_name_without_extension = clist.split("/")[-1].split(".")[0].replace("_", "-")
- return f"/recipes/{category}/{file_name_without_extension}"
+ return f"/recipes/{category}/{file_name_without_extension}/"
def format_titles(path):
@@ -32,27 +32,27 @@ def component_grid():
rx.box(
rx.el.h2(
rx.utils.format.to_title_case(category),
- class_name="font-large text-secondary-12",
+ class_name="text-lg font-book leading-6 tracking-tight text-foreground",
),
rx.icon(
icons.get(category, "shapes"),
size=18,
- class_name="!text-secondary-9",
+ class_name="text-muted-foreground",
),
- class_name="px-4 py-2 flex flex-row !text-secondary-12 gap-3 items-center justify-between",
+ class_name="px-5 py-4 flex flex-row text-foreground gap-3 items-center justify-between",
),
rx.box(
*[
rx.link(
format_titles(c),
href=get_component_link(category, c),
- class_name="font-small text-secondary-11 hover:!text-primary-9 transition-color w-fit",
+ class_name="text-sm font-book leading-6 text-muted-foreground hover:text-foreground transition-colors w-fit rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
)
for c in recipes_list[category]
],
- class_name="flex flex-col gap-3 px-4 py-2 border-t border-secondary-5",
+ class_name="flex flex-col gap-2 px-5 pb-5",
),
- class_name="flex flex-col border border-secondary-5 rounded-xl bg-secondary-2 shadow-large overflow-hidden",
+ class_name="docs-catalog-card flex flex-col border border-border-subtle rounded-card bg-background overflow-hidden",
)
)
@@ -65,13 +65,13 @@ def info_card(title, content):
return rx.box(
rx.el.h2(
title,
- class_name="font-md-smbold text-secondary-12",
+ class_name="text-lg font-book leading-6 tracking-tight text-foreground",
),
rx.text(
content,
- class_name="font-small text-secondary-11",
+ class_name="text-sm font-normal leading-6 text-muted-foreground",
),
- class_name="flex flex-col border gap-2 border-secondary-5 rounded-xl bg-secondary-1 shadow-large overflow-hidden px-4 py-2",
+ class_name="docs-catalog-card flex flex-col gap-2 border border-border-subtle rounded-card bg-background overflow-hidden p-5",
)
@@ -95,7 +95,7 @@ def card_section():
)
-@docpage(set_path="/recipes", right_sidebar=False)
+@docpage(set_path="/recipes/", right_sidebar=False)
def overview():
return rx.box(
h1_comp(text="Recipes"),
diff --git a/docs/app/reflex_docs/pages/docs/source.py b/docs/app/reflex_docs/pages/docs/source.py
index 3178d77d8ad..26166a91231 100644
--- a/docs/app/reflex_docs/pages/docs/source.py
+++ b/docs/app/reflex_docs/pages/docs/source.py
@@ -12,10 +12,10 @@
from reflex_docs.templates.docpage import h1_comp, h2_comp
table_header_class_name = (
- "font-small text-secondary-12 text-normal w-auto justify-start pl-4 font-bold"
+ "text-sm font-medium text-foreground w-auto justify-start pl-4"
)
-description_cell_class_name = "font-small text-secondary-11"
+description_cell_class_name = "text-sm font-normal leading-6 text-muted-foreground"
def format_field(field: FieldDocumentation) -> rx.Component:
diff --git a/docs/app/reflex_docs/pages/docs_landing/__init__.py b/docs/app/reflex_docs/pages/docs_landing/__init__.py
index 4dbffcc1fd1..1c642dc134f 100644
--- a/docs/app/reflex_docs/pages/docs_landing/__init__.py
+++ b/docs/app/reflex_docs/pages/docs_landing/__init__.py
@@ -1,8 +1,7 @@
import reflex as rx
from reflex_site_shared.constants import REFLEX_ASSETS_CDN
from reflex_site_shared.meta.meta import create_meta_tags
-from reflex_site_shared.views.cta_card import cta_card
-from reflex_site_shared.views.footer import footer_index
+from reflex_site_shared.utils.url import public_url
from reflex_docs.pages.docs_landing.views import (
ai_builder_section,
@@ -14,7 +13,9 @@
other_section,
self_hosting_section,
)
+from reflex_docs.pages.docs_landing.views.cta import docs_cta
from reflex_docs.views.docs_navbar import docs_navbar
+from reflex_docs.views.editorial_footer import editorial_footer
@rx.page(
@@ -24,12 +25,15 @@
# create_meta_tags list to avoid a favicon-default + preview duplicate.
image=f"{REFLEX_ASSETS_CDN}previews/index_preview.webp",
meta=[
+ rx.el.link(rel="alternate", type="text/markdown", href=public_url("/index.md"))
+ ]
+ + [
m
for m in create_meta_tags(
title="Reflex Documentation - Build Web Apps in Pure Python",
description="Reflex documentation: tutorials, API reference, and guides for building full-stack Python web apps. Get started in minutes.",
image=f"{REFLEX_ASSETS_CDN}previews/index_preview.webp",
- url="https://reflex.dev/docs/",
+ url=public_url("/"),
)
if not (isinstance(m, dict) and m.get("property") == "og:image")
],
@@ -47,8 +51,8 @@ def docs_landing() -> rx.Component:
hosting_section(),
self_hosting_section(),
other_section(),
- cta_card(),
- footer_index(),
+ docs_cta(),
+ editorial_footer(),
class_name="flex flex-col relative justify-center items-center w-full overflow-hidden",
),
class_name="flex flex-col w-full relative h-full justify-center items-center",
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py b/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py
index 6339dded631..8456a74d68c 100644
--- a/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py
+++ b/docs/app/reflex_docs/pages/docs_landing/views/ai_builder.py
@@ -4,10 +4,10 @@
import reflex as rx
import reflex_components_internal as ui
from reflex_site_shared.components.marquee import marquee
-from reflex_site_shared.constants import REFLEX_ASSETS_CDN
from reflex_site_shared.integrations import get_integration_logo_url
from reflex_docs.pages.docs import ai_builder as ai_builder_pages
+from reflex_docs.pages.docs_landing.views.artwork import artwork
def get_integration_path() -> list:
@@ -67,33 +67,49 @@ def get_integration_path() -> list:
def card(
- title: str, description: str, content: str, href: str, enteprise_only: bool = False
+ title: str,
+ description: str,
+ content: rx.Component,
+ href: str,
+ tone: str,
+ enterprise_only: bool = False,
) -> rx.Component:
+ """Render a linked guide with a matching editorial illustration panel."""
return rx.el.div(
- rx.el.span(
- "Enterprise-only",
- class_name="text-secondary-12 text-xs font-medium bg-secondary-1 px-2.5 h-7 absolute top-0 right-0 border-b border-l rounded-bl-lg border-secondary-4 flex justify-center items-center",
- )
- if enteprise_only
- else None,
rx.el.div(
- rx.el.span(
- title,
- class_name="text-secondary-12 text-xl font-[575]",
+ rx.el.div(
+ rx.el.h3(
+ title,
+ class_name="docs-ai-card-title text-xl font-book tracking-tight",
+ ),
+ rx.el.span(
+ "Enterprise-only",
+ class_name="w-fit rounded-full border border-border bg-muted px-2.5 py-1 text-xs text-muted-foreground",
+ )
+ if enterprise_only
+ else None,
+ rx.icon(
+ "arrow-up-right",
+ size=18,
+ aria_hidden=True,
+ class_name="ml-auto shrink-0",
+ ),
+ class_name="flex w-full items-center gap-3 text-foreground",
),
- rx.el.span(
+ rx.el.p(
description,
- class_name="text-secondary-11 text-sm font-[475]",
+ class_name="text-sm font-normal leading-6 text-muted-foreground",
),
- class_name="flex flex-col gap-2 p-8",
+ class_name="flex flex-1 flex-col items-start gap-3 p-7 sm:p-8",
),
- content,
+ rx.el.div(content, class_name="docs-ai-card-art", aria_hidden=True),
rx.el.a(
href=href,
aria_label=title,
- class_name="absolute inset-0 rounded-xl focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-primary-9",
+ class_name="docs-ai-card-link absolute inset-0 rounded-panel focus-visible:outline-2 focus-visible:-outline-offset-4 focus-visible:outline-foreground",
),
- class_name="flex flex-col bg-secondary-1/96 backdrop-blur-[16px] rounded-xl relative cursor-pointer transition-colors overflow-hidden shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_12px_24px_0_rgba(0,0,0,0.08),0_1px_1px_0_rgba(0,0,0,0.01),0_4px_8px_0_rgba(0,0,0,0.03)] dark:shadow-none dark:border dark:border-secondary-4",
+ data_tone=tone,
+ class_name="docs-ai-card flex min-w-0 flex-col overflow-hidden rounded-panel border border-border bg-background relative text-left transition-colors hover:border-border-strong",
)
@@ -106,7 +122,7 @@ def integration_icon_marquee(integration_name: str) -> rx.Component:
),
alt=f"{integration_name} logo",
unstyled=True,
- class_name="size-full",
+ class_name="size-6 object-contain",
),
ui.avatar.fallback(
integration_name[0],
@@ -114,7 +130,7 @@ def integration_icon_marquee(integration_name: str) -> rx.Component:
unstyled=True,
),
unstyled=True,
- class_name="size-6.5 flex items-center justify-center mx-3",
+ class_name="docs-ai-integration flex size-14 shrink-0 items-center justify-center rounded-xl border border-border bg-background mx-2",
)
@@ -127,22 +143,20 @@ def integrations_marquee() -> rx.Component:
marquee(
*[integration_icon_marquee(name) for name in reversed(integration_names)],
direction="left",
- gradient_color="light-dark(rgba(255, 255, 255, 0.96), var(--secondary-1))",
+ gradient=False,
class_name="h-auto w-full overflow-hidden",
- gradient_width=65,
speed=25,
- pause_on_hover=False,
+ pause_on_hover=True,
),
marquee(
*[integration_icon_marquee(name) for name in integration_names],
direction="right",
- gradient_color="light-dark(rgba(255, 255, 255, 0.96), var(--secondary-1))",
+ gradient=False,
class_name="h-auto w-full overflow-hidden",
- gradient_width=65,
speed=25,
- pause_on_hover=False,
+ pause_on_hover=True,
),
- class_name="flex flex-col gap-6.5 px-8 max-lg:pb-6",
+ class_name="docs-ai-integrations flex w-full flex-col gap-4",
)
@@ -152,11 +166,11 @@ def ai_builder_section() -> rx.Component:
rx.el.div(
rx.el.h2(
"AI Builder",
- class_name="text-secondary-12 text-3xl font-[575]",
+ class_name="text-secondary-12 text-3xl font-book tracking-tight",
),
rx.el.p(
"Learn how to build applications with Reflex AI.",
- class_name="text-secondary-11 text-sm font-[475]",
+ class_name="text-secondary-11 text-sm font-normal",
),
class_name="flex flex-col gap-4",
),
@@ -164,33 +178,34 @@ def ai_builder_section() -> rx.Component:
card(
title="Getting Started",
description="A comprehensive guide to working effectively with AI Builder. The key to success is clarity, structure, and iteration.",
- content=rx.image(
- src=f"{REFLEX_ASSETS_CDN}docs/{rx.color_mode_cond('light', 'dark')}/getting_started_1.svg",
- alt="AI Builder getting started guide",
- class_name="w-full h-auto pb-8",
+ content=artwork(
+ "ai_getting_started",
+ "w-full",
),
href=ai_builder_pages.overview.best_practices.path,
+ tone="blue",
),
card(
title="Integrations",
description="Easily connect with the tools your team already uses or extend your app with any Python SDK, library, or API.",
content=integrations_marquee(),
href=ai_builder_pages.integrations.overview.path,
+ tone="peach",
),
card(
title="MCP",
description="The Reflex Model Context Protocol (MCP) provides AI assistants and coding tools with structured access to Reflex documentation and component information.",
- content=rx.image(
- src=f"{REFLEX_ASSETS_CDN}docs/{rx.color_mode_cond('light', 'dark')}/mcp_1.svg",
- alt="Reflex MCP integration illustration",
- class_name="w-full h-auto -mt-4",
+ content=artwork(
+ "ai_mcp",
+ "w-full",
),
href=ai_builder_pages.integrations.mcp_overview.path,
- enteprise_only=True,
+ tone="mint",
+ enterprise_only=True,
),
- class_name="grid grid-cols-1 lg:grid-cols-3 gap-12",
+ class_name="grid grid-cols-1 lg:grid-cols-3 gap-6",
),
class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto",
),
- class_name="bg-gradient-to-b from-secondary-2 to-secondary-1 w-full lg:pt-24 lg:pb-24 pb-10 max-xl:px-6 max-lg:pt-10",
+ class_name="bg-muted w-full lg:pt-24 lg:pb-24 pb-10 max-xl:px-6 max-lg:pt-10",
)
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/ai_getting_started.svg b/docs/app/reflex_docs/pages/docs_landing/views/ai_getting_started.svg
new file mode 100644
index 00000000000..b11c3368494
--- /dev/null
+++ b/docs/app/reflex_docs/pages/docs_landing/views/ai_getting_started.svg
@@ -0,0 +1,17 @@
+
\ No newline at end of file
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/ai_mcp.svg b/docs/app/reflex_docs/pages/docs_landing/views/ai_mcp.svg
new file mode 100644
index 00000000000..8f4e33e0d47
--- /dev/null
+++ b/docs/app/reflex_docs/pages/docs_landing/views/ai_mcp.svg
@@ -0,0 +1,15 @@
+
\ No newline at end of file
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/artwork.py b/docs/app/reflex_docs/pages/docs_landing/views/artwork.py
new file mode 100644
index 00000000000..75abbb8e544
--- /dev/null
+++ b/docs/app/reflex_docs/pages/docs_landing/views/artwork.py
@@ -0,0 +1,14 @@
+"""Theme-aware versions of the documentation illustrations."""
+
+from pathlib import Path
+
+import reflex as rx
+
+
+def artwork(name: str, class_name: str = "") -> rx.Component:
+ """Inline a trusted local SVG so its colors follow the page theme."""
+ return rx.html(
+ Path(__file__).with_name(f"{name}.svg").read_text(encoding="utf-8"),
+ aria_hidden=True,
+ class_name=f"pointer-events-none [&_svg]:h-auto [&_svg]:w-full {class_name}",
+ )
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/cta.py b/docs/app/reflex_docs/pages/docs_landing/views/cta.py
new file mode 100644
index 00000000000..2e6b3353f31
--- /dev/null
+++ b/docs/app/reflex_docs/pages/docs_landing/views/cta.py
@@ -0,0 +1,58 @@
+"""A quiet closing invitation using the editorial theme."""
+
+import reflex as rx
+import reflex_components_internal as ui
+from reflex_components_internal.blocks.demo_form import demo_form_open_cs
+from reflex_site_shared.components.marketing_button import button
+from reflex_site_shared.constants import REFLEX_BUILD_URL
+
+from reflex_docs.pages.docs_landing.views.artwork import artwork
+
+
+def docs_cta() -> rx.Component:
+ """Render the closing actions alongside the original product illustration."""
+ return rx.el.section(
+ rx.el.div(
+ rx.el.div(
+ rx.el.h2(
+ "The platform to build and scale enterprise apps.",
+ id="docs-cta-title",
+ class_name="max-w-xl text-3xl sm:text-4xl font-book leading-[1.15] tracking-[-0.03em] text-foreground text-balance",
+ ),
+ rx.el.p(
+ "Describe your idea, and let AI transform it into a complete, "
+ "production-ready Python web application.",
+ class_name="max-w-lg text-base leading-7 font-normal text-muted-foreground",
+ ),
+ rx.el.div(
+ rx.el.elements.a(
+ button(
+ "Try for free",
+ ui.icon("ArrowRight01Icon", aria_hidden=True),
+ variant="primary",
+ native_button=False,
+ ),
+ href=REFLEX_BUILD_URL,
+ target="_blank",
+ rel="noopener noreferrer",
+ class_name="rounded-full focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
+ ),
+ button(
+ "Book a Demo",
+ variant="ghost",
+ aria_haspopup="dialog",
+ on_click=rx.call_function(demo_form_open_cs.set_value(True)),
+ ),
+ class_name="flex flex-wrap items-center gap-x-5 gap-y-3",
+ ),
+ class_name="relative z-10 flex flex-col items-start gap-6 p-6 sm:p-10 lg:py-14 lg:pl-12 lg:pr-0",
+ ),
+ artwork(
+ "cta_illustration",
+ "docs-cta-art self-center w-full max-lg:hidden",
+ ),
+ class_name="grid lg:grid-cols-[1.1fr_1fr] items-center overflow-hidden rounded-panel border border-border-subtle bg-muted",
+ ),
+ aria_labelledby="docs-cta-title",
+ class_name="docs-cta mx-auto w-full max-w-(--landing-layout-max-width) px-6 xl:px-0 py-16 lg:py-24",
+ )
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/cta_illustration.svg b/docs/app/reflex_docs/pages/docs_landing/views/cta_illustration.svg
new file mode 100644
index 00000000000..3f9abdf4eed
--- /dev/null
+++ b/docs/app/reflex_docs/pages/docs_landing/views/cta_illustration.svg
@@ -0,0 +1,224 @@
+
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/enterprise.py b/docs/app/reflex_docs/pages/docs_landing/views/enterprise.py
index 3ed024c832b..9d99c82f13c 100644
--- a/docs/app/reflex_docs/pages/docs_landing/views/enterprise.py
+++ b/docs/app/reflex_docs/pages/docs_landing/views/enterprise.py
@@ -9,11 +9,11 @@ def enterprise_section() -> rx.Component:
rx.el.div(
rx.el.h2(
"Enterprise",
- class_name="text-secondary-12 text-3xl font-[575]",
+ class_name="text-secondary-12 text-3xl font-book tracking-tight",
),
rx.el.p(
"Learn how to build enterprise-ready applications with Reflex.",
- class_name="text-secondary-11 text-sm font-[475]",
+ class_name="text-secondary-11 text-sm font-normal",
),
class_name="flex flex-col gap-4",
),
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/framework.py b/docs/app/reflex_docs/pages/docs_landing/views/framework.py
index 81f1159fa07..d634ab45f6d 100644
--- a/docs/app/reflex_docs/pages/docs_landing/views/framework.py
+++ b/docs/app/reflex_docs/pages/docs_landing/views/framework.py
@@ -1,191 +1,215 @@
+"""Interactive framework overview with editorial product diagrams."""
+
import reflex as rx
import reflex_components_internal as ui
-from reflex_site_shared.components.marketing_button import button
-from reflex_site_shared.constants import REFLEX_ASSETS_CDN
from reflex_docs.pages.docs import database, enterprise, getting_started
from reflex_docs.pages.docs.library import library
-from reflex_docs.pages.library_previews import core_components_dict
+from reflex_docs.pages.docs_landing.views.artwork import artwork
+CAPABILITIES = (
+ (
+ "how",
+ "How It Works",
+ "Define your interface and state in Python. Reflex connects them into a reactive web application.",
+ "From Python to a live interface",
+ "Read the introduction",
+ getting_started.introduction.path,
+ "blue",
+ ),
+ (
+ "components",
+ "Components",
+ "Each component creates a part of your interface. Nest headings, inputs, and buttons inside layout components to build a page.",
+ "Python components. A complete interface.",
+ "Browse all components",
+ library.path,
+ "lavender",
+ ),
+ (
+ "auth",
+ "Auth",
+ "Connect your identity provider and control who can access your application with enterprise authentication.",
+ "Your identity. Your access rules.",
+ "Explore authentication",
+ enterprise.auth.overview.path,
+ "peach",
+ ),
+ (
+ "database",
+ "Database",
+ "Define typed models, query your database, and bring live data into your application with Python.",
+ "One model, from database to interface",
+ "Explore databases",
+ database.overview.path,
+ "mint",
+ ),
+)
-def docs_item(
- icon: str, title: str, description: str, href: str, enterprise_only: bool = False
-) -> rx.Component:
- return rx.el.div(
- rx.el.div(
- ui.icon(
- icon,
- class_name="size-6 group-hover:text-primary-10 group-hover:dark:text-secondary-11",
- stroke_width=1.5,
- ),
- rx.el.span(
- title,
- class_name="text-secondary-12 text-xl font-[575] group-hover:text-primary-10 group-hover:dark:text-secondary-11",
- ),
- rx.el.div(
- "Enterprise-only",
- class_name="text-secondary-12 text-xs font-medium bg-secondary-1 px-2.5 h-7 border-b border rounded-lg border-secondary-4 flex justify-center items-center ml-1",
- )
- if enterprise_only
- else None,
- ui.icon(
- "ArrowRight01Icon",
- class_name="size-4 ml-auto group-hover:text-primary-10 group-hover:dark:text-secondary-11",
- ),
- class_name="flex row items-center gap-3 h-8",
- ),
- rx.el.p(
- description,
- class_name="text-secondary-11 text-sm font-[475]",
- ),
- rx.el.a(to=href, class_name="absolute inset-0"),
- class_name="flex flex-col gap-2 py-8 pr-8 relative group lg:max-w-[21rem] w-full max-lg:text-start hover:bg-[linear-gradient(243deg,var(--secondary-2)_0%,var(--secondary-1)_100%)]",
- )
+class FrameworkCounterState(rx.State):
+ """Keep the landing-page counter in real server-side Python state."""
-def links_section() -> rx.Component:
- return rx.el.div(
- docs_item(
- "SourceCodeSquareIcon",
- "How It Works",
- "Learn the basics of how Reflex works behind the scenes and how its architecture enables flexible, advanced usage.",
- getting_started.introduction.path,
- ),
- docs_item(
- "ShieldUserIcon",
- "Auth",
- "Implement secure authentication for your apps using Reflexโs built-in features and extensible architecture.",
- enterprise.auth.overview.path,
- enterprise_only=True,
- ),
- docs_item(
- "DatabaseIcon",
- "Database",
- "Manage and interact with your data seamlessly using Reflexโs straightforward models and querying approach.",
- database.overview.path,
- ),
- class_name="flex flex-col border-r border-y border-secondary-4 divide-y divide-secondary-4",
- )
+ count: int = 0
+ @rx.event
+ def increment(self):
+ """Increase the displayed count by one."""
+ self.count += 1
-def component_link(name: str, href: str) -> rx.Component:
- return rx.el.a(
- button(
- name,
- ui.icon("ArrowRight01Icon", class_name="ml-auto"),
- variant="ghost",
- size="xs",
- class_name="font-[525] w-full text-secondary-12 px-0",
- ),
- to=f"/library/{href.strip('/')}",
- class_name="w-full",
+
+def example_window_header(title: str) -> rx.Component:
+ """Render the shared chrome of the code and preview windows."""
+ return rx.el.div(
+ rx.el.span("โขโขโข", aria_hidden=True, class_name="tracking-[3px]"),
+ rx.el.span(title),
+ class_name="flex items-center gap-5 border-b border-border px-5 py-3 text-xs text-muted-foreground",
)
-def components_section() -> rx.Component:
+def live_counter_diagram() -> rx.Component:
+ """Connect a readable Python example to a working counter interface."""
return rx.el.div(
rx.el.div(
- rx.el.div(
- ui.icon("MenuSquareIcon", class_name="size-6", stroke_width=1.5),
- rx.el.span(
- "Component Library",
- class_name="text-secondary-12 text-xl font-[575] group-hover:text-primary-10",
+ example_window_header("app.py"),
+ rx.el.pre(
+ rx.el.code(
+ "import reflex as rx\n\n"
+ "class State(rx.State):\n"
+ " count: int = 0\n\n"
+ " @rx.event\n"
+ " def increment(self):\n"
+ " self.count += 1",
),
- class_name="flex row items-center gap-3 h-8",
+ class_name="m-0 px-5 py-5 font-mono text-[11px] leading-6 text-foreground sm:text-[13px]",
),
- rx.el.p(
- "Build your app with our comprehensive collection of UI components and features.",
- class_name="text-secondary-11 text-sm font-[475] max-w-[16.5rem]",
- ),
- rx.el.a(
- button(
- "Browse All Components",
- variant="outline",
- native_button=False,
- class_name="font-[525] w-fit text-secondary-12",
- ),
- to=library.path,
- class_name="w-fit mt-4",
- ),
- class_name="flex flex-col gap-2 max-lg:text-start",
+ class_name="docs-framework-live-code w-full overflow-hidden rounded-xl border border-border bg-background",
),
+ rx.el.div(class_name="docs-framework-live-connector", aria_hidden=True),
rx.el.div(
+ example_window_header("Live preview"),
rx.el.div(
- component_link(
- "Data Display", core_components_dict["data-display"]["path"]
- ),
- component_link(
- "Disclosure", core_components_dict["disclosure"]["path"]
+ rx.el.h3(
+ "Your first Reflex app",
+ class_name="text-xl font-book tracking-tight text-foreground",
),
- component_link(
- "Dynamic Rendering",
- core_components_dict["dynamic_rendering"]["path"],
+ rx.el.div(
+ rx.el.output(
+ FrameworkCounterState.count,
+ aria_label="Counter value",
+ aria_live="polite",
+ aria_atomic=True,
+ class_name="docs-framework-counter-value flex min-h-16 min-w-20 items-center justify-center rounded-lg px-4 text-3xl tabular-nums",
+ ),
+ rx.el.button(
+ "Increment",
+ rx.icon("plus", size=16, aria_hidden=True),
+ type="button",
+ on_click=FrameworkCounterState.increment,
+ class_name="inline-flex min-h-11 items-center justify-center gap-2 rounded-full bg-foreground px-5 text-sm text-background transition-opacity hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
+ ),
+ class_name="flex flex-wrap items-center justify-between gap-4",
),
- component_link("Forms", core_components_dict["forms"]["path"]),
- component_link("Layout", core_components_dict["layout"]["path"]),
- component_link("Media", core_components_dict["media"]["path"]),
- class_name="flex flex-col gap-2",
+ class_name="flex flex-col gap-5 p-5 sm:p-6",
),
- rx.el.div(
- component_link("Other", core_components_dict["other"]["path"]),
- component_link("Overlays", core_components_dict["overlays"]["path"]),
- component_link(
- "Tables And Data Grids Rendering",
- core_components_dict["tables_and_data_grids"]["path"],
- ),
- component_link(
- "Typography", core_components_dict["typography"]["path"]
- ),
- class_name="flex flex-col gap-2",
- ),
- class_name="grid grid-cols-1 lg:grid-cols-2 lg:gap-28 gap-10 mt-auto",
+ class_name="docs-framework-live-app w-full overflow-hidden rounded-xl border border-border bg-background",
),
- class_name="flex flex-col gap-4 lg:px-8 pt-8 max-lg:pr-8 pb-6 h-auto w-full flex-1 border-r border-secondary-4 border-y",
+ class_name="docs-framework-live mx-auto flex w-full max-w-[30rem] flex-col items-center py-4",
)
-def squares_divider() -> rx.Component:
- return rx.el.div(
- rx.image(
- src=f"{REFLEX_ASSETS_CDN}common/{rx.color_mode_cond('light', 'dark')}/squares_vertical_docs.svg",
- alt="Squares Vertical Docs",
- loading="lazy",
- class_name="pointer-events-none w-auto h-full",
+def framework_tab(value: str, title: str, description: str) -> rx.Component:
+ """Select a diagram with a keyboard-accessible vertical tab."""
+ return ui.tabs.tab(
+ rx.el.span(
+ rx.el.span(
+ title, class_name="text-xl font-book tracking-tight sm:text-2xl"
+ ),
+ rx.el.span(
+ "Enterprise-only",
+ class_name="rounded-full border border-border bg-muted px-2 py-0.5 text-xs font-normal tracking-normal text-muted-foreground",
+ )
+ if value == "auth"
+ else None,
+ class_name="flex flex-wrap items-center gap-3",
),
- class_name="flex p-4.5 h-auto border-r border-y border-secondary-4 max-lg:hidden",
+ rx.el.span(description, class_name="docs-framework-tab-description"),
+ value=value,
+ aria_label=title,
+ unstyled=True,
+ class_name="docs-framework-tab w-full text-left text-foreground focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
)
-def framework() -> rx.Component:
- return rx.el.section(
+def framework_panel(
+ value: str, caption: str, link: str, href: str, tone: str
+) -> rx.Component:
+ """Pair an interactive example or illustration with its documentation link."""
+ return ui.tabs.panel(
rx.el.div(
- rx.el.h2(
- "Framework",
- class_name="text-secondary-12 text-3xl font-[575]",
- ),
- rx.el.p(
- "Learn how to build applications with Reflex Framework.",
- class_name="text-secondary-11 text-sm font-[475]",
- ),
- class_name="flex flex-col gap-4",
+ live_counter_diagram()
+ if value == "how"
+ else artwork(f"framework_{value}", class_name="docs-framework-diagram"),
+ class_name="docs-framework-stage",
),
rx.el.div(
- rx.el.div(
- class_name="absolute bottom-0 -left-24 w-24 h-px bg-gradient-to-r from-transparent to-current text-secondary-4"
- ),
- rx.el.div(
- class_name="absolute top-0 -left-24 w-24 h-px bg-gradient-to-r from-transparent to-current text-secondary-4"
+ rx.el.p(caption, class_name="text-base font-book text-foreground"),
+ rx.el.a(
+ link,
+ rx.icon("arrow-right", class_name="size-4", aria_hidden=True),
+ href=href,
+ class_name="inline-flex min-h-10 w-fit items-center gap-2 rounded-full border border-border bg-background px-4 text-sm font-book text-foreground transition-colors hover:border-border-strong focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
),
+ class_name="docs-framework-caption flex flex-wrap items-center justify-between gap-x-6 gap-y-2 border-t border-border bg-muted px-6 py-5 sm:px-8",
+ ),
+ value=value,
+ unstyled=True,
+ custom_attrs={"data-tone": tone},
+ class_name="docs-framework-panel min-w-0 overflow-hidden rounded-panel border border-border-subtle",
+ )
+
+
+def framework() -> rx.Component:
+ """Explore framework capabilities through tabs and product diagrams."""
+ return rx.el.section(
+ ui.tabs.root(
rx.el.div(
- class_name="absolute bottom-0 -right-24 w-24 h-px bg-gradient-to-l from-transparent to-current text-secondary-4"
+ rx.el.div(
+ rx.el.h2(
+ "Framework",
+ id="docs-framework-title",
+ class_name="text-foreground text-3xl font-book tracking-tight",
+ ),
+ rx.el.p(
+ "Learn how to build applications with Reflex Framework.",
+ class_name="text-muted-foreground text-sm font-normal",
+ ),
+ class_name="flex flex-col gap-4",
+ ),
+ ui.tabs.list(
+ *[
+ framework_tab(value, title, description)
+ for value, title, description, *_ in CAPABILITIES
+ ],
+ activate_on_focus=True,
+ aria_label="Framework capabilities",
+ unstyled=True,
+ class_name="docs-framework-tabs flex min-w-0 flex-col w-full",
+ ),
+ class_name="docs-framework-navigation flex min-w-0 flex-col gap-6",
),
rx.el.div(
- class_name="absolute right-0 -top-24 h-24 w-px bg-gradient-to-b from-transparent to-current text-secondary-4"
+ *[
+ framework_panel(value, caption, link, href, tone)
+ for value, _, _, caption, link, href, tone in CAPABILITIES
+ ],
+ class_name="min-w-0",
),
- links_section(),
- squares_divider(),
- components_section(),
- class_name="flex flex-col lg:flex-row relative",
+ default_value="how",
+ orientation="vertical",
+ unstyled=True,
+ class_name="grid grid-cols-1 items-center gap-8 lg:grid-cols-[minmax(0,1fr)_minmax(0,2fr)] lg:gap-12",
),
- class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto w-full justify-start lg:mb-24 mb-10 max-xl:px-6 overflow-hidden",
+ aria_labelledby="docs-framework-title",
+ class_name="docs-framework-section flex flex-col gap-10 max-w-(--landing-layout-max-width) mx-auto w-full lg:pt-24 pt-10 lg:mb-24 mb-10 max-xl:px-6",
)
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/framework_auth.svg b/docs/app/reflex_docs/pages/docs_landing/views/framework_auth.svg
new file mode 100644
index 00000000000..7eb44930289
--- /dev/null
+++ b/docs/app/reflex_docs/pages/docs_landing/views/framework_auth.svg
@@ -0,0 +1 @@
+
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/framework_components.svg b/docs/app/reflex_docs/pages/docs_landing/views/framework_components.svg
new file mode 100644
index 00000000000..6f4ab302fa5
--- /dev/null
+++ b/docs/app/reflex_docs/pages/docs_landing/views/framework_components.svg
@@ -0,0 +1 @@
+
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/framework_database.svg b/docs/app/reflex_docs/pages/docs_landing/views/framework_database.svg
new file mode 100644
index 00000000000..1b0c6b3463a
--- /dev/null
+++ b/docs/app/reflex_docs/pages/docs_landing/views/framework_database.svg
@@ -0,0 +1 @@
+
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/getting_started_illustration.svg b/docs/app/reflex_docs/pages/docs_landing/views/getting_started_illustration.svg
new file mode 100644
index 00000000000..e9663e3cac8
--- /dev/null
+++ b/docs/app/reflex_docs/pages/docs_landing/views/getting_started_illustration.svg
@@ -0,0 +1,339 @@
+
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/hero.py b/docs/app/reflex_docs/pages/docs_landing/views/hero.py
index bde3723225d..7fcb16108b4 100644
--- a/docs/app/reflex_docs/pages/docs_landing/views/hero.py
+++ b/docs/app/reflex_docs/pages/docs_landing/views/hero.py
@@ -1,66 +1,56 @@
+"""Editorial introduction to the documentation."""
+
import reflex as rx
import reflex_components_internal as ui
from reflex_site_shared.components.marketing_button import button
-from reflex_site_shared.constants import REFLEX_ASSETS_CDN
-from reflex_site_shared.views.hosting_banner import HostingBannerState
from reflex_docs.pages.docs import getting_started
+from reflex_docs.pages.docs_landing.views.artwork import artwork
def hero() -> rx.Component:
+ """Render the docs introduction using the marketing typography and actions."""
return rx.el.section(
rx.el.div(
- rx.el.p(
- "About Reflex",
- class_name="text-sm font-[525] text-primary-10 dark:text-secondary-11",
- ),
rx.el.h1(
"Reflex Documentation",
- class_name="text-secondary-12 lg:text-5xl text-3xl font-[575] lg:text-nowrap",
+ class_name="text-foreground text-4xl sm:text-5xl lg:text-6xl font-book tracking-[-0.03em] leading-[1.06] text-balance",
),
rx.el.p(
- "Get up and running with Reflex in minutes. A complete set ",
- rx.el.br(class_name="max-lg:hidden"),
- " of resources to build, deploy, and scale your application. ",
- class_name="text-base text-secondary-11 font-[475]",
- ),
- rx.el.a(
- button(
- "Get Started",
- ui.icon("ArrowRight01Icon"),
- variant="primary",
- size="md",
- native_button=False,
- class_name="w-fit",
- ),
- to=getting_started.introduction.path,
+ "Get up and running with Reflex in minutes. A complete set of resources "
+ "to build, deploy, and scale your application.",
+ class_name="max-w-2xl text-muted-foreground text-base sm:text-lg leading-7 font-normal text-balance",
),
- class_name=ui.cn(
- "flex flex-col gap-6 max-lg:text-center relative just-start lg:pb-24",
- rx.cond(
- HostingBannerState.is_banner_visible,
- "lg:pt-[14.5rem] pt-[12.5rem]",
- "lg:pt-[10.5rem] pt-[7.5rem]",
+ rx.el.div(
+ rx.el.a(
+ button(
+ "Build with AI",
+ ui.icon("ArrowRight01Icon"),
+ variant="primary",
+ size="lg",
+ native_button=False,
+ ),
+ href="/ai/",
+ class_name="rounded-full focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
),
- ),
- ),
- rx.el.div(
- rx.image(
- alt="Squares Docs Logo",
- custom_attrs={"fetchPriority": "high"},
- src=f"{REFLEX_ASSETS_CDN}common/{rx.color_mode_cond('light', 'dark')}/squares_docs_logo_1.svg",
- class_name="pointer-events-none h-auto w-auto lg:absolute max-lg:hidden",
- ),
- class_name=ui.cn(
- "flex",
- rx.cond(
- HostingBannerState.is_banner_visible,
- "lg:pt-[8.5rem] pt-0",
- "lg:pt-[4.5rem] pt-0",
+ rx.el.a(
+ button(
+ "Explore Framework",
+ ui.icon("ArrowRight01Icon"),
+ variant="outline",
+ size="lg",
+ native_button=False,
+ ),
+ href=getting_started.introduction.path,
+ class_name="rounded-full focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
),
+ class_name="flex flex-wrap items-center gap-3",
),
+ class_name="relative z-10 flex flex-col items-start gap-6 lg:max-w-[55%]",
),
- class_name=ui.cn(
- "flex lg:flex-row flex-col max-w-(--landing-layout-max-width) mx-auto w-full max-lg:pb-10 max-xl:px-6",
+ artwork(
+ "squares_docs_logo",
+ class_name="docs-hero-art absolute left-1/2 w-1/2 max-lg:hidden",
),
+ class_name="docs-hero relative max-w-(--landing-layout-max-width) mx-auto w-full px-6 xl:px-0 pb-16 lg:pb-24",
)
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 8fafce7a12a..006c9c46e4d 100644
--- a/docs/app/reflex_docs/pages/docs_landing/views/hosting.py
+++ b/docs/app/reflex_docs/pages/docs_landing/views/hosting.py
@@ -1,51 +1,175 @@
+"""A connected overview of the Reflex Cloud hosting guides."""
+
import reflex as rx
+from reflex_site_shared.constants import REFLEX_ASSETS_CDN
from reflex_docs.pages.docs import hosting as hosting_page
-from reflex_docs.pages.docs_landing.views.link_item import faded_borders, link_item
+from reflex_docs.pages.docs_landing.views.artwork import artwork
+
+
+def hosting_guide(icon: str, title: str, description: str, href: str) -> rx.Component:
+ """Link a hosting capability into the shared application diagram."""
+ return rx.el.a(
+ rx.el.div(
+ rx.icon(icon, size=20, aria_hidden=True, class_name="docs-cloud-icon"),
+ rx.icon("arrow-up-right", size=16, aria_hidden=True),
+ class_name="flex items-center justify-between text-muted-foreground",
+ ),
+ rx.el.h3(
+ title, class_name="text-base font-book tracking-tight text-foreground"
+ ),
+ rx.el.p(description, class_name="text-sm leading-6 text-muted-foreground"),
+ href=href,
+ aria_label=title,
+ class_name="docs-cloud-guide relative flex min-w-0 flex-1 flex-col gap-3 rounded-xl border border-border bg-background p-6 text-left transition-colors hover:border-border-strong focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
+ )
+
+
+def application_preview() -> rx.Component:
+ """Illustrate one complete hosted application in a single window."""
+ return rx.el.div(
+ rx.el.div(
+ rx.el.span("Overview", class_name="text-xs text-foreground"),
+ rx.el.span("โขโขโข", class_name="text-muted-foreground tracking-[2px]"),
+ class_name="flex items-center justify-between border-b border-border px-4 py-2",
+ ),
+ rx.el.div(
+ rx.el.div(
+ *[rx.icon(icon, size=14) for icon in ("layout-grid", "files", "users")],
+ class_name="flex w-10 shrink-0 flex-col items-center gap-5 border-r border-border bg-muted py-4 text-muted-foreground",
+ ),
+ rx.el.div(
+ rx.el.div(
+ *[
+ rx.el.div(
+ rx.el.span(
+ value, class_name="text-lg font-book text-foreground"
+ ),
+ rx.el.span(
+ label, class_name="text-[10px] text-muted-foreground"
+ ),
+ class_name="flex flex-1 flex-col rounded-md border border-border px-3 py-2",
+ )
+ for value, label in (("12", "Projects"), ("24", "Members"))
+ ],
+ class_name="flex gap-2",
+ ),
+ rx.el.div(
+ *[
+ rx.el.div(
+ style={"height": f"{height}%"},
+ class_name="docs-cloud-chart-bar min-w-0 flex-1 rounded-t-[2px]",
+ )
+ for height in (28, 44, 36, 61, 52, 73, 65, 92)
+ ],
+ class_name="flex h-16 items-end gap-2 border-b border-border pt-2",
+ ),
+ class_name="flex min-w-0 flex-1 flex-col gap-4 p-3",
+ ),
+ class_name="flex",
+ ),
+ aria_hidden=True,
+ class_name="overflow-hidden rounded-lg border border-border bg-background text-left",
+ )
+
+
+def hosting_application() -> rx.Component:
+ """Place one unified hosted application at the center of the diagram."""
+ return rx.el.div(
+ rx.el.div(
+ rx.el.span(
+ "Deploy anywhere",
+ class_name="whitespace-nowrap text-sm text-foreground",
+ ),
+ rx.el.div(
+ rx.el.div(
+ artwork("reflex_mark", class_name="w-4 text-foreground sm:w-5"),
+ role="img",
+ aria_label="Reflex",
+ title="Reflex",
+ class_name="shrink-0",
+ ),
+ *[
+ rx.el.img(
+ src=rx.color_mode_cond(
+ f"{REFLEX_ASSETS_CDN}landing/integrations/light/{provider}.svg",
+ f"{REFLEX_ASSETS_CDN}landing/integrations/dark/{provider}.svg",
+ ),
+ alt=label,
+ title=label,
+ class_name="size-4 object-contain sm:size-5",
+ )
+ for provider, label in (
+ ("aws", "AWS"),
+ ("azure", "Microsoft Azure"),
+ ("gcp", "Google Cloud"),
+ )
+ ],
+ class_name="flex shrink-0 items-center gap-1.5",
+ ),
+ class_name="flex items-center justify-between gap-2 border-b border-border px-4 py-4 sm:px-6",
+ ),
+ rx.el.div(
+ rx.el.h3(
+ "Your application",
+ class_name="text-xl font-book tracking-tight text-foreground",
+ ),
+ application_preview(),
+ class_name="flex flex-col gap-6 p-6 text-center",
+ ),
+ class_name="docs-cloud-hub relative min-w-0 self-center rounded-panel border border-border bg-background",
+ )
def hosting_section() -> rx.Component:
+ """Explore the connected deployment, security, and operations guides."""
return rx.el.section(
rx.el.div(
rx.el.h2(
"Cloud",
- class_name="text-secondary-12 text-3xl font-[575]",
+ id="docs-cloud-title",
+ class_name="text-foreground text-3xl font-book tracking-tight",
),
rx.el.p(
"Learn how to host your applications with Reflex Hosting.",
- class_name="text-secondary-11 text-sm font-[475]",
+ class_name="text-muted-foreground text-sm font-normal",
),
class_name="flex flex-col gap-4",
),
rx.el.div(
- faded_borders(),
- link_item(
- "CloudServerIcon",
- "Deployment",
- "Step-by-step instructions to deploy your Reflex application to the cloud, including configuration and setup guides.",
- hosting_page.deploy_quick_start.path,
- ),
- link_item(
- "LockKeyIcon",
- "Secret Management",
- "How to securely manage sensitive environment variables, API keys, and secrets in Reflex Hosting.",
- hosting_page.secrets_environment_vars.path,
- has_padding_left=True,
- ),
- link_item(
- "EyeIcon",
- "Observability",
- "Monitor your application's health, view logs, and gain insights using Reflex Hosting's integrated observability tools.",
- hosting_page.logs.path,
- ),
- link_item(
- "CodeIcon",
- "Custom Headers and Advanced Options",
- "Configure custom HTTP headers, set caching policies, and explore advanced hosting settings for your Reflex app.",
- hosting_page.deploy_quick_start.path,
- has_padding_left=True,
- ),
- class_name="grid grid-cols-1 lg:grid-cols-2 border-t border-secondary-4 relative",
- ),
- class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto w-full justify-start max-xl:px-6 lg:mb-24 overflow-hidden",
+ rx.el.div(
+ hosting_guide(
+ "cloud-upload",
+ "Deployment",
+ "Take your Python app from local development to the cloud.",
+ hosting_page.deploy_quick_start.path,
+ ),
+ hosting_guide(
+ "key-round",
+ "Secret Management",
+ "Securely provide API keys and environment variables to your app.",
+ hosting_page.secrets_environment_vars.path,
+ ),
+ class_name="docs-cloud-guides docs-cloud-inputs",
+ ),
+ hosting_application(),
+ rx.el.div(
+ hosting_guide(
+ "activity",
+ "Observability",
+ "View logs and monitor your application's health.",
+ hosting_page.logs.path,
+ ),
+ hosting_guide(
+ "settings-2",
+ "Custom Headers and Advanced Options",
+ "Set HTTP headers, caching policies, and hosting configuration.",
+ hosting_page.deploy_quick_start.path,
+ ),
+ class_name="docs-cloud-guides docs-cloud-outputs",
+ ),
+ class_name="docs-cloud-diagram rounded-panel border border-border",
+ ),
+ aria_labelledby="docs-cloud-title",
+ class_name="flex flex-col gap-10 max-lg:text-center relative max-w-(--landing-layout-max-width) mx-auto w-full justify-start max-xl:px-6 lg:mb-24",
)
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/link_item.py b/docs/app/reflex_docs/pages/docs_landing/views/link_item.py
index 41fbfb6adfd..7714026796c 100644
--- a/docs/app/reflex_docs/pages/docs_landing/views/link_item.py
+++ b/docs/app/reflex_docs/pages/docs_landing/views/link_item.py
@@ -3,20 +3,7 @@
def faded_borders() -> rx.Component:
- return rx.fragment(
- rx.el.div(
- class_name="absolute bottom-0 -left-24 w-24 h-px bg-gradient-to-r from-transparent to-current text-secondary-4"
- ),
- rx.el.div(
- class_name="absolute -top-px -left-24 w-24 h-px bg-gradient-to-r from-transparent to-current text-secondary-4"
- ),
- rx.el.div(
- class_name="absolute bottom-0 -right-24 w-24 h-px bg-gradient-to-l from-transparent to-current text-secondary-4"
- ),
- rx.el.div(
- class_name="absolute right-0 -top-24 h-24 w-px bg-gradient-to-b from-transparent to-current text-secondary-4"
- ),
- )
+ return rx.fragment()
def link_item(
@@ -31,7 +18,7 @@ def link_item(
),
rx.el.span(
title,
- class_name="text-secondary-12 text-xl font-[575] group-hover:text-primary-10 group-hover:dark:text-secondary-11",
+ class_name="text-secondary-12 text-xl font-book tracking-tight group-hover:text-primary-10 group-hover:dark:text-secondary-11",
),
ui.icon(
"ArrowRight01Icon",
@@ -41,15 +28,15 @@ def link_item(
),
rx.el.span(
description,
- class_name="text-secondary-11 text-sm font-[475] text-start",
+ class_name="text-secondary-11 text-sm font-normal text-start",
),
rx.el.a(
to=href,
aria_label=title,
- class_name="absolute inset-0 rounded-xl focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary-9",
+ class_name="absolute inset-0 rounded-compact focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary-9",
),
class_name=ui.cn(
- "flex flex-col gap-2 pr-8 py-8 group border-r border-b border-secondary-4 relative max-lg:p-6 hover:bg-[linear-gradient(243deg,var(--secondary-2)_0%,var(--secondary-1)_100%)]",
+ "flex flex-col gap-2 pr-8 py-8 group border-b border-border-subtle relative max-lg:p-6 hover:bg-accent transition-colors motion-reduce:transition-none",
"lg:pl-8 pl-6" if has_padding_left else "",
),
)
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/mcp_illustration.svg b/docs/app/reflex_docs/pages/docs_landing/views/mcp_illustration.svg
new file mode 100644
index 00000000000..e330d958f5b
--- /dev/null
+++ b/docs/app/reflex_docs/pages/docs_landing/views/mcp_illustration.svg
@@ -0,0 +1,26 @@
+
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/other.py b/docs/app/reflex_docs/pages/docs_landing/views/other.py
index f3945d82c7d..b91c5d5a618 100644
--- a/docs/app/reflex_docs/pages/docs_landing/views/other.py
+++ b/docs/app/reflex_docs/pages/docs_landing/views/other.py
@@ -10,11 +10,11 @@ def other_section() -> rx.Component:
rx.el.div(
rx.el.h2(
"Other",
- class_name="text-secondary-12 text-3xl font-[575]",
+ class_name="text-secondary-12 text-3xl font-book tracking-tight",
),
rx.el.p(
"Learn about other features and tools that Reflex offers.",
- class_name="text-secondary-11 text-sm font-[475]",
+ class_name="text-secondary-11 text-sm font-normal",
),
class_name="flex flex-col gap-4",
),
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/reflex_mark.svg b/docs/app/reflex_docs/pages/docs_landing/views/reflex_mark.svg
new file mode 100644
index 00000000000..cbecfd7ce7b
--- /dev/null
+++ b/docs/app/reflex_docs/pages/docs_landing/views/reflex_mark.svg
@@ -0,0 +1,11 @@
+
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/self_hosting.py b/docs/app/reflex_docs/pages/docs_landing/views/self_hosting.py
index 6f94f974f6e..e6b64ad180e 100644
--- a/docs/app/reflex_docs/pages/docs_landing/views/self_hosting.py
+++ b/docs/app/reflex_docs/pages/docs_landing/views/self_hosting.py
@@ -9,11 +9,11 @@ def self_hosting_section() -> rx.Component:
rx.el.div(
rx.el.h2(
"Self-Hosting",
- class_name="text-secondary-12 text-3xl font-[575]",
+ class_name="text-secondary-12 text-3xl font-book tracking-tight",
),
rx.el.p(
"Learn how to self-host your applications with Reflex.",
- class_name="text-secondary-11 text-sm font-[475]",
+ class_name="text-secondary-11 text-sm font-normal",
),
class_name="flex flex-col gap-4",
),
diff --git a/docs/app/reflex_docs/pages/docs_landing/views/squares_docs_logo.svg b/docs/app/reflex_docs/pages/docs_landing/views/squares_docs_logo.svg
new file mode 100644
index 00000000000..551cbce90c3
--- /dev/null
+++ b/docs/app/reflex_docs/pages/docs_landing/views/squares_docs_logo.svg
@@ -0,0 +1,343 @@
+
diff --git a/docs/app/reflex_docs/pages/integrations/integration_gallery.py b/docs/app/reflex_docs/pages/integrations/integration_gallery.py
index 8bf4c41c2c6..e2732f8b2d5 100644
--- a/docs/app/reflex_docs/pages/integrations/integration_gallery.py
+++ b/docs/app/reflex_docs/pages/integrations/integration_gallery.py
@@ -1,6 +1,7 @@
import reflex as rx
import reflex_components_internal as ui
from reflex.experimental import ClientStateVar
+from reflex_site_shared.components.marketing_button import button
from reflex_site_shared.integrations import get_integration_logo_url
from .integration_list import get_integration_path
@@ -19,16 +20,13 @@
def integration_filter_button(data: dict):
- active_pill = (
- "border border-primary-8 bg-primary-3 hover:bg-primary-3 !text-primary-10"
- )
-
- return ui.button(
+ active = selected_filter.value == data["name"]
+ return button(
ui.icon(icon=data["icon"]),
- rx.el.p(data["name"], class_name="text-sm"),
- variant="outline",
- class_name="flex flex-row items-center "
- + rx.cond(selected_filter.value == data["name"], active_pill, "").to(str),
+ data["name"],
+ variant=rx.cond(active, "primary", "outline"),
+ size="sm",
+ aria_pressed=active,
on_click=selected_filter.set_value(data["name"]),
)
@@ -37,9 +35,9 @@ def integration_filters():
return rx.el.div(
rx.el.div(
*[integration_filter_button(data) for data in FilterOptions],
- class_name="flex flex-row gap-3 items-center justify-center flex-wrap",
+ class_name="flex flex-row gap-2 items-center flex-wrap",
),
- class_name="w-full max-w-[64.19rem] pb-12",
+ class_name="w-full pb-8",
)
@@ -66,30 +64,25 @@ def integration_gallery_cards(data):
unstyled=True,
class_name="size-8 flex items-center justify-center",
),
- ui.link(
- render_=ui.button(
- "Learn More",
- variant="outline",
- class_name="group-hover:bg-secondary-2 hover:bg-transparent",
- ),
- to=data["path"],
+ rx.el.span(
+ "Learn more",
+ rx.icon("arrow-up-right", size=14, aria_hidden=True),
+ class_name="flex items-center gap-1 text-sm font-book text-muted-foreground group-hover:text-foreground",
),
class_name="w-full flex flex-row items-center justify-between",
),
rx.el.div(
- rx.el.p(
- data["title"], class_name="text-lg font-semibold text-secondary-12"
- ),
+ rx.el.p(data["title"], class_name="text-lg font-book text-foreground"),
rx.el.p(
data["description"],
- class_name="font-medium text-secondary-11 leading-[1.35]",
+ class_name="text-sm font-normal text-muted-foreground leading-6",
),
class_name="flex flex-col gap-y-1",
),
- class_name="flex flex-col gap-y-6 rounded-ui-xl border border-secondary-a4 bg-secondary-1 shadow-small p-6 h-[13rem] justify-between hover:bg-secondary-2",
+ class_name="flex flex-col gap-6 rounded-card border border-border-subtle bg-background p-5 min-h-48 h-full justify-between group-hover:bg-muted",
),
href=data["path"],
- class_name="group text-inherit hover:!text-inherit decoration-none no-underline "
+ class_name="docs-integration-card group rounded-card text-inherit hover:!text-inherit no-underline focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring "
+ rx.cond(
(selected_filter.value == data["tags"]) | (selected_filter.value == "All"),
"flex",
@@ -113,10 +106,6 @@ def integration_gallery():
def integration_request_form():
return rx.el.div(
rx.el.p("Missing an integration?"),
- rx.el.p(
- rx.fragment(
- "Click ", request_integration_dialog(), " to tell us what you need."
- )
- ),
- class_name="w-full max-w-[64.19rem] flex flex-col gap-y-1 text-md font-semibold py-10 items-center justify-center",
+ request_integration_dialog(),
+ class_name="w-full flex flex-wrap items-center justify-between gap-4 border-t border-border-subtle py-8 mt-10 text-sm font-normal text-muted-foreground",
)
diff --git a/docs/app/reflex_docs/pages/integrations/integration_header.py b/docs/app/reflex_docs/pages/integrations/integration_header.py
index c4aa997effb..dbf7dc067ed 100644
--- a/docs/app/reflex_docs/pages/integrations/integration_header.py
+++ b/docs/app/reflex_docs/pages/integrations/integration_header.py
@@ -5,12 +5,12 @@ def integration_header() -> rx.Component:
return rx.el.section(
rx.el.h1(
"Integrations",
- class_name="max-w-full inline-block bg-clip-text bg-gradient-to-r from-secondary-12 to-secondary-11 w-full font-xx-large text-center text-transparent text-balance mx-auto break-words",
+ class_name="w-full text-4xl sm:text-5xl font-book tracking-tight text-foreground text-balance",
),
- rx.el.h2(
+ rx.el.p(
"""Easily connect with the tools your team already uses
or extend your app with any Python SDK, library, or API.""",
- class_name="max-w-full w-full font-semibold text-md text-center text-secondary-11 -mt-2 md:text-2xl mx-auto text-balance word-wrap break-words md:whitespace-pre",
+ class_name="max-w-2xl text-base font-normal leading-7 text-muted-foreground text-balance",
),
- class_name="flex flex-col justify-center items-center gap-4 mx-auto w-full max-w-[64.19rem] pb-[2.5rem] pt-12",
+ class_name="flex flex-col items-start gap-4 w-full pb-8",
)
diff --git a/docs/app/reflex_docs/pages/integrations/integration_list.py b/docs/app/reflex_docs/pages/integrations/integration_list.py
index 128dfc7297b..b5237e8ca4c 100644
--- a/docs/app/reflex_docs/pages/integrations/integration_list.py
+++ b/docs/app/reflex_docs/pages/integrations/integration_list.py
@@ -49,7 +49,7 @@ def get_integration_path() -> list:
result.append({
key: {
- "path": f"{web_path_prefix}/{slug}",
+ "path": f"{web_path_prefix}/{slug}/",
"tags": tag,
"description": description,
"name": key,
diff --git a/docs/app/reflex_docs/pages/integrations/integration_request.py b/docs/app/reflex_docs/pages/integrations/integration_request.py
index 53e1cc52027..edacef3d027 100644
--- a/docs/app/reflex_docs/pages/integrations/integration_request.py
+++ b/docs/app/reflex_docs/pages/integrations/integration_request.py
@@ -1,5 +1,6 @@
import reflex as rx
import reflex_components_internal as ui
+from reflex_site_shared.components.marketing_button import button
from reflex_docs.templates.docpage.feedback_state import FeedbackState
@@ -8,9 +9,11 @@ def request_integration_dialog() -> rx.Component:
return ui.dialog(
title="Request Integration",
description="Let us know what integration you'd like to see added.",
- trigger=rx.el.strong(
- rx.el.u("here"),
- class_name="cursor-pointer text-primary-11 decoration-primary-9",
+ trigger=button(
+ "Request integration",
+ variant="outline",
+ size="sm",
+ native_button=False,
),
content=rx.el.form(
ui.textarea(
@@ -21,10 +24,11 @@ def request_integration_dialog() -> rx.Component:
max_length=2000,
class_name="h-[6rem]",
),
- ui.button(
+ button(
"Submit",
variant="primary",
size="md",
+ type="submit",
),
on_submit=[
rx.run_script(
@@ -35,4 +39,5 @@ def request_integration_dialog() -> rx.Component:
class_name="flex flex-col gap-4 w-full",
reset_on_submit=True,
),
+ class_name="docs-integration-dialog rounded-panel border-border [&_[data-slot=dialog-title]]:font-book [&_[data-slot=dialog-title]]:tracking-tight",
)
diff --git a/docs/app/reflex_docs/pages/library_previews.py b/docs/app/reflex_docs/pages/library_previews.py
index ed8fae173cf..b6efb3d958b 100644
--- a/docs/app/reflex_docs/pages/library_previews.py
+++ b/docs/app/reflex_docs/pages/library_previews.py
@@ -2,6 +2,7 @@
from reflex.utils.format import to_snake_case, to_title_case
from reflex_site_shared.constants import REFLEX_ASSETS_CDN
+from reflex_docs.docgen_pipeline import render_markdown
from reflex_docs.pages.docs.metadata import truncate_meta_description
from reflex_docs.templates.docpage import docpage, h1_comp, text_comp_2
@@ -55,16 +56,24 @@ def get_preview_asset(name: str, section: str) -> tuple[str, str]:
def component_card(name: str, link: str, section: str) -> rx.Component:
preview_section, preview_name = get_preview_asset(name, section)
+
+ def preview_src(mode: str) -> str:
+ """Resolve the bundled Treemap preview or an existing CDN preview."""
+ path = f"components_previews/{preview_section}/{mode}/{preview_name}.svg"
+ if (preview_section, preview_name) == ("charts", "treemap"):
+ return rx.asset(path)
+ return f"{REFLEX_ASSETS_CDN}{path}"
+
return rx.link(
rx.box(
rx.image(
- src=f"{REFLEX_ASSETS_CDN}components_previews/{preview_section}/light/{preview_name}.svg",
+ src=preview_src("light"),
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/{preview_section}/dark/{preview_name}.svg",
+ src=preview_src("dark"),
loading="lazy",
alt=f"Image preview of {name}",
class_name="object-contain object-center h-full w-full dark:block hidden",
@@ -72,14 +81,15 @@ def component_card(name: str, link: str, section: str) -> rx.Component:
rx.box(
rx.text(
get_display_name(name),
- class_name="truncate font-base text-secondary-12",
+ class_name="truncate text-sm font-book text-foreground",
),
rx.icon("chevron-right", size=14, class_name="!text-secondary-9"),
class_name="bottom-0 absolute flex flex-row justify-between w-full px-4 py-2 items-center",
),
- class_name="rounded-xl border overflow-hidden relative box-border shadow-large bg-secondary-2 hover:bg-secondary-3 transition-bg border-secondary-5",
+ class_name="aspect-[320/232] rounded-card border overflow-hidden relative box-border bg-background group-hover:bg-muted transition-colors border-border-subtle",
),
href=link,
+ class_name="docs-preview-card group rounded-card focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
)
@@ -115,6 +125,31 @@ def page() -> rx.Component:
)
component_list = get_component_list(type)
+ if component_category == "State":
+ return rx.box(
+ h1_comp(text="State"),
+ render_markdown(
+ "Reflex state connects your components to application data. "
+ "Store values in an `rx.State` class, pass its vars to component "
+ "props, and update those values from event handlers.\n\n"
+ "## Start with state and events\n\n"
+ "- [State overview](/docs/state/overview/): define application "
+ "state and connect it to your interface.\n"
+ "- [Base vars](/docs/vars/base-vars/): store typed values that "
+ "components can display.\n"
+ "- [Computed vars](/docs/vars/computed-vars/): derive values "
+ "from existing state.\n"
+ "- [Events](/docs/events/events-overview/): update state in "
+ "response to user interactions.\n\n"
+ "## Build reusable interactive components\n\n"
+ "Use [component state](/docs/state-structure/component-state/) "
+ "when each instance needs its own data. Use "
+ "[conditional rendering](/docs/components/conditional-rendering/) "
+ "and [rendering iterables](/docs/components/rendering-iterables/) "
+ "to make your interface respond to state changes."
+ ),
+ class_name="flex flex-col w-full mb-10",
+ )
return rx.box(
rx.box(
h1_comp(text=get_display_name(component_category)),
@@ -209,7 +244,7 @@ def page() -> rx.Component:
},
"state": {
"path": "state",
- "description": "Components that help with state, such as state variables, state hooks, and state management. These are useful for creating responsive and interactive user interfaces.",
+ "description": "Connect Reflex components to Python state, typed vars, computed values, and event handlers. Find guides for reusable interactive components and dynamic rendering.",
"component_category": "State",
},
}
diff --git a/docs/app/reflex_docs/pages/page404.py b/docs/app/reflex_docs/pages/page404.py
new file mode 100644
index 00000000000..7810a7dece4
--- /dev/null
+++ b/docs/app/reflex_docs/pages/page404.py
@@ -0,0 +1,54 @@
+"""Missing-page view using the same shell as the documentation landing page."""
+
+import reflex as rx
+from reflex_site_shared.components.marketing_button import button
+from reflex_site_shared.route import Route
+
+
+def not_found() -> rx.Component:
+ """Render a recovery path inside the current documentation design.
+
+ Returns:
+ Docs navigation, missing-page message, and shared landing footer.
+ """
+ # These views depend on the page registry, so load them after registration.
+ from reflex_docs.views.docs_navbar import docs_navbar
+ from reflex_docs.views.editorial_footer import editorial_footer
+
+ return rx.el.div(
+ docs_navbar(),
+ rx.el.main(
+ rx.el.div(
+ rx.el.p("404", class_name="text-sm font-book text-muted-foreground"),
+ rx.el.h1(
+ "Page not found",
+ class_name="text-4xl sm:text-5xl font-book tracking-tight text-foreground",
+ ),
+ rx.el.p(
+ "We couldn't find that documentation page. Browse the docs or use search to find what you need.",
+ class_name="max-w-md text-base leading-7 text-muted-foreground",
+ ),
+ rx.el.elements.a(
+ button(
+ "Back to docs",
+ variant="primary",
+ size="md",
+ native_button=False,
+ ),
+ href="/docs/",
+ class_name="rounded-full focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
+ ),
+ class_name="flex min-h-[65vh] flex-col items-start justify-center gap-6 mx-auto max-w-(--landing-layout-max-width) px-6 pb-16 pt-[calc(var(--docs-header-height)+4rem)]",
+ ),
+ editorial_footer(),
+ ),
+ class_name="min-h-screen bg-background",
+ )
+
+
+page404 = Route(
+ path="/404",
+ title="Page Not Found ยท Reflex Docs",
+ component=not_found,
+ add_as_page=False,
+)
diff --git a/docs/app/reflex_docs/reflex_docs.py b/docs/app/reflex_docs/reflex_docs.py
index b58f3adac72..58d8fb5f2f9 100644
--- a/docs/app/reflex_docs/reflex_docs.py
+++ b/docs/app/reflex_docs/reflex_docs.py
@@ -35,9 +35,11 @@ def _llms_txt_directive() -> rx.Component:
rx.el.span("For AI agents: the complete documentation index is at "),
rx.el.a("llms.txt", href=LLMS_TXT_PATH),
rx.el.span(
- ". Markdown versions are available by appending .md or sending "
- "Accept: text/markdown."
+ ". Remove the trailing slash from a page URL and append .md "
+ "to read its Markdown version. For the docs home, use "
),
+ rx.el.a("index.md", href="/index.md"),
+ ".",
class_name="sr-only",
)
@@ -52,7 +54,10 @@ def _llms_txt_directive() -> rx.Component:
radius="large",
accent_color="violet",
),
- head_components=favicons_links(),
+ head_components=[
+ *favicons_links(),
+ rx.el.link(rel="icon", type="image/svg+xml", href=rx.asset("favicon.svg")),
+ ],
)
app.register_lifespan_task(monitor_checkly_status)
@@ -140,6 +145,15 @@ def _canonical_url(path: str) -> str:
canonical = None
meta = list(route.meta) if route.meta is not None else []
if canonical is not None:
+ meta.append(
+ rx.el.link(
+ rel="alternate",
+ type="text/markdown",
+ href=canonical + "index.md"
+ if route.path.strip("/") == ""
+ else canonical.rstrip("/") + ".md",
+ )
+ )
meta.append(
rx.el.script(
json.dumps(
@@ -235,7 +249,7 @@ def _redirect_page(target: str):
],
)
-app.add_page(page404.component, route=page404.path)
+app.add_page(page404.component, route=page404.path, title=page404.title)
# HTTP 301 applies when page requests reach this backend. Separate frontend
diff --git a/docs/app/reflex_docs/templates/docpage/docpage.py b/docs/app/reflex_docs/templates/docpage/docpage.py
index 4de61d1528e..56c805f20fe 100644
--- a/docs/app/reflex_docs/templates/docpage/docpage.py
+++ b/docs/app/reflex_docs/templates/docpage/docpage.py
@@ -250,7 +250,7 @@ def breadcrumb(path: str, nav_sidebar: rx.Component, doc_content: str | None = N
label = to_title_case(to_snake_case(segment), sep=" ")
label = _BREADCRUMB_LABEL_OVERRIDES.get(label, label)
base_class = ui.cn(
- "min-h-8 flex items-center text-sm font-[525] text-secondary-12 last:text-secondary-11",
+ "min-h-8 flex items-center text-sm font-book text-foreground last:text-muted-foreground",
"truncate" if i == len(segments) - 1 else "",
)
@@ -265,7 +265,7 @@ def breadcrumb(path: str, nav_sidebar: rx.Component, doc_content: str | None = N
label,
class_name=ui.cn(
base_class,
- "hover:text-primary-10 dark:hover:text-primary-9",
+ "hover:text-muted-foreground rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
),
underline="none",
href=href,
@@ -318,7 +318,7 @@ def breadcrumb(path: str, nav_sidebar: rx.Component, doc_content: str | None = N
class_name="flex flex-row items-center gap-2 lg:p-0 p-[0.563rem]",
),
class_name=ui.cn(
- "relative z-10 flex flex-row justify-between items-center gap-4 lg:gap-0 border-secondary-4 mt-[139px] lg:p-0 border-b lg:border-none w-full max-lg:py-2",
+ "docs-breadcrumb relative z-10 flex flex-row justify-between items-center gap-4 lg:gap-0 border-secondary-4 mt-[139px] lg:p-0 border-b lg:border-none w-full max-lg:py-2",
rx.cond(
HostingBannerState.is_banner_visible,
"lg:mt-[139px]",
@@ -418,10 +418,11 @@ def wrapper(*args, **kwargs) -> rx.Component:
),
underline="none",
href=prev.link,
- class_name="py-0.5 lg:py-0 rounded-lg lg:w-auto font-small text-secondary-9 hover:!text-secondary-11 transition-color",
+ class_name="py-0.5 rounded-sm text-sm font-normal text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
),
rx.text(
- next_prev_name, class_name="font-smbold text-secondary-12"
+ next_prev_name,
+ class_name="text-base font-book tracking-tight text-foreground",
),
class_name="flex flex-col justify-start gap-1",
)
@@ -442,10 +443,11 @@ def wrapper(*args, **kwargs) -> rx.Component:
),
underline="none",
href=next.link,
- class_name="py-0.5 lg:py-0 rounded-lg lg:w-auto font-small text-secondary-9 hover:!text-secondary-11 transition-color",
+ class_name="py-0.5 rounded-sm text-sm font-normal text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
),
rx.text(
- next_prev_name, class_name="font-smbold text-secondary-12"
+ next_prev_name,
+ class_name="text-base font-book tracking-tight text-foreground",
),
class_name="flex flex-col justify-start gap-1 items-end",
)
@@ -492,10 +494,11 @@ def wrapper(*args, **kwargs) -> rx.Component:
),
),
rx.box(
- rx.el.article(comp, class_name="[&>div]:!p-0"),
+ rx.el.article(comp, class_name="docs-prose [&>div]:!p-0"),
rx.el.nav(
*links,
- class_name="flex flex-row gap-2 mt-8 lg:mt-10 mb-6 lg:mb-12",
+ aria_label="Previous and next pages",
+ class_name="docs-page-navigation flex flex-row gap-2 mt-8 lg:mt-10 mb-8",
),
docpage_footer(path=path.rstrip("/"), edit_href=edit_href),
class_name="lg:mt-0 h-auto",
@@ -554,10 +557,10 @@ def hover_item(component: rx.Component, component_str: str) -> rx.Component:
get_icon(icon="copy", class_name="p-[5px]"),
rx.text(
component_str,
- class_name="flex-1 font-small truncate",
+ class_name="flex-1 text-sm font-book truncate",
),
on_click=rx.set_clipboard(component_str),
- class_name="flex flex-row items-center gap-1.5 border-secondary-5 bg-secondary-1 hover:bg-secondary-3 shadow-small pr-1.5 border rounded-md w-full max-w-[300px] text-secondary-11 transition-bg cursor-pointer",
+ class_name="flex flex-row items-center gap-1.5 border-border bg-background hover:bg-muted pr-3 border rounded-compact min-h-9 w-full max-w-[300px] text-muted-foreground transition-colors cursor-pointer focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
),
),
)
@@ -780,5 +783,6 @@ def style_grid(
),
),
),
- class_name="flex flex-col justify-center items-center gap-6 border-secondary-4 bg-secondary-2 mb-4 p-6 border rounded-xl",
+ class_name="flex flex-col justify-center items-center gap-6 border-border-subtle bg-muted mb-4 p-6 border rounded-card",
+ data_docs_example=True,
)
diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py
index 22f9459de8d..37cf44bad3e 100644
--- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py
+++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar.py
@@ -31,7 +31,6 @@
from .state import SideBarBase, SideBarItem
SIDEBAR_ICON_MAP = {
- "Getting Started": "rocket",
"Tutorials": "graduation-cap",
"Advanced Onboarding": "newspaper",
"Components": "layers",
@@ -347,6 +346,7 @@ def sidebar_comp(
is_docs_hosting = url.startswith("/hosting/")
is_docs_ai_builder = url.startswith("/ai/")
+ is_ai_overview = url == "/ai/"
is_ai_mcp_or_skills = (
url.startswith("/ai/integrations/agent-toolkit/")
| url.startswith("/ai/integrations/skills/")
@@ -381,10 +381,16 @@ def sidebar_comp(
ai_builder_categories = rx.el.ul(
sidebar_category(
- "AI Builder",
- ai_builder_pages.overview.best_practices.path,
+ "Overview",
+ "/ai/",
+ "sparkles",
+ is_ai_overview,
+ ),
+ sidebar_category(
+ "Reflex Build",
+ ai_builder_pages.overview.what_is_reflex_build.path,
"bot",
- ~is_ai_mcp_or_skills,
+ ~is_ai_mcp_or_skills & ~is_ai_overview,
),
sidebar_category(
"Agent Toolkit",
@@ -422,7 +428,7 @@ def sidebar_comp(
)
ai_builder_overview_content = rx.el.ul(
create_sidebar_section(
- "Overview",
+ "Guides",
ai_builder_pages.overview.best_practices.path,
ai_builder_overview_items,
ai_builder_overview_index,
@@ -498,19 +504,19 @@ def sidebar_comp(
rx.icon("atom", size=16), # pyright: ignore [reportCallIssue]
rx.el.h5(
"Custom Components",
- class_name="font-smbold text-[0.875rem] text-secondary-12 leading-5 tracking-[-0.01313rem] transition-color",
+ class_name="text-sm font-book leading-5 tracking-tight text-foreground transition-colors",
),
class_name="flex flex-row items-center gap-3 text-secondary-12",
),
rx.text( # pyright: ignore [reportCallIssue]
"See what components people have made with Reflex!",
- class_name="font-small text-secondary-11",
+ class_name="text-sm font-normal leading-5 text-muted-foreground",
),
- class_name="flex flex-col gap-2 border-secondary-5 bg-secondary-1 hover:bg-secondary-3 shadow-large px-3.5 py-2 border rounded-xl transition-bg",
+ class_name="flex flex-col gap-2 border-border-subtle bg-muted hover:border-border-strong px-3.5 py-3 border rounded-card transition-colors",
),
underline="none",
href=custom_components.path,
- class_name="w-fit lg:ml-[2.5rem]",
+ class_name="w-fit rounded-card lg:ml-[2.5rem] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
),
class_name="m-0 p-0 flex flex-col items-start gap-8 w-full list-none list-style-none",
)
diff --git a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py
index 049bf502293..904713f5271 100644
--- a/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py
+++ b/docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py
@@ -7,7 +7,7 @@ def get_sidebar_items_ai_builder_overview():
return [
create_item(
- "Overview",
+ "Getting Started",
children=[
ai_builder.overview.best_practices,
ai_builder.overview.what_is_reflex_build,
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 ba4090e25a6..526e789c2d6 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
@@ -40,7 +40,7 @@ def sort_html_components(components: list) -> list:
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.
- return f"/library/{prefix.strip('/') + '/' if prefix.strip('/') else ''}{category.lower().replace(' ', '-')}/{component_name.lower()}"
+ return f"/library/{prefix.strip('/') + '/' if prefix.strip('/') else ''}{category.lower().replace(' ', '-')}/{component_name.lower()}/"
def get_category_children(category, category_list, prefix=""):
diff --git a/docs/app/reflex_docs/views/docs_navbar.py b/docs/app/reflex_docs/views/docs_navbar.py
index aea1fe95652..9c3fb7be981 100644
--- a/docs/app/reflex_docs/views/docs_navbar.py
+++ b/docs/app/reflex_docs/views/docs_navbar.py
@@ -1,6 +1,9 @@
import reflex as rx
import reflex_components_internal as ui
-from reflex_components_internal.blocks.demo_form import demo_form_dialog
+from reflex_components_internal.blocks.demo_form import (
+ demo_form_dialog,
+ demo_form_open_cs,
+)
from reflex_site_shared.components.docs_shell import docs_navbar_frame
from reflex_site_shared.components.icons import get_icon
from reflex_site_shared.components.marketing_button import button
@@ -8,34 +11,34 @@
GITHUB_STARS,
GITHUB_URL,
REFLEX_ASSETS_CDN,
- REFLEX_URL,
+ XY_GITHUB_STARS,
+)
+from reflex_site_shared.views.hosting_banner import (
+ AGENT_TOOLKIT_EARLY_ACCESS_URL,
+ HostingBannerState,
)
-from reflex_docs.components.docpage.navbar.buttons.sidebar import navbar_sidebar_button
-from reflex_docs.pages.docs import ai_builder, getting_started, hosting
+from reflex_docs.pages.docs import getting_started, hosting
from reflex_docs.views.search import search_bar
def github_button() -> rx.Component:
- label = f"View Reflex on GitHub - {GITHUB_STARS // 1000}K stars"
+ stars = f"{(GITHUB_STARS + XY_GITHUB_STARS) / 1000:.0f}K"
+ label = f"View Reflex on GitHub - {stars} combined stars for Reflex and Reflex XY"
return rx.el.elements.a(
- button(
- get_icon(icon="github_navbar", class_name="shrink-0"),
- f"{GITHUB_STARS // 1000}K",
- custom_attrs={"aria-label": label},
- size="sm",
- variant="ghost",
- native_button=False,
- ),
+ get_icon(icon="github_navbar", class_name="size-4 shrink-0"),
+ stars,
href=GITHUB_URL,
target="_blank",
rel="noopener noreferrer",
- custom_attrs={"aria-label": label},
+ aria_label=label,
+ title=f"Reflex + Reflex XY: {stars} combined GitHub stars",
+ class_name="inline-flex h-9 items-center gap-2 rounded-full text-sm font-book text-foreground transition-colors hover:text-muted-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
)
def logo() -> rx.Component:
- return rx.el.elements.a(
+ return rx.el.a(
rx.el.div(
rx.image(
src=f"{REFLEX_ASSETS_CDN}logos/light/reflex.svg",
@@ -60,8 +63,9 @@ def logo() -> rx.Component:
class_name="shrink-0 hidden dark:block",
),
),
- href=REFLEX_URL,
- class_name="flex flex-row gap-2.5 items-center shrink-0 mr-10",
+ href="/",
+ aria_label="Docs overview",
+ class_name="flex flex-row gap-2.5 items-center shrink-0 rounded-compact focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring",
)
@@ -69,117 +73,198 @@ def menu_item(
text: str, href: str, active_str: str = "", external: bool = False
) -> rx.Component:
router_path = rx.State.router.page.path
- active_cn = "shadow-[inset_0_-1px_0_0_var(--primary-10)] [&_button]:text-primary-10 [&_div]:text-primary-10"
+ # Router paths include the deployment prefix; navigation describes docs routes.
+ router_path = rx.cond(
+ (router_path == "/docs") | router_path.startswith("/docs/"),
+ router_path[5:],
+ router_path,
+ )
+ is_overview = (router_path == "") | (router_path == "/") | (router_path == "/index")
- # For paths starting with "/" (like Start), use exact match
- # For "framework", it's the default - active when in /docs but not matching other sections
- # For other segments (like "ai"), use contains
if active_str.startswith("/"):
- if active_str == "/":
- active = (router_path == "/") | (router_path == "/index")
- else:
- active = router_path == active_str
+ active = is_overview if active_str == "/" else router_path == active_str
elif active_str == "framework":
- is_overview = (router_path == "/") | (router_path == "/index")
- is_ai_builder = router_path.startswith("/ai/") | router_path.startswith(
- "/docs/ai/"
- )
- is_hosting = router_path.contains("hosting")
- is_xy = router_path.startswith("/xy/") | router_path.startswith("/docs/xy/")
+ is_ai_builder = (router_path == "/ai") | router_path.startswith("/ai/")
+ is_hosting = router_path.startswith("/hosting/")
+ is_xy = router_path.startswith("/xy/")
active = ~is_overview & ~is_ai_builder & ~is_hosting & ~is_xy
- elif active_str == "ai":
- active = router_path.startswith("/ai/") | router_path.startswith("/docs/ai/")
- elif active_str == "xy":
- active = router_path.startswith("/xy/") | router_path.startswith("/docs/xy/")
else:
- active = router_path.contains(active_str)
+ active = (router_path == f"/{active_str}") | router_path.startswith(
+ f"/{active_str}/"
+ )
anchor = rx.el.elements.a if external else rx.el.a
- return ui.navigation_menu.item(
+ return rx.el.li(
anchor(
+ text,
+ href=href,
+ aria_current=rx.cond(active, "page", "false"),
+ class_name="docs-navbar-link inline-flex h-9 items-center justify-center rounded-full px-3 text-sm font-book text-muted-foreground transition-colors hover:text-foreground aria-[current=page]:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
+ ),
+ class_name="flex items-center",
+ )
+
+
+def mobile_navigation() -> rx.Component:
+ """Show the same docs destinations as the desktop header.
+
+ Returns:
+ Mobile navigation drawer with native links and the shared booking action.
+ """
+ return rx.drawer.root(
+ rx.drawer.trigger(
button(
- text,
- size="sm",
+ rx.icon("menu", size=18),
variant="ghost",
- native_button=False,
+ size="icon-sm",
+ aria_label="Open sidebar",
),
- href=href,
+ as_child=True,
),
- class_name=ui.cn(
- "md:flex hidden h-full items-center justify-center",
- rx.cond(active, active_cn, ""),
+ rx.drawer.portal(
+ rx.drawer.content(
+ rx.el.div(
+ rx.drawer.title(
+ "Documentation",
+ class_name="text-xs font-book text-muted-foreground",
+ font_size="0.75rem",
+ font_weight="450",
+ line_height="1.5",
+ letter_spacing="normal",
+ ),
+ rx.drawer.close(
+ button(
+ rx.icon("x", size=18),
+ variant="ghost",
+ size="icon-sm",
+ aria_label="Close navigation",
+ ),
+ as_child=True,
+ ),
+ class_name="flex items-center justify-between gap-4",
+ ),
+ rx.el.nav(
+ *[
+ rx.drawer.close(
+ (rx.el.elements.a if external else rx.el.a)(
+ label,
+ rx.icon(
+ "arrow-up-right" if external else "arrow-right",
+ size=16,
+ aria_hidden=True,
+ ),
+ href=href,
+ class_name="flex min-h-14 items-center justify-between gap-4 border-b border-border-subtle text-base font-book text-foreground focus-visible:outline-2 focus-visible:outline-ring",
+ ),
+ as_child=True,
+ )
+ for label, href, external in (
+ ("Overview", "/", False),
+ ("Build with AI", "/ai/", False),
+ ("Framework", getting_started.introduction.path, False),
+ ("Cloud", hosting.deploy_quick_start.path, False),
+ ("XY", "/docs/xy/", True),
+ )
+ ],
+ aria_label="Documentation navigation",
+ class_name="flex flex-col",
+ ),
+ rx.drawer.close(
+ button(
+ "Book a Demo",
+ variant="primary",
+ size="md",
+ on_click=rx.call_function(demo_form_open_cs.set_value(True)),
+ class_name="w-full mt-6",
+ ),
+ as_child=True,
+ ),
+ class_name="docs-mobile-menu fixed inset-x-0 bottom-0 top-(--docs-header-height) flex flex-col gap-4 overflow-y-auto border-t border-border-subtle bg-background p-6 outline-none",
+ top="var(--docs-header-height)",
+ z_index=10000,
+ ),
),
- custom_attrs={"role": "menuitem"},
+ direction="bottom",
)
def navigation_menu() -> rx.Component:
- return ui.navigation_menu.root(
- ui.navigation_menu.list(
- menu_item("Overview", "/", "/"),
- menu_item("Build with AI", ai_builder.overview.best_practices.path, "ai"),
- menu_item("Framework", getting_started.introduction.path, "framework"),
- menu_item("Cloud", hosting.deploy_quick_start.path, "hosting"),
- menu_item("XY", "/docs/xy/", "xy", external=True),
- class_name="flex flex-row items-center gap-2 m-0 h-full list-none",
- custom_attrs={"role": "menubar"},
- ),
- ui.navigation_menu.list(
- ui.navigation_menu.item(
- github_button(),
- unstyled=True,
- class_name="md:flex hidden",
- custom_attrs={"role": "menuitem"},
+ """Pair quiet documentation links with compact marketing-style actions."""
+ return rx.el.div(
+ rx.el.nav(
+ rx.el.ul(
+ menu_item("Overview", "/", "/"),
+ menu_item("Build with AI", "/ai/", "ai"),
+ menu_item("Framework", getting_started.introduction.path, "framework"),
+ menu_item("Cloud", hosting.deploy_quick_start.path, "hosting"),
+ menu_item("XY", "/docs/xy/", "xy", external=True),
+ class_name="m-0 flex h-full list-none items-center p-0",
),
- ui.navigation_menu.item(
+ aria_label="Documentation navigation",
+ class_name="hidden h-full lg:flex",
+ ),
+ rx.el.div(
+ rx.el.div(
+ button(
+ ui.icon("Search01Icon", size=16, aria_hidden=True),
+ variant="ghost",
+ size="icon-sm",
+ aria_label="Search (loading)",
+ aria_disabled=True,
+ aria_busy=True,
+ tab_index=-1,
+ class_name="group-has-[.ReflexSearch-root]/docs-search:hidden",
+ ),
search_bar(),
- unstyled=True,
- custom_attrs={"role": "menuitem"},
+ class_name="group/docs-search docs-navbar-search flex h-9 w-9 lg:w-40 shrink-0 items-center justify-center",
),
- ui.navigation_menu.item(
+ rx.el.div(github_button(), class_name="hidden xl:flex"),
+ rx.el.div(
demo_form_dialog(
+ id_prefix="docs-booking",
trigger=button(
"Book a Demo",
size="sm",
variant="primary",
- class_name=" whitespace-nowrap max-xl:hidden",
+ class_name="whitespace-nowrap text-sm",
native_button=False,
),
),
- unstyled=True,
- class_name="xl:flex hidden",
- custom_attrs={"role": "menuitem"},
+ class_name="hidden xl:flex",
),
- ui.navigation_menu.item(
- navbar_sidebar_button(),
- class_name="md:hidden flex",
- unstyled=True,
- custom_attrs={"role": "menuitem"},
- ),
- class_name="flex flex-row lg:gap-4 gap-2 m-0 h-full list-none items-center",
- custom_attrs={"role": "menubar"},
+ rx.el.div(mobile_navigation(), class_name="flex lg:hidden"),
+ class_name="ml-auto flex items-center gap-2 sm:gap-6",
),
- ui.navigation_menu.portal(
- ui.navigation_menu.positioner(
- ui.navigation_menu.popup(
- ui.navigation_menu.viewport(),
- unstyled=True,
- class_name="relative h-[var(--popup-height)] w-max origin-[var(--transform-origin)] transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0 min-[500px]:w-[var(--popup-width)] xs:w-[var(--popup-width)] rounded-xl bg-secondary-1 overflow-hidden",
- style={
- "box-shadow": "0 0 0 1px rgba(0, 0, 0, 0.03), 0 -1px 1px 0 rgba(0, 0, 0, 0.04), 0 16px 32px 0 rgba(0, 0, 0, 0.08), 0 1px 1px 0 rgba(0, 0, 0, 0.08), 0 4px 8px 0 rgba(0, 0, 0, 0.03);",
- },
- ),
- side_offset=30,
- align="start",
- align_offset=-20,
- ),
- ),
- unstyled=True,
- class_name="relative flex w-full items-center h-full justify-between gap-6 mx-auto flex-row",
+ class_name="flex h-full min-w-0 flex-1 items-center justify-between gap-2",
)
@rx.memo
def docs_navbar() -> rx.Component:
- return docs_navbar_frame(logo(), navigation_menu())
+ """Render the editorial header with a dismissible announcement."""
+ return rx.fragment(
+ rx.cond(
+ HostingBannerState.is_banner_visible,
+ rx.el.aside(
+ rx.el.elements.a(
+ "Reflex Agent Toolkit is launching. Get early access",
+ href=AGENT_TOOLKIT_EARLY_ACCESS_URL,
+ class_name="text-center text-xs sm:text-sm font-medium rounded-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#ffffff]",
+ ),
+ rx.el.button(
+ ui.icon("MultiplicationSignIcon", size=16),
+ type="button",
+ aria_label="Close banner",
+ on_click=HostingBannerState.hide_banner,
+ class_name="absolute right-2 top-1/2 -translate-y-1/2 flex size-8 items-center justify-center rounded-full hover:bg-white/15 focus-visible:outline-2 focus-visible:outline-[#ffffff]",
+ ),
+ custom_attrs={"data-docs-announcement": ""},
+ class_name="fixed top-0 z-[10000] flex h-14 sm:h-10 w-full items-center justify-center bg-[#181818] text-[#ffffff] px-12",
+ ),
+ ),
+ rx.el.div(
+ docs_navbar_frame(logo(), navigation_menu(), show_banner=False),
+ class_name="[&_.docs-navbar]:top-[calc(var(--docs-header-height)-4rem)]",
+ ),
+ )
diff --git a/docs/app/reflex_docs/views/editorial_footer.py b/docs/app/reflex_docs/views/editorial_footer.py
new file mode 100644
index 00000000000..91a2b6c3380
--- /dev/null
+++ b/docs/app/reflex_docs/views/editorial_footer.py
@@ -0,0 +1,244 @@
+"""Open footer layout for the documentation landing page."""
+
+from datetime import datetime
+from typing import Literal
+
+import reflex as rx
+from reflex.style import color_mode, set_color_mode
+from reflex_site_shared.backend.signup import IndexState
+from reflex_site_shared.backend.status import StatusState
+from reflex_site_shared.components.icons import get_icon
+from reflex_site_shared.components.server_status import server_status
+from reflex_site_shared.constants import (
+ CHANGELOG_URL,
+ DISCORD_URL,
+ GITHUB_ORG_URL,
+ LINKEDIN_URL,
+ REFLEX_ASSETS_CDN,
+ REFLEX_BUILD_URL,
+ REFLEX_URL,
+ TWITTER_URL,
+)
+
+_FOOTER_LINKS = (
+ (
+ "Product",
+ (
+ ("AI Builder", REFLEX_BUILD_URL),
+ ("Agent Toolkit", "/docs/ai/integrations/agent-toolkit/"),
+ ("Enterprise", "/docs/enterprise/overview/"),
+ ("App Management", "/hosting/"),
+ ("Pricing", "/pricing/"),
+ ),
+ ),
+ (
+ "Solutions",
+ (
+ ("Enterprise", "/use-cases/"),
+ ("Finance", "/use-cases/finance/"),
+ ("Healthcare", "/use-cases/healthcare/"),
+ ("Consulting", "/use-cases/consulting/"),
+ ("Government", "/use-cases/government/"),
+ ),
+ ),
+ (
+ "Resources",
+ (
+ ("Blog", "/blog/"),
+ ("Templates", "/templates/"),
+ ("Integrations", "/docs/ai/integrations/overview/"),
+ ("FAQ", "/faq/"),
+ ),
+ ),
+ (
+ "Developers",
+ (
+ ("Documentation", "/docs/"),
+ ("Changelog", CHANGELOG_URL),
+ ("Common Errors", "/errors/"),
+ ),
+ ),
+ (
+ "Migration",
+ (
+ ("From No-Code", "/migration/no-code/"),
+ ("From Low-Code", "/migration/low-code/"),
+ ("From Other Frameworks", "/migration/other-frameworks/"),
+ ("From Other AI Tools", "/migration/other-ai-tools/"),
+ ),
+ ),
+ (
+ "Company",
+ (
+ ("About", "/about/"),
+ ("Careers", "https://www.ycombinator.com/companies/reflex/jobs"),
+ ("Privacy Policy", "https://build.reflex.dev/privacy-policy"),
+ ("Terms of Service", "https://build.reflex.dev/terms-of-use"),
+ ),
+ ),
+)
+
+_FOCUS = (
+ "focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-ring"
+)
+
+
+def _newsletter() -> rx.Component:
+ """Integrate a quiet, labeled signup into the footer's column grid."""
+ return rx.el.div(
+ rx.el.h2(
+ "Updates from Reflex",
+ class_name="text-base font-book tracking-tight text-foreground",
+ ),
+ rx.cond(
+ IndexState.signed_up,
+ rx.el.div(
+ rx.el.p("Thanks for subscribing!", role="status"),
+ rx.el.button(
+ "Use another email",
+ type="button",
+ on_click=IndexState.signup_for_another_user,
+ class_name=f"w-fit text-sm underline underline-offset-4 {_FOCUS}",
+ ),
+ class_name="mt-2 flex flex-col items-start gap-3 text-sm text-muted-foreground",
+ ),
+ rx.el.div(
+ rx.el.p(
+ "Product updates and new guides, in your inbox.",
+ id="docs-newsletter-description",
+ class_name="mt-2 text-sm leading-6 text-muted-foreground",
+ ),
+ rx.el.form(
+ rx.el.label(
+ "Email address",
+ html_for="docs-newsletter-email",
+ class_name="sr-only",
+ ),
+ rx.el.input(
+ id="docs-newsletter-email",
+ name="input_email",
+ type="email",
+ auto_complete="email",
+ aria_describedby="docs-newsletter-description",
+ placeholder="Your email address",
+ required=True,
+ class_name="h-11 w-0 min-w-0 flex-1 border-0 bg-transparent p-0 text-sm text-foreground outline-none placeholder:text-subtle-foreground focus-visible:outline-none",
+ ),
+ rx.el.button(
+ "Subscribe",
+ rx.icon("arrow-right", size=14, aria_hidden=True),
+ type="submit",
+ class_name=f"inline-flex shrink-0 items-center gap-2 rounded-sm py-3 text-sm font-book text-foreground transition-colors hover:text-muted-foreground {_FOCUS}",
+ ),
+ on_submit=IndexState.signup,
+ class_name="mt-3 flex items-center gap-4 border-b border-border transition-colors focus-within:border-foreground",
+ ),
+ ),
+ ),
+ class_name="docs-footer-newsletter w-full min-w-0 lg:col-span-2",
+ )
+
+
+def _theme_toggle() -> rx.Component:
+ """Offer the three existing color modes in a flat segmented control."""
+ modes: tuple[tuple[Literal["system", "light", "dark"], str], ...] = (
+ ("system", "computer_footer"),
+ ("light", "sun_footer"),
+ ("dark", "moon_footer"),
+ )
+ return rx.el.div(
+ *[
+ rx.el.button(
+ get_icon(icon, class_name="size-3.5"),
+ on_click=set_color_mode(mode),
+ type="button",
+ aria_label=f"Toggle {mode} color mode",
+ aria_pressed=color_mode == mode,
+ class_name=f"flex size-7 items-center justify-center rounded-compact transition-colors {_FOCUS}",
+ )
+ for mode, icon in modes
+ ],
+ role="group",
+ aria_label="Color mode",
+ class_name="docs-theme-toggle flex w-fit items-center gap-0.5",
+ )
+
+
+def _link_column(heading: str, links: tuple[tuple[str, str], ...]) -> rx.Component:
+ """Keep root-site destinations outside the docs router basename."""
+ return rx.el.nav(
+ rx.el.h2(heading, class_name="mb-3 text-xs font-book text-muted-foreground"),
+ *[
+ rx.el.elements.a(
+ text,
+ href=href,
+ target="_blank" if not href.startswith("/") else None,
+ rel="noopener noreferrer" if not href.startswith("/") else None,
+ class_name=f"w-fit rounded-sm text-sm font-book leading-6 text-foreground transition-colors hover:text-muted-foreground {_FOCUS}",
+ )
+ for text, href in links
+ ],
+ aria_label=f"{heading} footer links",
+ class_name="flex min-w-0 flex-col gap-2",
+ )
+
+
+def editorial_footer() -> rx.Component:
+ """Render the newsletter, open link directory, and compact utility row."""
+ return rx.el.footer(
+ rx.el.div(
+ rx.el.elements.a(
+ rx.image(
+ src=f"{REFLEX_ASSETS_CDN}logos/light/reflex.svg",
+ alt="Reflex home",
+ class_name="block h-5 w-auto dark:hidden",
+ ),
+ rx.image(
+ src=f"{REFLEX_ASSETS_CDN}logos/dark/reflex.svg",
+ alt="Reflex home",
+ class_name="hidden h-5 w-auto dark:block",
+ ),
+ href=REFLEX_URL,
+ class_name=f"w-fit rounded-sm lg:col-span-4 {_FOCUS}",
+ ),
+ _newsletter(),
+ class_name="grid grid-cols-1 items-start gap-8 pb-12 sm:grid-cols-2 lg:grid-cols-6",
+ ),
+ rx.el.div(
+ *[_link_column(heading, links) for heading, links in _FOOTER_LINKS],
+ class_name="grid grid-cols-2 gap-x-8 gap-y-10 md:grid-cols-3 lg:grid-cols-6",
+ ),
+ rx.el.div(
+ rx.el.div(
+ *[
+ rx.el.elements.a(
+ get_icon(icon, class_name="size-4 shrink-0"),
+ href=url,
+ aria_label=f"Social link for {name}",
+ title=f"{name} (opens in a new tab)",
+ target="_blank",
+ rel="noopener noreferrer",
+ class_name="docs-footer-social-link flex size-7 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-foreground",
+ )
+ for icon, url, name in (
+ ("twitter_footer", TWITTER_URL, "Twitter"),
+ ("github_navbar", GITHUB_ORG_URL, "GitHub"),
+ ("linkedin_footer", LINKEDIN_URL, "LinkedIn"),
+ ("discord_navbar", DISCORD_URL, "Discord"),
+ )
+ ],
+ class_name="flex flex-wrap items-center gap-3",
+ ),
+ rx.el.span(
+ f"Reflex ยฉ {datetime.now().year} Pynecone, Inc.",
+ class_name="text-xs font-normal text-muted-foreground",
+ ),
+ rx.el.div(
+ server_status(StatusState.status),
+ _theme_toggle(),
+ class_name="flex flex-wrap items-center gap-4 [&>a]:rounded-full [&>a]:focus-visible:outline-2 [&>a]:focus-visible:outline-ring [&>a>div]:text-xs [&>a>div]:font-normal",
+ ),
+ class_name="flex flex-col items-start justify-between gap-6 pt-16 lg:flex-row lg:flex-wrap lg:items-center",
+ ),
+ class_name="docs-footer mx-auto w-full max-w-(--landing-layout-max-width) border-t border-border-subtle px-6 pb-10 pt-12 text-foreground sm:pt-16 xl:px-0",
+ )
diff --git a/docs/app/rxconfig.py b/docs/app/rxconfig.py
index f5c884863bb..893de890942 100644
--- a/docs/app/rxconfig.py
+++ b/docs/app/rxconfig.py
@@ -5,6 +5,7 @@
config = rx.Config(
app_name="reflex_docs",
+ deploy_url="https://reflex.dev",
frontend_path="/docs",
show_built_with_reflex=False,
frontend_lazy_bundled_libraries=True,
@@ -17,7 +18,7 @@
telemetry_enabled=False,
plugins=[
rx.plugins.TailwindV4Plugin(),
- SharedSiteStylesPlugin(),
+ SharedSiteStylesPlugin(editorial=True),
rx.plugins.SitemapPlugin(trailing_slash="always"),
AgentFilesPlugin(),
],
diff --git a/docs/app/tests/frontend_quality.test.mjs b/docs/app/tests/frontend_quality.test.mjs
index 4d6ecbbd01b..cbeacd169a0 100644
--- a/docs/app/tests/frontend_quality.test.mjs
+++ b/docs/app/tests/frontend_quality.test.mjs
@@ -196,3 +196,36 @@ with RegistrationContext(), patch('reflex.compiler.compiler.get_config', return_
else globalThis.window = previousWindow;
}
});
+
+test("Editorial actions preserve native semantics and caller events", async () => {
+ const { createElement } = await import(resolve("react"));
+ const { renderToStaticMarkup } = await import(resolve("react-dom/server"));
+ const { transform } = await import(resolve("rolldown/utils"));
+ const filename = path.join(web, "public/components/GradientButton.tsx");
+ const source = await readFile(filename, "utf8");
+ const compiled = await transform(filename, source, {jsx: {runtime: "automatic"}});
+ const { GradientButton } = await import(moduleUrl(compiled.code
+ .replaceAll('from "react/jsx-runtime"', `from "${resolve("react/jsx-runtime")}"`)
+ .replaceAll('from "clsx-for-tailwind"', `from "${resolve("clsx-for-tailwind")}"`)));
+ const html = renderToStaticMarkup(createElement(GradientButton, {
+ type: "submit", disabled: true, name: "action", value: "send", form: "feedback",
+ }, "Send"));
+ assert.match(html, /^