Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions docs/ai_builder/features/design_systems.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Design Systems

Design Systems give Reflex Build reusable visual guidelines for every app in a project. Define colors, typography, spacing, component styles, and other brand rules once, then let the AI Builder apply them as it creates and updates your apps.

## Open Design Systems

You can access Design Systems in two places:

- In your project, open **Project Settings** and select **Design Systems** to create and manage the project's design systems.
- In the new-app prompt box, open the **Design System** selector to choose an existing design system or create one without leaving the builder.

Design systems belong to the project, so every app in that project can reuse them.

## Create a Design System

From **Project Settings > Design Systems**, click **Create new**. Enter a name and provide at least one source for the design:

- **Guidelines**: Describe the visual direction in plain language.
- **Reference website**: Enter a public website URL. Reflex captures a screenshot of the page and uses its visual language as a reference.
- **Reference file**: Upload a PDF or image containing a brand guide, style guide, screenshot, or mockup.

You can combine written guidelines with a website or uploaded file. Click **Generate** to create and save the design system. The new design system becomes active automatically.

For example:

```text
Use a warm, editorial style with cream backgrounds, dark green text,
serif headings, compact navigation, and orange accents for primary actions.
Keep cards flat with thin borders and use generous spacing between sections.
```

### Supported Reference Files

Reflex accepts one reference file up to 20 MB in any of these formats:

- PDF
- PNG
- JPG or JPEG
- WebP

## Start From an Example

The **Examples** section includes ready-made design systems such as Minimal, Modern, Material, Carbon, Neobrutalism, and Glassmorphism. Select **Add Style** on an example to add it to the project and make it active.

Examples are useful as a starting point. After adding one, edit its saved Markdown instructions to match your brand more closely.

## Select the Active Design System

Enable **Auto-enable** next to a saved design system to make it active for subsequent AI Builder requests in the project. You can also select or create a design system from the new-app prompt box before generating an app.

Only one design system can be active at a time. Activating, creating, or adding another design system automatically deactivates the current one. Turn off the active design system if you want to build without design-system guidance.

The active design system also guides later updates to the project's apps. You can still provide page-specific requirements in your prompt:

```text
Use the active design system, but make this checkout page more compact
and reserve the accent color for the final purchase action.
```

## Edit or Delete a Design System

In **Project Settings > Design Systems**, open the menu next to a saved design system.

- Select **Edit** to change its name or update the Markdown instructions that the AI Builder follows.
- Select **Delete** to remove it from the project.

Editing the Markdown is useful when you need exact tokens or component rules:

