diff --git a/reflex/.templates/apps/demo/.gitignore b/reflex/.templates/apps/demo/.gitignore
new file mode 100644
index 000000000..eab0d4b05
--- /dev/null
+++ b/reflex/.templates/apps/demo/.gitignore
@@ -0,0 +1,4 @@
+*.db
+*.py[cod]
+.web
+__pycache__/
\ No newline at end of file
diff --git a/reflex/.templates/apps/demo/assets/favicon.ico b/reflex/.templates/apps/demo/assets/favicon.ico
new file mode 100644
index 000000000..166ae995e
Binary files /dev/null and b/reflex/.templates/apps/demo/assets/favicon.ico differ
diff --git a/reflex/.templates/apps/demo/assets/github.svg b/reflex/.templates/apps/demo/assets/github.svg
new file mode 100644
index 000000000..61c9d791b
--- /dev/null
+++ b/reflex/.templates/apps/demo/assets/github.svg
@@ -0,0 +1,10 @@
+
diff --git a/reflex/.templates/apps/demo/assets/icon.svg b/reflex/.templates/apps/demo/assets/icon.svg
new file mode 100644
index 000000000..b9cc89da9
--- /dev/null
+++ b/reflex/.templates/apps/demo/assets/icon.svg
@@ -0,0 +1,37 @@
+
diff --git a/reflex/.templates/apps/demo/assets/logo.svg b/reflex/.templates/apps/demo/assets/logo.svg
new file mode 100644
index 000000000..94fe1f511
--- /dev/null
+++ b/reflex/.templates/apps/demo/assets/logo.svg
@@ -0,0 +1,68 @@
+
diff --git a/reflex/.templates/apps/demo/assets/paneleft.svg b/reflex/.templates/apps/demo/assets/paneleft.svg
new file mode 100644
index 000000000..ac9c5040a
--- /dev/null
+++ b/reflex/.templates/apps/demo/assets/paneleft.svg
@@ -0,0 +1,13 @@
+
diff --git a/reflex/.templates/apps/demo/code/__init__.py b/reflex/.templates/apps/demo/code/__init__.py
new file mode 100644
index 000000000..e1d286346
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/__init__.py
@@ -0,0 +1 @@
+"""Base template for Reflex."""
diff --git a/reflex/.templates/apps/demo/code/demo.py b/reflex/.templates/apps/demo/code/demo.py
new file mode 100644
index 000000000..fe98a31eb
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/demo.py
@@ -0,0 +1,123 @@
+"""Welcome to Reflex! This file outlines the steps to create a basic app."""
+from typing import Callable
+
+import reflex as rx
+
+from .pages import chatapp_page, datatable_page, forms_page, graphing_page, home_page
+from .sidebar import sidebar
+from .state import State
+from .styles import *
+
+meta = [
+ {
+ "name": "viewport",
+ "content": "width=device-width, shrink-to-fit=no, initial-scale=1",
+ },
+]
+
+
+def template(main_content: Callable[[], rx.Component]) -> rx.Component:
+ """The template for each page of the app.
+
+ Args:
+ main_content (Callable[[], rx.Component]): The main content of the page.
+
+ Returns:
+ rx.Component: The template for each page of the app.
+ """
+ menu_button = rx.box(
+ rx.menu(
+ rx.menu_button(
+ rx.icon(
+ tag="hamburger",
+ size="4em",
+ color=text_color,
+ ),
+ ),
+ rx.menu_list(
+ rx.menu_item(rx.link("Home", href="/", width="100%")),
+ rx.menu_divider(),
+ rx.menu_item(
+ rx.link("About", href="https://github.com/reflex-dev", width="100%")
+ ),
+ rx.menu_item(
+ rx.link("Contact", href="mailto:founders@=reflex.dev", width="100%")
+ ),
+ ),
+ ),
+ position="fixed",
+ right="1.5em",
+ top="1.5em",
+ z_index="500",
+ )
+
+ return rx.hstack(
+ sidebar(),
+ main_content(),
+ rx.spacer(),
+ menu_button,
+ align_items="flex-start",
+ transition="left 0.5s, width 0.5s",
+ position="relative",
+ left=rx.cond(State.sidebar_displayed, "0px", f"-{sidebar_width}"),
+ )
+
+
+@rx.page("/", meta=meta)
+@template
+def home() -> rx.Component:
+ """Home page.
+
+ Returns:
+ rx.Component: The home page.
+ """
+ return home_page()
+
+
+@rx.page("/forms", meta=meta)
+@template
+def forms() -> rx.Component:
+ """Forms page.
+
+ Returns:
+ rx.Component: The settings page.
+ """
+ return forms_page()
+
+
+@rx.page("/graphing", meta=meta)
+@template
+def graphing() -> rx.Component:
+ """Graphing page.
+
+ Returns:
+ rx.Component: The graphing page.
+ """
+ return graphing_page()
+
+
+@rx.page("/datatable", meta=meta)
+@template
+def datatable() -> rx.Component:
+ """Data Table page.
+
+ Returns:
+ rx.Component: The chatapp page.
+ """
+ return datatable_page()
+
+
+@rx.page("/chatapp", meta=meta)
+@template
+def chatapp() -> rx.Component:
+ """Chatapp page.
+
+ Returns:
+ rx.Component: The chatapp page.
+ """
+ return chatapp_page()
+
+
+# Add state and page to the app.
+app = rx.App(style=base_style)
+app.compile()
diff --git a/reflex/.templates/apps/demo/code/pages/__init__.py b/reflex/.templates/apps/demo/code/pages/__init__.py
new file mode 100644
index 000000000..7f319d25c
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/pages/__init__.py
@@ -0,0 +1,6 @@
+"""The pages of the app."""
+from .chatapp import chatapp_page
+from .datatable import datatable_page
+from .forms import forms_page
+from .graphing import graphing_page
+from .home import home_page
diff --git a/reflex/.templates/apps/demo/code/pages/chatapp.py b/reflex/.templates/apps/demo/code/pages/chatapp.py
new file mode 100644
index 000000000..5f06cc15d
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/pages/chatapp.py
@@ -0,0 +1,31 @@
+"""The main Chat app."""
+
+import reflex as rx
+
+from ..styles import *
+from ..webui import styles
+from ..webui.components import chat, modal, navbar, sidebar
+
+
+def chatapp_page() -> rx.Component:
+ """The main app.
+
+ Returns:
+ The UI for the main app.
+ """
+ return rx.box(
+ rx.vstack(
+ navbar(),
+ chat.chat(),
+ chat.action_bar(),
+ sidebar(),
+ modal(),
+ bg=styles.bg_dark_color,
+ color=styles.text_light_color,
+ min_h="100vh",
+ align_items="stretch",
+ spacing="0",
+ style=template_content_style,
+ ),
+ style=template_page_style,
+ )
diff --git a/reflex/.templates/apps/demo/code/pages/datatable.py b/reflex/.templates/apps/demo/code/pages/datatable.py
new file mode 100644
index 000000000..64bdea0eb
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/pages/datatable.py
@@ -0,0 +1,359 @@
+"""The settings page for the template."""
+from typing import Any
+
+import reflex as rx
+from reflex.components.datadisplay.dataeditor import DataEditorTheme
+
+from ..styles import *
+from ..webui.state import State
+
+
+class DataTableState(State):
+ """Datatable state."""
+
+ cols: list[Any] = [
+ {"title": "Title", "type": "str"},
+ {
+ "title": "Name",
+ "type": "str",
+ "group": "Data",
+ "width": 300,
+ },
+ {
+ "title": "Birth",
+ "type": "str",
+ "group": "Data",
+ "width": 150,
+ },
+ {
+ "title": "Human",
+ "type": "bool",
+ "group": "Data",
+ "width": 80,
+ },
+ {
+ "title": "House",
+ "type": "str",
+ "group": "Data",
+ },
+ {
+ "title": "Wand",
+ "type": "str",
+ "group": "Data",
+ "width": 250,
+ },
+ {
+ "title": "Patronus",
+ "type": "str",
+ "group": "Data",
+ },
+ {
+ "title": "Blood status",
+ "type": "str",
+ "group": "Data",
+ "width": 200,
+ },
+ ]
+
+ data = [
+ [
+ "1",
+ "Harry James Potter",
+ "31 July 1980",
+ True,
+ "Gryffindor",
+ "11' Holly phoenix feather",
+ "Stag",
+ "Half-blood",
+ ],
+ [
+ "2",
+ "Ronald Bilius Weasley",
+ "1 March 1980",
+ True,
+ "Gryffindor",
+ "12' Ash unicorn tail hair",
+ "Jack Russell terrier",
+ "Pure-blood",
+ ],
+ [
+ "3",
+ "Hermione Jean Granger",
+ "19 September, 1979",
+ True,
+ "Gryffindor",
+ "10¾' vine wood dragon heartstring",
+ "Otter",
+ "Muggle-born",
+ ],
+ [
+ "4",
+ "Albus Percival Wulfric Brian Dumbledore",
+ "Late August 1881",
+ True,
+ "Gryffindor",
+ "15' Elder Thestral tail hair core",
+ "Phoenix",
+ "Half-blood",
+ ],
+ [
+ "5",
+ "Rubeus Hagrid",
+ "6 December 1928",
+ False,
+ "Gryffindor",
+ "16' Oak unknown core",
+ "None",
+ "Part-Human (Half-giant)",
+ ],
+ [
+ "6",
+ "Fred Weasley",
+ "1 April, 1978",
+ True,
+ "Gryffindor",
+ "Unknown",
+ "Unknown",
+ "Pure-blood",
+ ],
+ [
+ "7",
+ "George Weasley",
+ "1 April, 1978",
+ True,
+ "Gryffindor",
+ "Unknown",
+ "Unknown",
+ "Pure-blood",
+ ],
+ ]
+
+
+code_show = """rx.hstack(
+ rx.divider(orientation="vertical", height="100vh", border="solid black 1px"),
+ rx.vstack(
+ rx.box(
+ rx.data_editor(
+ columns=DataTableState.cols,
+ data=DataTableState.data,
+ draw_focus_ring=True,
+ row_height=50,
+ smooth_scroll_x=True,
+ smooth_scroll_y=True,
+ column_select="single",
+ # style
+ theme=DataEditorTheme(**darkTheme),
+ width="80vw",
+ height="80vh",
+ ),
+ ),
+ rx.spacer(),
+ height="100vh",
+ spacing="25",
+ ),
+)"""
+
+state_show = """class DataTableState(State):
+ cols: list[Any] = [
+ {"title": "Title", "type": "str"},
+ {
+ "title": "Name",
+ "type": "str",
+ "group": "Data",
+ "width": 300,
+ },
+ {
+ "title": "Birth",
+ "type": "str",
+ "group": "Data",
+ "width": 150,
+ },
+ {
+ "title": "Human",
+ "type": "bool",
+ "group": "Data",
+ "width": 80,
+ },
+ {
+ "title": "House",
+ "type": "str",
+ "group": "Data",
+ },
+ {
+ "title": "Wand",
+ "type": "str",
+ "group": "Data",
+ "width": 250,
+ },
+ {
+ "title": "Patronus",
+ "type": "str",
+ "group": "Data",
+ },
+ {
+ "title": "Blood status",
+ "type": "str",
+ "group": "Data",
+ "width": 200,
+ },
+ ]"""
+
+data_show = """[
+ ["1", "Harry James Potter", "31 July 1980", True, "Gryffindor", "11' Holly phoenix feather", "Stag", "Half-blood"],
+ ["2", "Ronald Bilius Weasley", "1 March 1980", True,"Gryffindor", "12' Ash unicorn tail hair", "Jack Russell terrier", "Pure-blood"],
+ ["3", "Hermione Jean Granger", "19 September, 1979", True, "Gryffindor", "10¾' vine wood dragon heartstring", "Otter", "Muggle-born"],
+ ["4", "Albus Percival Wulfric Brian Dumbledore", "Late August 1881", True, "Gryffindor", "15' Elder Thestral tail hair core", "Phoenix", "Half-blood"],
+ ["5", "Rubeus Hagrid", "6 December 1928", False, "Gryffindor", "16' Oak unknown core", "None", "Part-Human (Half-giant)"],
+ ["6", "Fred Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"],
+ ["7", "George Weasley", "1 April, 1978", True, "Gryffindor", "Unknown", "Unknown", "Pure-blood"],
+]"""
+
+
+darkTheme = {
+ "accent_color": "#8c96ff",
+ "accent_light": "rgba(202, 206, 255, 0.253)",
+ "text_dark": "#ffffff",
+ "text_medium": "#b8b8b8",
+ "text_light": "#a0a0a0",
+ "text_bubble": "#ffffff",
+ "bg_icon_header": "#b8b8b8",
+ "fg_icon_header": "#000000",
+ "text_header": "#a1a1a1",
+ "text_header_selected": "#000000",
+ "bg_cell": "#16161b",
+ "bg_cell_medium": "#202027",
+ "bg_header": "#212121",
+ "bg_header_has_focus": "#474747",
+ "bg_header_hovered": "#404040",
+ "bg_bubble": "#212121",
+ "bg_bubble_selected": "#000000",
+ "bg_search_result": "#423c24",
+ "border_color": "rgba(225,225,225,0.2)",
+ "drilldown_border": "rgba(225,225,225,0.4)",
+ "link_color": "#4F5DFF",
+ "header_font_style": "bold 14px",
+ "base_font_style": "13px",
+ "font_family": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif",
+}
+
+darkTheme_show = """darkTheme={
+ "accent_color": "#8c96ff",
+ "accent_light": "rgba(202, 206, 255, 0.253)",
+ "text_dark": "#ffffff",
+ "text_medium": "#b8b8b8",
+ "text_light": "#a0a0a0",
+ "text_bubble": "#ffffff",
+ "bg_icon_header": "#b8b8b8",
+ "fg_icon_header": "#000000",
+ "text_header": "#a1a1a1",
+ "text_header_selected": "#000000",
+ "bg_cell": "#16161b",
+ "bg_cell_medium": "#202027",
+ "bg_header": "#212121",
+ "bg_header_has_focus": "#474747",
+ "bg_header_hovered": "#404040",
+ "bg_bubble": "#212121",
+ "bg_bubble_selected": "#000000",
+ "bg_search_result": "#423c24",
+ "border_color": "rgba(225,225,225,0.2)",
+ "drilldown_border": "rgba(225,225,225,0.4)",
+ "link_color": "#4F5DFF",
+ "header_font_style": "bold 14px",
+ "base_font_style": "13px",
+ "font_family": "Inter, Roboto, -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Ubuntu, noto, arial, sans-serif",
+}"""
+
+
+def datatable_page() -> rx.Component:
+ """The UI for the settings page.
+
+ Returns:
+ rx.Component: The UI for the settings page.
+ """
+ return rx.box(
+ rx.vstack(
+ rx.heading(
+ "Data Table Demo",
+ font_size="3em",
+ ),
+ rx.hstack(
+ rx.vstack(
+ rx.box(
+ rx.data_editor(
+ columns=DataTableState.cols,
+ data=DataTableState.data,
+ draw_focus_ring=True,
+ row_height=50,
+ smooth_scroll_x=True,
+ smooth_scroll_y=True,
+ column_select="single",
+ # style
+ theme=DataEditorTheme(**darkTheme),
+ width="80vw",
+ ),
+ ),
+ rx.spacer(),
+ spacing="25",
+ ),
+ ),
+ rx.tabs(
+ rx.tab_list(
+ rx.tab("Code", style=tab_style),
+ rx.tab("Data", style=tab_style),
+ rx.tab("State", style=tab_style),
+ rx.tab("Styling", style=tab_style),
+ padding_x=0,
+ ),
+ rx.tab_panels(
+ rx.tab_panel(
+ rx.code_block(
+ code_show,
+ language="python",
+ show_line_numbers=True,
+ ),
+ width="100%",
+ padding_x=0,
+ padding_y=".25em",
+ ),
+ rx.tab_panel(
+ rx.code_block(
+ data_show,
+ language="python",
+ show_line_numbers=True,
+ ),
+ width="100%",
+ padding_x=0,
+ padding_y=".25em",
+ ),
+ rx.tab_panel(
+ rx.code_block(
+ state_show,
+ language="python",
+ show_line_numbers=True,
+ ),
+ width="100%",
+ padding_x=0,
+ padding_y=".25em",
+ ),
+ rx.tab_panel(
+ rx.code_block(
+ darkTheme_show,
+ language="python",
+ show_line_numbers=True,
+ ),
+ width="100%",
+ padding_x=0,
+ padding_y=".25em",
+ ),
+ width="100%",
+ ),
+ variant="unstyled",
+ color_scheme="purple",
+ align="end",
+ width="100%",
+ padding_top=".5em",
+ ),
+ style=template_content_style,
+ ),
+ style=template_page_style,
+ )
diff --git a/reflex/.templates/apps/demo/code/pages/forms.py b/reflex/.templates/apps/demo/code/pages/forms.py
new file mode 100644
index 000000000..904f003cd
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/pages/forms.py
@@ -0,0 +1,254 @@
+"""The settings page for the template."""
+import reflex as rx
+
+from ..states.form_state import FormState, UploadState
+from ..styles import *
+
+forms_1_code = """rx.vstack(
+ rx.form(
+ rx.vstack(
+ rx.input(
+ placeholder="First Name",
+ id="first_name",
+ ),
+ rx.input(
+ placeholder="Last Name", id="last_name"
+ ),
+ rx.hstack(
+ rx.checkbox("Checked", id="check"),
+ rx.switch("Switched", id="switch"),
+ ),
+ rx.button("Submit",
+ type_="submit",
+ bg="#ecfdf5",
+ color="#047857",
+ border_radius="lg",
+ ),
+ ),
+ on_submit=FormState.handle_submit,
+ ),
+ rx.divider(),
+ rx.heading("Results"),
+ rx.text(FormState.form_data.to_string()),
+ width="100%",
+)"""
+
+color = "rgb(107,99,246)"
+
+forms_1_state = """class FormState(rx.State):
+
+ form_data: dict = {}
+
+ def handle_submit(self, form_data: dict):
+ "Handle the form submit."
+ self.form_data = form_data"""
+
+
+forms_2_code = """rx.vstack(
+ rx.upload(
+ rx.vstack(
+ rx.button(
+ "Select File",
+ color=color,
+ bg="white",
+ border=f"1px solid {color}",
+ ),
+ rx.text(
+ "Drag and drop files here or click to select files"
+ ),
+ ),
+ border=f"1px dotted {color}",
+ padding="5em",
+ ),
+ rx.hstack(rx.foreach(rx.selected_files, rx.text)),
+ rx.button(
+ "Upload",
+ on_click=lambda: UploadState.handle_upload(
+ rx.upload_files()
+ ),
+ ),
+ rx.button(
+ "Clear",
+ on_click=rx.clear_selected_files,
+ ),
+ rx.foreach(
+ UploadState.img, lambda img: rx.image(src=img, width="20%", height="auto",)
+ ),
+ padding="5em",
+ width="100%",
+)"""
+
+forms_2_state = """class UploadState(State):
+ "The app state."
+
+ # The images to show.
+ img: list[str]
+
+ async def handle_upload(
+ self, files: list[rx.UploadFile]
+ ):
+ "Handle the upload of file(s).
+
+ Args:
+ files: The uploaded files.
+ "
+ for file in files:
+ upload_data = await file.read()
+ outfile = rx.get_asset_path(file.filename)
+ # Save the file.
+ with open(outfile, "wb") as file_object:
+ file_object.write(upload_data)
+
+ # Update the img var.
+ self.img.append(f"/{file.filename}")"""
+
+
+def forms_page() -> rx.Component:
+ """The UI for the settings page.
+
+ Returns:
+ rx.Component: The UI for the settings page.
+ """
+ return rx.box(
+ rx.vstack(
+ rx.heading(
+ "Forms Demo",
+ font_size="3em",
+ ),
+ rx.vstack(
+ rx.form(
+ rx.vstack(
+ rx.input(
+ placeholder="First Name",
+ id="first_name",
+ ),
+ rx.input(placeholder="Last Name", id="last_name"),
+ rx.hstack(
+ rx.checkbox("Checked", id="check"),
+ rx.switch("Switched", id="switch"),
+ ),
+ rx.button(
+ "Submit",
+ type_="submit",
+ bg="#ecfdf5",
+ color="#047857",
+ border_radius="lg",
+ ),
+ ),
+ on_submit=FormState.handle_submit,
+ ),
+ rx.divider(),
+ rx.heading("Results"),
+ rx.text(FormState.form_data.to_string()),
+ width="100%",
+ ),
+ rx.tabs(
+ rx.tab_list(
+ rx.tab("Code", style=tab_style),
+ rx.tab("State", style=tab_style),
+ padding_x=0,
+ ),
+ rx.tab_panels(
+ rx.tab_panel(
+ rx.code_block(
+ forms_1_code,
+ language="python",
+ show_line_numbers=True,
+ ),
+ width="100%",
+ padding_x=0,
+ padding_y=".25em",
+ ),
+ rx.tab_panel(
+ rx.code_block(
+ forms_1_state,
+ language="python",
+ show_line_numbers=True,
+ ),
+ width="100%",
+ padding_x=0,
+ padding_y=".25em",
+ ),
+ width="100%",
+ ),
+ variant="unstyled",
+ color_scheme="purple",
+ align="end",
+ width="100%",
+ padding_top=".5em",
+ ),
+ rx.heading("Upload Example", font_size="3em"),
+ rx.text("Try uploading some images and see how they look."),
+ rx.vstack(
+ rx.upload(
+ rx.vstack(
+ rx.button(
+ "Select File",
+ color=color,
+ bg="white",
+ border=f"1px solid {color}",
+ ),
+ rx.text("Drag and drop files here or click to select files"),
+ ),
+ border=f"1px dotted {color}",
+ padding="5em",
+ ),
+ rx.hstack(rx.foreach(rx.selected_files, rx.text)),
+ rx.button(
+ "Upload",
+ on_click=lambda: UploadState.handle_upload(rx.upload_files()),
+ ),
+ rx.button(
+ "Clear",
+ on_click=rx.clear_selected_files,
+ ),
+ rx.foreach(
+ UploadState.img,
+ lambda img: rx.image(
+ src=img,
+ width="20%",
+ height="auto",
+ ),
+ ),
+ padding="5em",
+ width="100%",
+ ),
+ rx.tabs(
+ rx.tab_list(
+ rx.tab("Code", style=tab_style),
+ rx.tab("State", style=tab_style),
+ padding_x=0,
+ ),
+ rx.tab_panels(
+ rx.tab_panel(
+ rx.code_block(
+ forms_2_code,
+ language="python",
+ show_line_numbers=True,
+ ),
+ width="100%",
+ padding_x=0,
+ padding_y=".25em",
+ ),
+ rx.tab_panel(
+ rx.code_block(
+ forms_2_state,
+ language="python",
+ show_line_numbers=True,
+ ),
+ width="100%",
+ padding_x=0,
+ padding_y=".25em",
+ ),
+ width="100%",
+ ),
+ variant="unstyled",
+ color_scheme="purple",
+ align="end",
+ width="100%",
+ padding_top=".5em",
+ ),
+ style=template_content_style,
+ ),
+ style=template_page_style,
+ )
diff --git a/reflex/.templates/apps/demo/code/pages/graphing.py b/reflex/.templates/apps/demo/code/pages/graphing.py
new file mode 100644
index 000000000..0accf81a0
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/pages/graphing.py
@@ -0,0 +1,252 @@
+"""The dashboard page for the template."""
+import reflex as rx
+
+from ..states.pie_state import PieChartState
+from ..styles import *
+
+data_1 = [
+ {"name": "Page A", "uv": 4000, "pv": 2400, "amt": 2400},
+ {"name": "Page B", "uv": 3000, "pv": 1398, "amt": 2210},
+ {"name": "Page C", "uv": 2000, "pv": 9800, "amt": 2290},
+ {"name": "Page D", "uv": 2780, "pv": 3908, "amt": 2000},
+ {"name": "Page E", "uv": 1890, "pv": 4800, "amt": 2181},
+ {"name": "Page F", "uv": 2390, "pv": 3800, "amt": 2500},
+ {"name": "Page G", "uv": 3490, "pv": 4300, "amt": 2100},
+]
+data_1_show = """[
+ {"name": "Page A", "uv": 4000, "pv": 2400, "amt": 2400},
+ {"name": "Page B", "uv": 3000, "pv": 1398, "amt": 2210},
+ {"name": "Page C", "uv": 2000, "pv": 9800, "amt": 2290},
+ {"name": "Page D", "uv": 2780, "pv": 3908, "amt": 2000},
+ {"name": "Page E", "uv": 1890, "pv": 4800, "amt": 2181},
+ {"name": "Page F", "uv": 2390, "pv": 3800, "amt": 2500},
+ {"name": "Page G", "uv": 3490, "pv": 4300, "amt": 2100},
+]"""
+
+
+graph_1_code = """rx.recharts.composed_chart(
+ rx.recharts.area(
+ data_key="uv", stroke="#8884d8", fill="#8884d8"
+ ),
+ rx.recharts.bar(
+ data_key="amt", bar_size=20, fill="#413ea0"
+ ),
+ rx.recharts.line(
+ data_key="pv", type_="monotone", stroke="#ff7300"
+ ),
+ rx.recharts.x_axis(data_key="name"),
+ rx.recharts.y_axis(),
+ rx.recharts.cartesian_grid(stroke_dasharray="3 3"),
+ rx.recharts.graphing_tooltip(),
+ data=data,
+)"""
+
+
+graph_2_code = """rx.recharts.pie_chart(
+ rx.recharts.pie(
+ data=PieChartState.resources,
+ data_key="count",
+ name_key="type_",
+ cx="50%",
+ cy="50%",
+ start_angle=180,
+ end_angle=0,
+ fill="#8884d8",
+ label=True,
+ ),
+ rx.recharts.graphing_tooltip(),
+),
+rx.vstack(
+ rx.foreach(
+ PieChartState.resource_types,
+ lambda type_, i: rx.hstack(
+ rx.button(
+ "-",
+ on_click=PieChartState.decrement(type_),
+ ),
+ rx.text(
+ type_,
+ PieChartState.resources[i]["count"],
+ ),
+ rx.button(
+ "+",
+ on_click=PieChartState.increment(type_),
+ ),
+ ),
+ ),
+)"""
+
+graph_2_state = """class PieChartState(rx.State):
+ resources: list[dict[str, Any]] = [
+ dict(type_="🏆", count=1),
+ dict(type_="🪵", count=1),
+ dict(type_="🥑", count=1),
+ dict(type_="🧱", count=1),
+ ]
+
+ @rx.cached_var
+ def resource_types(self) -> list[str]:
+ return [r["type_"] for r in self.resources]
+
+ def increment(self, type_: str):
+ for resource in self.resources:
+ if resource["type_"] == type_:
+ resource["count"] += 1
+ break
+
+ def decrement(self, type_: str):
+ for resource in self.resources:
+ if (
+ resource["type_"] == type_
+ and resource["count"] > 0
+ ):
+ resource["count"] -= 1
+ break
+"""
+
+
+def graphing_page() -> rx.Component:
+ """The UI for the dashboard page.
+
+ Returns:
+ rx.Component: The UI for the dashboard page.
+ """
+ return rx.box(
+ rx.vstack(
+ rx.heading(
+ "Graphing Demo",
+ font_size="3em",
+ ),
+ rx.heading(
+ "Composed Chart",
+ font_size="2em",
+ ),
+ rx.stack(
+ rx.recharts.composed_chart(
+ rx.recharts.area(data_key="uv", stroke="#8884d8", fill="#8884d8"),
+ rx.recharts.bar(data_key="amt", bar_size=20, fill="#413ea0"),
+ rx.recharts.line(data_key="pv", type_="monotone", stroke="#ff7300"),
+ rx.recharts.x_axis(data_key="name"),
+ rx.recharts.y_axis(),
+ rx.recharts.cartesian_grid(stroke_dasharray="3 3"),
+ rx.recharts.graphing_tooltip(),
+ data=data_1,
+ # height="15em",
+ ),
+ width="100%",
+ height="20em",
+ ),
+ rx.tabs(
+ rx.tab_list(
+ rx.tab("Code", style=tab_style),
+ rx.tab("Data", style=tab_style),
+ padding_x=0,
+ ),
+ rx.tab_panels(
+ rx.tab_panel(
+ rx.code_block(
+ graph_1_code,
+ language="python",
+ show_line_numbers=True,
+ ),
+ width="100%",
+ padding_x=0,
+ padding_y=".25em",
+ ),
+ rx.tab_panel(
+ rx.code_block(
+ data_1_show,
+ language="python",
+ show_line_numbers=True,
+ ),
+ width="100%",
+ padding_x=0,
+ padding_y=".25em",
+ ),
+ width="100%",
+ ),
+ variant="unstyled",
+ color_scheme="purple",
+ align="end",
+ width="100%",
+ padding_top=".5em",
+ ),
+ rx.heading("Interactive Example", font_size="2em"),
+ rx.hstack(
+ rx.recharts.pie_chart(
+ rx.recharts.pie(
+ data=PieChartState.resources,
+ data_key="count",
+ name_key="type_",
+ cx="50%",
+ cy="50%",
+ start_angle=180,
+ end_angle=0,
+ fill="#8884d8",
+ label=True,
+ ),
+ rx.recharts.graphing_tooltip(),
+ ),
+ rx.vstack(
+ rx.foreach(
+ PieChartState.resource_types,
+ lambda type_, i: rx.hstack(
+ rx.button(
+ "-",
+ on_click=PieChartState.decrement(type_),
+ ),
+ rx.text(
+ type_,
+ PieChartState.resources[i]["count"],
+ ),
+ rx.button(
+ "+",
+ on_click=PieChartState.increment(type_),
+ ),
+ ),
+ ),
+ ),
+ width="100%",
+ height="15em",
+ ),
+ rx.tabs(
+ rx.tab_list(
+ rx.tab("Code", style=tab_style),
+ rx.tab("State", style=tab_style),
+ padding_x=0,
+ ),
+ rx.tab_panels(
+ rx.tab_panel(
+ rx.code_block(
+ graph_2_code,
+ language="python",
+ show_line_numbers=True,
+ ),
+ width="100%",
+ padding_x=0,
+ padding_y=".25em",
+ ),
+ rx.tab_panel(
+ rx.code_block(
+ graph_2_state,
+ language="python",
+ show_line_numbers=True,
+ ),
+ width="100%",
+ padding_x=0,
+ padding_y=".25em",
+ ),
+ width="100%",
+ ),
+ variant="unstyled",
+ color_scheme="purple",
+ align="end",
+ width="100%",
+ padding_top=".5em",
+ ),
+ style=template_content_style,
+ min_h="100vh",
+ ),
+ style=template_page_style,
+ min_h="100vh",
+ )
diff --git a/reflex/.templates/apps/demo/code/pages/home.py b/reflex/.templates/apps/demo/code/pages/home.py
new file mode 100644
index 000000000..3b94dfe0a
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/pages/home.py
@@ -0,0 +1,55 @@
+"""The home page of the app."""
+import reflex as rx
+
+from ..styles import *
+
+
+def home_page() -> rx.Component:
+ """The UI for the home page.
+
+ Returns:
+ rx.Component: The UI for the home page.
+ """
+ return rx.box(
+ rx.vstack(
+ rx.heading(
+ "Welcome to Reflex! 👋",
+ font_size="3em",
+ ),
+ rx.text(
+ "Reflex is an open-source app framework built specifically to allow you to build web apps in pure python. 👈 Select a demo from the sidebar to see some examples of what Reflex can do!",
+ ),
+ rx.heading(
+ "Things to check out:",
+ font_size="2em",
+ ),
+ rx.unordered_list(
+ rx.list_item(
+ "Take a look at ",
+ rx.link(
+ "reflex.dev",
+ href="https://reflex.dev",
+ color="rgb(107,99,246)",
+ ),
+ ),
+ rx.list_item(
+ "Check out our ",
+ rx.link(
+ "docs",
+ href="https://reflex.dev/docs/getting-started/introduction/",
+ color="rgb(107,99,246)",
+ ),
+ ),
+ rx.list_item(
+ "Ask a question in our ",
+ rx.link(
+ "community",
+ href="https://discord.gg/T5WSbC2YtQ",
+ color="rgb(107,99,246)",
+ ),
+ ),
+ ),
+ style=template_content_style,
+ ),
+ style=template_page_style,
+ )
diff --git a/reflex/.templates/apps/demo/code/sidebar.py b/reflex/.templates/apps/demo/code/sidebar.py
new file mode 100644
index 000000000..e4cd1672a
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/sidebar.py
@@ -0,0 +1,177 @@
+"""Sidebar component for the app."""
+
+import reflex as rx
+
+from .state import State
+from .styles import *
+
+
+def sidebar_header() -> rx.Component:
+ """Sidebar header.
+
+ Returns:
+ rx.Component: The sidebar header component.
+ """
+ return rx.hstack(
+ rx.image(
+ src="/icon.svg",
+ height="2em",
+ ),
+ rx.spacer(),
+ rx.link(
+ rx.center(
+ rx.image(
+ src="/github.svg",
+ height="3em",
+ padding="0.5em",
+ ),
+ box_shadow=box_shadow,
+ bg="transparent",
+ border_radius=border_radius,
+ _hover={
+ "bg": accent_color,
+ },
+ ),
+ href="https://github.com/reflex-dev/reflex",
+ ),
+ width="100%",
+ border_bottom=border,
+ padding="1em",
+ )
+
+
+def sidebar_footer() -> rx.Component:
+ """Sidebar footer.
+
+ Returns:
+ rx.Component: The sidebar footer component.
+ """
+ return rx.hstack(
+ rx.link(
+ rx.center(
+ rx.image(
+ src="/paneleft.svg",
+ height="2em",
+ padding="0.5em",
+ ),
+ bg="transparent",
+ border_radius=border_radius,
+ **hover_accent_bg,
+ ),
+ on_click=State.toggle_sidebar_displayed,
+ transform=rx.cond(~State.sidebar_displayed, "rotate(180deg)", ""),
+ transition="transform 0.5s, left 0.5s",
+ position="relative",
+ left=rx.cond(State.sidebar_displayed, "0px", "20.5em"),
+ **overlapping_button_style,
+ ),
+ rx.spacer(),
+ rx.link(
+ rx.text(
+ "Docs",
+ ),
+ href="https://reflex.dev/docs/getting-started/introduction/",
+ ),
+ rx.link(
+ rx.text(
+ "Blog",
+ ),
+ href="https://reflex.dev/blog/",
+ ),
+ width="100%",
+ border_top=border,
+ padding="1em",
+ )
+
+
+def sidebar_item(text: str, icon: str, url: str) -> rx.Component:
+ """Sidebar item.
+
+ Args:
+ text (str): The text of the item.
+ icon (str): The icon of the item.
+ url (str): The URL of the item.
+
+ Returns:
+ rx.Component: The sidebar item component.
+ """
+ return rx.link(
+ rx.hstack(
+ rx.image(
+ src=icon,
+ height="2.5em",
+ padding="0.5em",
+ ),
+ rx.text(
+ text,
+ ),
+ bg=rx.cond(
+ State.origin_url == f"/{text.lower()}/",
+ accent_color,
+ "transparent",
+ ),
+ color=rx.cond(
+ State.origin_url == f"/{text.lower()}/",
+ accent_text_color,
+ text_color,
+ ),
+ border_radius=border_radius,
+ box_shadow=box_shadow,
+ width="100%",
+ padding_x="1em",
+ ),
+ href=url,
+ width="100%",
+ )
+
+
+def sidebar() -> rx.Component:
+ """Sidebar.
+
+ Returns:
+ rx.Component: The sidebar component.
+ """
+ return rx.box(
+ rx.vstack(
+ sidebar_header(),
+ rx.vstack(
+ sidebar_item(
+ "Welcome",
+ "/github.svg",
+ "/",
+ ),
+ sidebar_item(
+ "Graphing Demo",
+ "/github.svg",
+ "/graphing",
+ ),
+ sidebar_item(
+ "Data Table Demo",
+ "/github.svg",
+ "/datatable",
+ ),
+ sidebar_item(
+ "Forms Demo",
+ "/github.svg",
+ "/forms",
+ ),
+ sidebar_item(
+ "Chat App Demo",
+ "/github.svg",
+ "/chatapp",
+ ),
+ width="100%",
+ overflow_y="auto",
+ align_items="flex-start",
+ padding="1em",
+ ),
+ rx.spacer(),
+ sidebar_footer(),
+ height="100dvh",
+ ),
+ min_width=sidebar_width,
+ height="100%",
+ position="sticky",
+ top="0px",
+ border_right=border,
+ )
diff --git a/reflex/.templates/apps/demo/code/state.py b/reflex/.templates/apps/demo/code/state.py
new file mode 100644
index 000000000..a5c6f57bd
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/state.py
@@ -0,0 +1,22 @@
+"""Base state for the app."""
+
+import reflex as rx
+
+
+class State(rx.State):
+ """State for the app."""
+
+ sidebar_displayed: bool = True
+
+ @rx.var
+ def origin_url(self) -> str:
+ """Get the url of the current page.
+
+ Returns:
+ str: The url of the current page.
+ """
+ return self.router_data.get("asPath", "")
+
+ def toggle_sidebar_displayed(self) -> None:
+ """Toggle the sidebar displayed."""
+ self.sidebar_displayed = not self.sidebar_displayed
diff --git a/reflex/.templates/apps/demo/code/states/form_state.py b/reflex/.templates/apps/demo/code/states/form_state.py
new file mode 100644
index 000000000..2b30e859e
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/states/form_state.py
@@ -0,0 +1,40 @@
+import reflex as rx
+
+from ..state import State
+
+
+class FormState(State):
+ """Form state."""
+
+ form_data: dict = {}
+
+ def handle_submit(self, form_data: dict):
+ """Handle the form submit.
+
+ Args:
+ form_data: The form data.
+ """
+ self.form_data = form_data
+
+
+class UploadState(State):
+ """The app state."""
+
+ # The images to show.
+ img: list[str]
+
+ async def handle_upload(self, files: list[rx.UploadFile]):
+ """Handle the upload of file(s).
+
+ Args:
+ files: The uploaded files.
+ """
+ for file in files:
+ upload_data = await file.read()
+ outfile = rx.get_asset_path(file.filename)
+ # Save the file.
+ with open(outfile, "wb") as file_object:
+ file_object.write(upload_data)
+
+ # Update the img var.
+ self.img.append(f"/{file.filename}")
diff --git a/reflex/.templates/apps/demo/code/states/pie_state.py b/reflex/.templates/apps/demo/code/states/pie_state.py
new file mode 100644
index 000000000..1c380c3af
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/states/pie_state.py
@@ -0,0 +1,47 @@
+from typing import Any
+
+import reflex as rx
+
+from ..state import State
+
+
+class PieChartState(State):
+ """Pie Chart State."""
+
+ resources: list[dict[str, Any]] = [
+ dict(type_="🏆", count=1),
+ dict(type_="🪵", count=1),
+ dict(type_="🥑", count=1),
+ dict(type_="🧱", count=1),
+ ]
+
+ @rx.cached_var
+ def resource_types(self) -> list[str]:
+ """Get the resource types.
+
+ Returns:
+ The resource types.
+ """
+ return [r["type_"] for r in self.resources]
+
+ def increment(self, type_: str):
+ """Increment the count of a resource type.
+
+ Args:
+ type_: The type of resource to increment.
+ """
+ for resource in self.resources:
+ if resource["type_"] == type_:
+ resource["count"] += 1
+ break
+
+ def decrement(self, type_: str):
+ """Decrement the count of a resource type.
+
+ Args:
+ type_: The type of resource to decrement.
+ """
+ for resource in self.resources:
+ if resource["type_"] == type_ and resource["count"] > 0:
+ resource["count"] -= 1
+ break
diff --git a/reflex/.templates/apps/demo/code/styles.py b/reflex/.templates/apps/demo/code/styles.py
new file mode 100644
index 000000000..934821ad6
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/styles.py
@@ -0,0 +1,67 @@
+"""Styles for the app."""
+import reflex as rx
+
+from .state import State
+
+border_radius = "0.375rem"
+box_shadow = "0px 0px 0px 1px rgba(84, 82, 95, 0.14)"
+border = "1px solid #F4F3F6"
+text_color = "black"
+accent_text_color = "#1A1060"
+accent_color = "#F5EFFE"
+hover_accent_color = {"_hover": {"color": accent_color}}
+hover_accent_bg = {"_hover": {"bg": accent_color}}
+content_width_vw = "90vw"
+sidebar_width = "20em"
+
+template_page_style = {
+ "padding_top": "5em",
+ "padding_x": "2em",
+}
+
+template_content_style = {
+ "width": rx.cond(
+ State.sidebar_displayed,
+ f"calc({content_width_vw} - {sidebar_width})",
+ content_width_vw,
+ ),
+ "min-width": sidebar_width,
+ "align_items": "flex-start",
+ "box_shadow": box_shadow,
+ "border_radius": border_radius,
+ "padding": "1em",
+ "margin_bottom": "2em",
+}
+
+link_style = {
+ "color": text_color,
+ "text_decoration": "none",
+ **hover_accent_color,
+}
+
+overlapping_button_style = {
+ "background_color": "white",
+ "border": border,
+ "border_radius": border_radius,
+}
+
+base_style = {
+ rx.MenuButton: {
+ "width": "3em",
+ "height": "3em",
+ **overlapping_button_style,
+ },
+ rx.MenuItem: hover_accent_bg,
+}
+
+tab_style = {
+ "color": "#494369",
+ "font_weight": 600,
+ "_selected": {
+ "color": "#5646ED",
+ "bg": "#F5EFFE",
+ "padding_x": "0.5em",
+ "padding_y": "0.25em",
+ "border_radius": "8px",
+ },
+}
diff --git a/reflex/.templates/apps/demo/code/webui/__init__.py b/reflex/.templates/apps/demo/code/webui/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/reflex/.templates/apps/demo/code/webui/components/__init__.py b/reflex/.templates/apps/demo/code/webui/components/__init__.py
new file mode 100644
index 000000000..e29eb0ab2
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/webui/components/__init__.py
@@ -0,0 +1,4 @@
+from .loading_icon import loading_icon
+from .modal import modal
+from .navbar import navbar
+from .sidebar import sidebar
diff --git a/reflex/.templates/apps/demo/code/webui/components/chat.py b/reflex/.templates/apps/demo/code/webui/components/chat.py
new file mode 100644
index 000000000..9ac6ddf9e
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/webui/components/chat.py
@@ -0,0 +1,118 @@
+import reflex as rx
+
+from ...webui import styles
+from ...webui.components import loading_icon
+from ...webui.state import QA, State
+
+
+def message(qa: QA) -> rx.Component:
+ """A single question/answer message.
+
+ Args:
+ qa: The question/answer pair.
+
+ Returns:
+ A component displaying the question/answer pair.
+ """
+ return rx.box(
+ rx.box(
+ rx.text(
+ qa.question,
+ bg=styles.border_color,
+ shadow=styles.shadow_light,
+ **styles.message_style,
+ ),
+ text_align="right",
+ margin_top="1em",
+ ),
+ rx.box(
+ rx.text(
+ qa.answer,
+ bg=styles.accent_color,
+ shadow=styles.shadow_light,
+ **styles.message_style,
+ ),
+ text_align="left",
+ padding_top="1em",
+ ),
+ width="100%",
+ )
+
+
+def chat() -> rx.Component:
+ """List all the messages in a single conversation.
+
+ Returns:
+ A component displaying all the messages in a single conversation.
+ """
+ return rx.vstack(
+ rx.box(rx.foreach(State.chats[State.current_chat], message)),
+ py="8",
+ flex="1",
+ width="100%",
+ max_w="3xl",
+ padding_x="4",
+ align_self="center",
+ overflow="hidden",
+ padding_bottom="5em",
+ **styles.base_style[rx.Vstack],
+ )
+
+
+def action_bar() -> rx.Component:
+ """The action bar to send a new message.
+
+ Returns:
+ The action bar to send a new message.
+ """
+ return rx.box(
+ rx.vstack(
+ rx.form(
+ rx.form_control(
+ rx.hstack(
+ rx.input(
+ placeholder="Type something...",
+ value=State.question,
+ on_change=State.set_question,
+ _placeholder={"color": "#fffa"},
+ _hover={"border_color": styles.accent_color},
+ style=styles.input_style,
+ ),
+ rx.button(
+ rx.cond(
+ State.processing,
+ loading_icon(height="1em"),
+ rx.text("Send"),
+ ),
+ type_="submit",
+ _hover={"bg": styles.accent_color},
+ style=styles.input_style,
+ ),
+ **styles.base_style[rx.Hstack],
+ ),
+ is_disabled=State.processing,
+ ),
+ on_submit=State.process_question,
+ width="100%",
+ ),
+ rx.text(
+ "ReflexGPT may return factually incorrect or misleading responses. Use discretion.",
+ font_size="xs",
+ color="#fff6",
+ text_align="center",
+ ),
+ width="100%",
+ max_w="3xl",
+ mx="auto",
+ **styles.base_style[rx.Vstack],
+ ),
+ position="sticky",
+ bottom="0",
+ left="0",
+ py="4",
+ backdrop_filter="auto",
+ backdrop_blur="lg",
+ border_top=f"1px solid {styles.border_color}",
+ align_items="stretch",
+ width="100%",
+ )
diff --git a/reflex/.templates/apps/demo/code/webui/components/loading_icon.py b/reflex/.templates/apps/demo/code/webui/components/loading_icon.py
new file mode 100644
index 000000000..2678e8fa9
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/webui/components/loading_icon.py
@@ -0,0 +1,26 @@
+import reflex as rx
+
+
+class LoadingIcon(rx.Component):
+ """A custom loading icon component."""
+
+ library = "react-loading-icons"
+ tag = "SpinningCircles"
+ stroke: rx.Var[str]
+ stroke_opacity: rx.Var[str]
+ fill: rx.Var[str]
+ fill_opacity: rx.Var[str]
+ stroke_width: rx.Var[str]
+ speed: rx.Var[str]
+ height: rx.Var[str]
+
+ def get_event_triggers(self) -> dict:
+ """Get the event triggers that pass the component's value to the handler.
+
+ Returns:
+ A dict mapping the event trigger to the var that is passed to the handler.
+ """
+ return {"on_change": lambda status: [status]}
+
+
+loading_icon = LoadingIcon.create
diff --git a/reflex/.templates/apps/demo/code/webui/components/modal.py b/reflex/.templates/apps/demo/code/webui/components/modal.py
new file mode 100644
index 000000000..ce51b9b6b
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/webui/components/modal.py
@@ -0,0 +1,56 @@
+import reflex as rx
+
+from ...webui.state import State
+
+
+def modal() -> rx.Component:
+ """A modal to create a new chat.
+
+ Returns:
+ The modal component.
+ """
+ return rx.modal(
+ rx.modal_overlay(
+ rx.modal_content(
+ rx.modal_header(
+ rx.hstack(
+ rx.text("Create new chat"),
+ rx.icon(
+ tag="close",
+ font_size="sm",
+ on_click=State.toggle_modal,
+ color="#fff8",
+ _hover={"color": "#fff"},
+ cursor="pointer",
+ ),
+ align_items="center",
+ justify_content="space-between",
+ )
+ ),
+ rx.modal_body(
+ rx.input(
+ placeholder="Type something...",
+ on_blur=State.set_new_chat_name,
+ bg="#222",
+ border_color="#fff3",
+ _placeholder={"color": "#fffa"},
+ ),
+ ),
+ rx.modal_footer(
+ rx.button(
+ "Create",
+ bg="#5535d4",
+ box_shadow="md",
+ px="4",
+ py="2",
+ h="auto",
+ _hover={"bg": "#4c2db3"},
+ on_click=State.create_chat,
+ ),
+ ),
+ bg="#222",
+ color="#fff",
+ ),
+ ),
+ is_open=State.modal_open,
+ )
diff --git a/reflex/.templates/apps/demo/code/webui/components/navbar.py b/reflex/.templates/apps/demo/code/webui/components/navbar.py
new file mode 100644
index 000000000..35c3e2522
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/webui/components/navbar.py
@@ -0,0 +1,68 @@
+import reflex as rx
+
+from ...webui import styles
+from ...webui.state import State
+
+
+def navbar():
+ return rx.box(
+ rx.hstack(
+ rx.hstack(
+ rx.icon(
+ tag="hamburger",
+ mr=4,
+ on_click=State.toggle_drawer,
+ cursor="pointer",
+ ),
+ rx.link(
+ rx.box(
+ rx.image(src="favicon.ico", width=30, height="auto"),
+ p="1",
+ border_radius="6",
+ bg="#F0F0F0",
+ mr="2",
+ ),
+ href="/",
+ ),
+ rx.breadcrumb(
+ rx.breadcrumb_item(
+ rx.heading("ReflexGPT", size="sm"),
+ ),
+ rx.breadcrumb_item(
+ rx.text(State.current_chat, size="sm", font_weight="normal"),
+ ),
+ ),
+ ),
+ rx.hstack(
+ rx.button(
+ "+ New chat",
+ bg=styles.accent_color,
+ px="4",
+ py="2",
+ h="auto",
+ on_click=State.toggle_modal,
+ ),
+ rx.menu(
+ rx.menu_button(
+ rx.avatar(name="User", size="md"),
+ rx.box(),
+ ),
+ rx.menu_list(
+ rx.menu_item("Help"),
+ rx.menu_divider(),
+ rx.menu_item("Settings"),
+ ),
+ ),
+ spacing="8",
+ ),
+ justify="space-between",
+ ),
+ bg=styles.bg_dark_color,
+ backdrop_filter="auto",
+ backdrop_blur="lg",
+ p="4",
+ border_bottom=f"1px solid {styles.border_color}",
+ position="sticky",
+ top="0",
+ z_index="100",
+ )
diff --git a/reflex/.templates/apps/demo/code/webui/components/sidebar.py b/reflex/.templates/apps/demo/code/webui/components/sidebar.py
new file mode 100644
index 000000000..91b7acd8d
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/webui/components/sidebar.py
@@ -0,0 +1,66 @@
+import reflex as rx
+
+from ...webui import styles
+from ...webui.state import State
+
+
+def sidebar_chat(chat: str) -> rx.Component:
+ """A sidebar chat item.
+
+ Args:
+ chat: The chat item.
+
+ Returns:
+ The sidebar chat item.
+ """
+ return rx.hstack(
+ rx.box(
+ chat,
+ on_click=lambda: State.set_chat(chat),
+ style=styles.sidebar_style,
+ color=styles.icon_color,
+ flex="1",
+ ),
+ rx.box(
+ rx.icon(
+ tag="delete",
+ style=styles.icon_style,
+ on_click=State.delete_chat,
+ ),
+ style=styles.sidebar_style,
+ ),
+ color=styles.text_light_color,
+ cursor="pointer",
+ )
+
+
+def sidebar() -> rx.Component:
+ """The sidebar component.
+
+ Returns:
+ The sidebar component.
+ """
+ return rx.drawer(
+ rx.drawer_overlay(
+ rx.drawer_content(
+ rx.drawer_header(
+ rx.hstack(
+ rx.text("Chats"),
+ rx.icon(
+ tag="close",
+ on_click=State.toggle_drawer,
+ style=styles.icon_style,
+ ),
+ )
+ ),
+ rx.drawer_body(
+ rx.vstack(
+ rx.foreach(State.chat_titles, lambda chat: sidebar_chat(chat)),
+ align_items="stretch",
+ )
+ ),
+ ),
+ ),
+ placement="left",
+ is_open=State.drawer_open,
+ )
diff --git a/reflex/.templates/apps/demo/code/webui/state.py b/reflex/.templates/apps/demo/code/webui/state.py
new file mode 100644
index 000000000..4956e6f59
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/webui/state.py
@@ -0,0 +1,145 @@
+import asyncio
+
+import reflex as rx
+
+from ..state import State
+
+# openai.api_key = os.environ["OPENAI_API_KEY"]
+# openai.api_base = os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1")
+
+
+class QA(rx.Base):
+ """A question and answer pair."""
+
+ question: str
+ answer: str
+
+
+DEFAULT_CHATS = {
+ "Intros": [],
+}
+
+
+class State(State):
+ """The app state."""
+
+ # A dict from the chat name to the list of questions and answers.
+ chats: dict[str, list[QA]] = DEFAULT_CHATS
+
+ # The current chat name.
+ current_chat = "Intros"
+
+ # The current question.
+ question: str
+
+ # Whether we are processing the question.
+ processing: bool = False
+
+ # The name of the new chat.
+ new_chat_name: str = ""
+
+ # Whether the drawer is open.
+ drawer_open: bool = False
+
+ # Whether the modal is open.
+ modal_open: bool = False
+
+ def create_chat(self):
+ """Create a new chat."""
+ # Add the new chat to the list of chats.
+ self.current_chat = self.new_chat_name
+ self.chats[self.new_chat_name] = []
+
+ # Toggle the modal.
+ self.modal_open = False
+
+ def toggle_modal(self):
+ """Toggle the new chat modal."""
+ self.modal_open = not self.modal_open
+
+ def toggle_drawer(self):
+ """Toggle the drawer."""
+ self.drawer_open = not self.drawer_open
+
+ def delete_chat(self):
+ """Delete the current chat."""
+ del self.chats[self.current_chat]
+ if len(self.chats) == 0:
+ self.chats = DEFAULT_CHATS
+ self.current_chat = list(self.chats.keys())[0]
+ self.toggle_drawer()
+
+ def set_chat(self, chat_name: str):
+ """Set the name of the current chat.
+
+ Args:
+ chat_name: The name of the chat.
+ """
+ self.current_chat = chat_name
+ self.toggle_drawer()
+
+ @rx.var
+ def chat_titles(self) -> list[str]:
+ """Get the list of chat titles.
+
+ Returns:
+ The list of chat names.
+ """
+ return list(self.chats.keys())
+
+ async def process_question(self, form_data: dict[str, str]):
+ """Get the response from the API.
+
+ Args:
+ form_data: A dict with the current question.
+
+ Yields:
+ The current question and the response.
+ """
+ # Check if the question is empty
+ if self.question == "":
+ return
+
+ # Add the question to the list of questions.
+ qa = QA(question=self.question, answer="")
+ self.chats[self.current_chat].append(qa)
+
+ # Clear the input and start the processing.
+ self.processing = True
+ self.question = ""
+ yield
+
+ # # Build the messages.
+ # messages = [
+ # {"role": "system", "content": "You are a friendly chatbot named Reflex."}
+ # ]
+ # for qa in self.chats[self.current_chat]:
+ # messages.append({"role": "user", "content": qa.question})
+ # messages.append({"role": "assistant", "content": qa.answer})
+
+ # # Remove the last mock answer.
+ # messages = messages[:-1]
+
+ # Start a new session to answer the question.
+ # session = openai.ChatCompletion.create(
+ # model=os.getenv("OPENAI_MODEL", "gpt-3.5-turbo"),
+ # messages=messages,
+ # stream=True,
+ # )
+
+ # Stream the results, yielding after every word.
+ # for item in session:
+ answer = "I don't know! This Chatbot still needs to add in AI API keys!"
+ for i in range(len(answer)):
+ # Pause to show the streaming effect.
+ await asyncio.sleep(0.1)
+ # Add one letter at a time to the output.
+
+ # if hasattr(item.choices[0].delta, "content"):
+ # answer_text = item.choices[0].delta.content
+ self.chats[self.current_chat][-1].answer += answer[i]
+ self.chats = self.chats
+ yield
+
+ # Toggle the processing flag.
+ self.processing = False
diff --git a/reflex/.templates/apps/demo/code/webui/styles.py b/reflex/.templates/apps/demo/code/webui/styles.py
new file mode 100644
index 000000000..7abc579fb
--- /dev/null
+++ b/reflex/.templates/apps/demo/code/webui/styles.py
@@ -0,0 +1,88 @@
+import reflex as rx
+
+bg_dark_color = "#111"
+bg_medium_color = "#222"
+
+border_color = "#fff3"
+
+accennt_light = "#6649D8"
+accent_color = "#5535d4"
+accent_dark = "#4c2db3"
+
+icon_color = "#fff8"
+
+text_light_color = "#fff"
+shadow_light = "rgba(17, 12, 46, 0.15) 0px 48px 100px 0px;"
+shadow = "rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px, rgba(10, 37, 64, 0.35) 0px -2px 6px 0px inset;"
+
+message_style = dict(display="inline-block", p="4", border_radius="xl", max_w="30em")
+
+input_style = dict(
+ bg=bg_medium_color,
+ border_color=border_color,
+ border_width="1px",
+ p="4",
+)
+
+icon_style = dict(
+ font_size="md",
+ color=icon_color,
+ _hover=dict(color=text_light_color),
+ cursor="pointer",
+ w="8",
+)
+
+sidebar_style = dict(
+ border="double 1px transparent;",
+ border_radius="10px;",
+ background_image=f"linear-gradient({bg_dark_color}, {bg_dark_color}), radial-gradient(circle at top left, {accent_color},{accent_dark});",
+ background_origin="border-box;",
+ background_clip="padding-box, border-box;",
+ p="2",
+ _hover=dict(
+ background_image=f"linear-gradient({bg_dark_color}, {bg_dark_color}), radial-gradient(circle at top left, {accent_color},{accennt_light});",
+ ),
+)
+
+base_style = {
+ rx.Avatar: {
+ "shadow": shadow,
+ "color": text_light_color,
+ # "bg": border_color,
+ },
+ rx.Button: {
+ "shadow": shadow,
+ "color": text_light_color,
+ "_hover": {
+ "bg": accent_dark,
+ },
+ },
+ rx.Menu: {
+ "bg": bg_dark_color,
+ "border": f"red",
+ },
+ rx.MenuList: {
+ "bg": bg_dark_color,
+ "border": f"1.5px solid {bg_medium_color}",
+ },
+ rx.MenuDivider: {
+ "border": f"1px solid {bg_medium_color}",
+ },
+ rx.MenuItem: {
+ "bg": bg_dark_color,
+ "color": text_light_color,
+ },
+ rx.DrawerContent: {
+ "bg": bg_dark_color,
+ "color": text_light_color,
+ "opacity": "0.9",
+ },
+ rx.Hstack: {
+ "align_items": "center",
+ "justify_content": "space-between",
+ },
+ rx.Vstack: {
+ "align_items": "stretch",
+ "justify_content": "space-between",
+ },
+}