Skip to content

Commit b9d112d

Browse files
authored
ENG-10641: Document AI Builder Design System feature (#6817)
1 parent 1358e00 commit b9d112d

4 files changed

Lines changed: 220 additions & 4 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Design Systems
2+
3+
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.
4+
5+
## Open Design Systems
6+
7+
You can access Design Systems in two places:
8+
9+
- In your project, open **Project Settings** and select **Design Systems** to create and manage the project's design systems.
10+
- In the new-app prompt box, open the **Design System** selector to choose an existing design system or create one without leaving the builder.
11+
12+
Design systems belong to the project, so every app in that project can reuse them.
13+
14+
## Create a Design System
15+
16+
From **Project Settings > Design Systems**, click **Create new**. Enter a name and provide at least one source for the design:
17+
18+
- **Guidelines**: Describe the visual direction in plain language.
19+
- **Reference website**: Enter a public website URL. Reflex captures a screenshot of the page and uses its visual language as a reference.
20+
- **Reference file**: Upload a PDF or image containing a brand guide, style guide, screenshot, or mockup.
21+
22+
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.
23+
24+
For example:
25+
26+
```text
27+
Use a warm, editorial style with cream backgrounds, dark green text,
28+
serif headings, compact navigation, and orange accents for primary actions.
29+
Keep cards flat with thin borders and use generous spacing between sections.
30+
```
31+
32+
### Supported Reference Files
33+
34+
Reflex accepts one reference file up to 20 MB in any of these formats:
35+
36+
- PDF
37+
- PNG
38+
- JPG or JPEG
39+
- WebP
40+
41+
## Start From an Example
42+
43+
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.
44+
45+
Examples are useful as a starting point. After adding one, edit its saved Markdown instructions to match your brand more closely.
46+
47+
## Select the Active Design System
48+
49+
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.
50+
51+
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.
52+
53+
The active design system also guides later updates to the project's apps. You can still provide page-specific requirements in your prompt:
54+
55+
```text
56+
Use the active design system, but make this checkout page more compact
57+
and reserve the accent color for the final purchase action.
58+
```
59+
60+
## Edit or Delete a Design System
61+
62+
In **Project Settings > Design Systems**, open the menu next to a saved design system.
63+
64+
- Select **Edit** to change its name or update the Markdown instructions that the AI Builder follows.
65+
- Select **Delete** to remove it from the project.
66+
67+
Editing the Markdown is useful when you need exact tokens or component rules:
68+
69+
```markdown
70+
## Buttons
71+
72+
- Primary buttons use `#0F62FE` with white text.
73+
- Use a 4 px corner radius.
74+
- Button labels use 14 px semibold text.
75+
- Destructive actions use `#DA1E28`.
76+
```
77+
78+
## Best Practices
79+
80+
- Give each design system a descriptive name, such as `Marketing Site` or `Internal Admin Tools`.
81+
- Describe concrete choices such as colors, typefaces, spacing, borders, and component behavior instead of asking for a design that is only "modern" or "clean."
82+
- Use a public, visually representative page when supplying a reference website.
83+
- Upload a focused brand guide or screenshot that clearly shows the styles you want the AI Builder to reproduce.
84+
- Review the saved Markdown and add any non-negotiable accessibility or brand requirements.
85+
- Keep one design system active while building a consistent group of apps, and switch it when the project needs a different visual direction.

docs/app/reflex_docs/templates/docpage/sidebar/sidebar_items/ai.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def get_sidebar_items_ai_builder_overview():
2828
ai_builder.features.connect_to_github,
2929
ai_builder.features.connect_to_git_providers,
3030
ai_builder.features.knowledge,
31+
ai_builder.features.design_systems,
3132
ai_builder.features.image_as_prompt,
3233
# ai_builder.features.automated_testing,
3334
ai_builder.features.customization,

docs/app/reflex_docs/views/docs_navbar.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
def github_button() -> rx.Component:
2020
label = f"View Reflex on GitHub - {GITHUB_STARS // 1000}K stars"
21-
return rx.el.a(
21+
return rx.el.elements.a(
2222
button(
2323
get_icon(icon="github_navbar", class_name="shrink-0"),
2424
f"{GITHUB_STARS // 1000}K",
@@ -65,7 +65,9 @@ def logo() -> rx.Component:
6565
)
6666

6767

68-
def menu_item(text: str, href: str, active_str: str = "") -> rx.Component:
68+
def menu_item(
69+
text: str, href: str, active_str: str = "", external: bool = False
70+
) -> rx.Component:
6971
router_path = rx.State.router.page.path
7072
active_cn = "shadow-[inset_0_-1px_0_0_var(--primary-10)] [&_button]:text-primary-10 [&_div]:text-primary-10"
7173

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

97+
anchor = rx.el.elements.a if external else rx.el.a
98+
9599
return ui.navigation_menu.item(
96-
rx.el.elements.a(
100+
anchor(
97101
button(
98102
text,
99103
size="sm",
@@ -117,7 +121,7 @@ def navigation_menu() -> rx.Component:
117121
menu_item("Build with AI", ai_builder.overview.best_practices.path, "ai"),
118122
menu_item("Framework", getting_started.introduction.path, "framework"),
119123
menu_item("Cloud", hosting.deploy_quick_start.path, "hosting"),
120-
menu_item("XY", "/docs/xy/", "xy"),
124+
menu_item("XY", "/docs/xy/", "xy", external=True),
121125
class_name="flex flex-row items-center gap-2 m-0 h-full list-none",
122126
custom_attrs={"role": "menubar"},
123127
),

docs/app/tests/test_docs_navbar.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""Tests for the docs navbar links.
2+
3+
The docs app is served under ``frontend_path="/docs"``. ``rx.el.a`` compiles to
4+
React Router's ``Link``, which resolves its destination against that basename;
5+
``rx.el.elements.a`` stays a raw HTML anchor, which does not. In-app links must
6+
therefore use the former, and links that already carry a full path (other
7+
deployments, the marketing site) must use the latter.
8+
"""
9+
10+
import pytest
11+
import reflex as rx
12+
13+
14+
@pytest.fixture
15+
def navbar():
16+
"""Import the navbar module through the pages package.
17+
18+
Importing ``reflex_docs.views.docs_navbar`` first in a fresh interpreter
19+
trips a pre-existing circular import via ``reflex_docs.pages``.
20+
21+
Yields:
22+
The ``reflex_docs.views.docs_navbar`` module.
23+
"""
24+
import reflex_docs.pages # noqa: F401
25+
from reflex_docs.views import docs_navbar
26+
27+
yield docs_navbar
28+
29+
30+
def _collect_links(component) -> list[tuple[str, str]]:
31+
"""Walk a component tree and collect every anchor destination.
32+
33+
Walks the tree rather than matching against ``str(component)``, whose repr
34+
is truncated for a tree this size.
35+
36+
Args:
37+
component: The component to walk.
38+
39+
Returns:
40+
A list of ``(kind, destination)`` pairs, where kind is ``"router"`` for
41+
a React Router link and ``"anchor"`` for a raw HTML anchor.
42+
"""
43+
links = []
44+
name = type(component).__name__
45+
if name == "ReactRouterLink":
46+
links.append(("router", str(component.to).strip('"')))
47+
elif name == "A":
48+
links.append(("anchor", str(component.href).strip('"')))
49+
for child in getattr(component, "children", ()):
50+
links.extend(_collect_links(child))
51+
return links
52+
53+
54+
def test_internal_menu_items_use_router_links(navbar):
55+
"""In-app navbar links must compile to React Router links.
56+
57+
Regression test: as a raw anchor, "Build with AI" sent users to
58+
``reflex.dev/ai/overview/best-practices/`` instead of
59+
``reflex.dev/docs/ai/overview/best-practices/``.
60+
"""
61+
links = _collect_links(
62+
navbar.menu_item("Build with AI", "/ai/overview/best-practices/", "ai")
63+
)
64+
65+
assert links == [("router", "/ai/overview/best-practices/")]
66+
67+
68+
def test_external_menu_items_use_plain_anchors(navbar):
69+
"""Cross-app navbar links must stay raw anchors that own their full path."""
70+
links = _collect_links(navbar.menu_item("XY", "/docs/xy/", "xy", external=True))
71+
72+
assert links == [("anchor", "/docs/xy/")]
73+
74+
75+
def test_navigation_menu_routes_in_app_destinations(navbar):
76+
"""Every in-app navbar destination compiles to a router link."""
77+
from reflex_docs.pages.docs import ai_builder, getting_started, hosting
78+
79+
router_targets = {
80+
dest
81+
for kind, dest in _collect_links(navbar.navigation_menu())
82+
if kind == "router"
83+
}
84+
85+
for path in (
86+
"/",
87+
ai_builder.overview.best_practices.path,
88+
getting_started.introduction.path,
89+
hosting.deploy_quick_start.path,
90+
):
91+
assert path in router_targets, f"{path} is not a router link"
92+
93+
# Router links resolve against frontend_path, so a literal /docs prefix
94+
# here would compile to /docs/docs/...
95+
double_prefixed = [dest for dest in router_targets if dest.startswith("/docs")]
96+
assert not double_prefixed, f"double-prefixed router links: {double_prefixed}"
97+
98+
99+
def test_navigation_menu_keeps_cross_app_destinations_raw(navbar):
100+
"""Destinations outside this app render as raw anchors."""
101+
anchor_targets = {
102+
dest
103+
for kind, dest in _collect_links(navbar.navigation_menu())
104+
if kind == "anchor"
105+
}
106+
107+
assert "/docs/xy/" in anchor_targets
108+
109+
110+
def test_external_links_bypass_the_router(navbar):
111+
"""Absolute off-site URLs render as raw anchors, not router links."""
112+
from reflex_site_shared.constants import GITHUB_URL, REFLEX_URL
113+
114+
assert _collect_links(navbar.github_button()) == [("anchor", GITHUB_URL)]
115+
assert _collect_links(navbar.logo()) == [("anchor", REFLEX_URL)]
116+
117+
118+
def test_reflex_el_a_and_elements_a_are_not_interchangeable():
119+
"""Guard the distinction the navbar relies on.
120+
121+
``rx.el.a`` is aliased to React Router's ``Link``; ``rx.el.elements.a`` is
122+
the raw HTML anchor. If those ever converge, the navbar's internal vs.
123+
external split becomes meaningless and this test should be revisited.
124+
"""
125+
assert _collect_links(rx.el.a(href="/x/")) == [("router", "/x/")]
126+
assert _collect_links(rx.el.elements.a(href="/x/")) == [("anchor", "/x/")]

0 commit comments

Comments
 (0)