```markdown
## Buttons

- Primary buttons use `#0F62FE` with white text.
- Use a 4 px corner radius.
- Button labels use 14 px semibold text.
- Destructive actions use `#DA1E28`.
```

## Best Practices

- Give each design system a descriptive name, such as `Marketing Site` or `Internal Admin Tools`.
- Describe concrete choices such as colors, typefaces, spacing, borders, and component behavior instead of asking for a design that is only "modern" or "clean."
- Use a public, visually representative page when supplying a reference website.
- Upload a focused brand guide or screenshot that clearly shows the styles you want the AI Builder to reproduce.
- Review the saved Markdown and add any non-negotiable accessibility or brand requirements.
- Keep one design system active while building a consistent group of apps, and switch it when the project needs a different visual direction.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def get_sidebar_items_ai_builder_overview():
ai_builder.features.connect_to_github,
ai_builder.features.connect_to_git_providers,
ai_builder.features.knowledge,
ai_builder.features.design_systems,
ai_builder.features.image_as_prompt,
# ai_builder.features.automated_testing,
ai_builder.features.customization,
Expand Down
12 changes: 8 additions & 4 deletions docs/app/reflex_docs/views/docs_navbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

def github_button() -> rx.Component:
label = f"View Reflex on GitHub - {GITHUB_STARS // 1000}K stars"
return rx.el.a(
return rx.el.elements.a(
button(
get_icon(icon="github_navbar", class_name="shrink-0"),
f"{GITHUB_STARS // 1000}K",
Expand Down Expand Up @@ -65,7 +65,9 @@ def logo() -> rx.Component:
)


def menu_item(text: str, href: str, active_str: str = "") -> rx.Component:
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"

Expand All @@ -92,8 +94,10 @@ def menu_item(text: str, href: str, active_str: str = "") -> rx.Component:
else:
active = router_path.contains(active_str)

anchor = rx.el.elements.a if external else rx.el.a

return ui.navigation_menu.item(
rx.el.elements.a(
anchor(
button(
text,
size="sm",
Expand All @@ -117,7 +121,7 @@ def navigation_menu() -> rx.Component:
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"),
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"},
),
Expand Down
126 changes: 126 additions & 0 deletions docs/app/tests/test_docs_navbar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""Tests for the docs navbar links.

The docs app is served under ``frontend_path="/docs"``. ``rx.el.a`` compiles to
React Router's ``Link``, which resolves its destination against that basename;
``rx.el.elements.a`` stays a raw HTML anchor, which does not. In-app links must
therefore use the former, and links that already carry a full path (other
deployments, the marketing site) must use the latter.
"""

import pytest
import reflex as rx


@pytest.fixture
def navbar():
"""Import the navbar module through the pages package.

Importing ``reflex_docs.views.docs_navbar`` first in a fresh interpreter
trips a pre-existing circular import via ``reflex_docs.pages``.

Yields:
The ``reflex_docs.views.docs_navbar`` module.
"""
import reflex_docs.pages # noqa: F401
from reflex_docs.views import docs_navbar

yield docs_navbar


def _collect_links(component) -> list[tuple[str, str]]:
"""Walk a component tree and collect every anchor destination.

Walks the tree rather than matching against ``str(component)``, whose repr
is truncated for a tree this size.

Args:
component: The component to walk.

Returns:
A list of ``(kind, destination)`` pairs, where kind is ``"router"`` for
a React Router link and ``"anchor"`` for a raw HTML anchor.
"""
links = []
name = type(component).__name__
if name == "ReactRouterLink":
links.append(("router", str(component.to).strip('"')))
elif name == "A":
links.append(("anchor", str(component.href).strip('"')))
for child in getattr(component, "children", ()):
links.extend(_collect_links(child))
return links


def test_internal_menu_items_use_router_links(navbar):
"""In-app navbar links must compile to React Router links.

Regression test: as a raw anchor, "Build with AI" sent users to
``reflex.dev/ai/overview/best-practices/`` instead of
``reflex.dev/docs/ai/overview/best-practices/``.
"""
links = _collect_links(
navbar.menu_item("Build with AI", "/ai/overview/best-practices/", "ai")
)

assert links == [("router", "/ai/overview/best-practices/")]


def test_external_menu_items_use_plain_anchors(navbar):
"""Cross-app navbar links must stay raw anchors that own their full path."""
links = _collect_links(navbar.menu_item("XY", "/docs/xy/", "xy", external=True))

assert links == [("anchor", "/docs/xy/")]


def test_navigation_menu_routes_in_app_destinations(navbar):
"""Every in-app navbar destination compiles to a router link."""
from reflex_docs.pages.docs import ai_builder, getting_started, hosting

router_targets = {
dest
for kind, dest in _collect_links(navbar.navigation_menu())
if kind == "router"
}

for path in (
"/",
ai_builder.overview.best_practices.path,
getting_started.introduction.path,
hosting.deploy_quick_start.path,
):
assert path in router_targets, f"{path} is not a router link"

# Router links resolve against frontend_path, so a literal /docs prefix
# here would compile to /docs/docs/...
double_prefixed = [dest for dest in router_targets if dest.startswith("/docs")]
assert not double_prefixed, f"double-prefixed router links: {double_prefixed}"


def test_navigation_menu_keeps_cross_app_destinations_raw(navbar):
"""Destinations outside this app render as raw anchors."""
anchor_targets = {
dest
for kind, dest in _collect_links(navbar.navigation_menu())
if kind == "anchor"
}

assert "/docs/xy/" in anchor_targets


def test_external_links_bypass_the_router(navbar):
"""Absolute off-site URLs render as raw anchors, not router links."""
from reflex_site_shared.constants import GITHUB_URL, REFLEX_URL

assert _collect_links(navbar.github_button()) == [("anchor", GITHUB_URL)]
assert _collect_links(navbar.logo()) == [("anchor", REFLEX_URL)]


def test_reflex_el_a_and_elements_a_are_not_interchangeable():
"""Guard the distinction the navbar relies on.

``rx.el.a`` is aliased to React Router's ``Link``; ``rx.el.elements.a`` is
the raw HTML anchor. If those ever converge, the navbar's internal vs.
external split becomes meaningless and this test should be revisited.
"""
assert _collect_links(rx.el.a(href="/x/")) == [("router", "/x/")]
assert _collect_links(rx.el.elements.a(href="/x/")) == [("anchor", "/x/")]
Loading