浏览代码

add_style api (#3202)

Thomas Brandého 1 年之前
父节点
当前提交
57476966f3
共有 32 个文件被更改,包括 883 次插入616 次删除
  1. 1 3
      reflex/app.py
  2. 9 0
      reflex/compiler/compiler.py
  3. 97 20
      reflex/components/component.py
  4. 0 9
      reflex/components/core/cond.py
  5. 1 16
      reflex/components/core/foreach.py
  6. 0 15
      reflex/components/core/match.py
  7. 5 2
      reflex/components/datadisplay/code.py
  8. 1 0
      reflex/components/datadisplay/code.pyi
  9. 2 2
      reflex/components/markdown/markdown.py
  10. 0 1
      reflex/components/markdown/markdown.pyi
  11. 225 404
      reflex/components/radix/primitives/accordion.py
  12. 367 26
      reflex/components/radix/primitives/accordion.pyi
  13. 30 26
      reflex/components/radix/primitives/form.py
  14. 6 1
      reflex/components/radix/primitives/form.pyi
  15. 17 9
      reflex/components/radix/primitives/progress.py
  16. 2 0
      reflex/components/radix/primitives/progress.pyi
  17. 30 18
      reflex/components/radix/primitives/slider.py
  18. 4 0
      reflex/components/radix/primitives/slider.pyi
  19. 13 11
      reflex/components/radix/themes/components/icon_button.py
  20. 2 0
      reflex/components/radix/themes/components/icon_button.pyi
  21. 8 3
      reflex/components/radix/themes/layout/center.py
  22. 2 1
      reflex/components/radix/themes/layout/center.pyi
  23. 8 3
      reflex/components/radix/themes/layout/list.py
  24. 3 19
      reflex/components/radix/themes/layout/list.pyi
  25. 8 3
      reflex/components/radix/themes/layout/spacer.py
  26. 2 1
      reflex/components/radix/themes/layout/spacer.pyi
  27. 2 0
      reflex/constants/colors.py
  28. 24 6
      reflex/experimental/layout.py
  29. 6 1
      reflex/style.py
  30. 1 10
      tests/components/core/test_foreach.py
  31. 3 4
      tests/components/lucide/test_icon.py
  32. 4 2
      tests/components/radix/test_icon_button.py

+ 1 - 3
reflex/app.py

@@ -833,9 +833,7 @@ class App(Base):
 
         for _route, component in self.pages.items():
             # Merge the component style with the app style.
-            component._add_style_recursive(self.style)
-
-            component.apply_theme(self.theme)
+            component._add_style_recursive(self.style, self.theme)
 
             # Add component._get_all_imports() to all_imports.
             all_imports.update(component._get_all_imports())

+ 9 - 0
reflex/compiler/compiler.py

@@ -263,9 +263,18 @@ def _compile_stateful_components(
             # Reset this flag to render the actual component.
             component.rendered_as_shared = False
 
+            # Include dynamic imports in the shared component.
+            if dynamic_imports := component._get_all_dynamic_imports():
+                rendered_components.update(
+                    {dynamic_import: None for dynamic_import in dynamic_imports}
+                )
+
+            # Include custom code in the shared component.
             rendered_components.update(
                 {code: None for code in component._get_all_custom_code()},
             )
+
+            # Include all imports in the shared component.
             all_import_dicts.append(component._get_all_imports())
 
             # Indicate that this component now imports from the shared file.

+ 97 - 20
reflex/components/component.py

@@ -608,6 +608,8 @@ class Component(BaseComponent, ABC):
     def _apply_theme(self, theme: Optional[Component]):
         """Apply the theme to this component.
 
+        Deprecated. Use add_style instead.
+
         Args:
             theme: The theme to apply.
         """
@@ -779,44 +781,119 @@ class Component(BaseComponent, ABC):
 
         return cls(children=children, **props)
 
-    def _add_style(self, style: dict):
-        """Add additional style to the component.
+    def add_style(self) -> Style | None:
+        """Add style to the component.
 
-        Args:
-            style: A style dict to apply.
+        Downstream components can override this method to return a style dict
+        that will be applied to the component.
+
+        Returns:
+            The style to add.
+        """
+        return None
+
+    def _add_style(self) -> Style:
+        """Call add_style for all bases in the MRO.
+
+        Downstream components should NOT override. Use add_style instead.
+
+        Returns:
+            The style to add.
         """
-        self.style.update(style)
+        styles = []
+        vars = []
+
+        # Walk the MRO to call all `add_style` methods.
+        for base in self._iter_parent_classes_with_method("add_style"):
+            s = base.add_style(self)  # type: ignore
+            if s is not None:
+                styles.append(s)
+                vars.append(s._var_data)
+
+        _style = Style()
+        for s in reversed(styles):
+            _style.update(s)
+
+        _style._var_data = VarData.merge(*vars)
+        return _style
+
+    def _get_component_style(self, styles: ComponentStyle) -> Style | None:
+        """Get the style to the component from `App.style`.
 
-    def _add_style_recursive(self, style: ComponentStyle) -> Component:
+        Args:
+            styles: The style to apply.
+
+        Returns:
+            The style of the component.
+        """
+        component_style = None
+        if type(self) in styles:
+            component_style = Style(styles[type(self)])
+        if self.create in styles:
+            component_style = Style(styles[self.create])
+        return component_style
+
+    def _add_style_recursive(
+        self, style: ComponentStyle, theme: Optional[Component] = None
+    ) -> Component:
         """Add additional style to the component and its children.
 
+        Apply order is as follows (with the latest overriding the earliest):
+        1. Default style from `_add_style`/`add_style`.
+        2. User-defined style from `App.style`.
+        3. User-defined style from `Component.style`.
+        4. style dict and css props passed to the component instance.
+
         Args:
             style: A dict from component to styling.
+            theme: The theme to apply. (for retro-compatibility with deprecated _apply_theme API)
+
+        Raises:
+            UserWarning: If `_add_style` has been overridden.
 
         Returns:
             The component with the additional style.
         """
-        component_style = None
-        if type(self) in style:
-            # Extract the style for this component.
-            component_style = Style(style[type(self)])
-        if self.create in style:
-            component_style = Style(style[self.create])
-        if component_style is not None:
-            # Only add style props that are not overridden.
-            component_style = {
-                k: v for k, v in component_style.items() if k not in self.style
-            }
+        # 1. Default style from `_add_style`/`add_style`.
+        if type(self)._add_style != Component._add_style:
+            raise UserWarning(
+                "Do not override _add_style directly. Use add_style instead."
+            )
+        new_style = self._add_style()
+        style_vars = [new_style._var_data]
+
+        # 2. User-defined style from `App.style`.
+        component_style = self._get_component_style(style)
+        if component_style:
+            new_style.update(component_style)
+            style_vars.append(component_style._var_data)
+
+        # 3. User-defined style from `Component.style`.
+        # Apply theme for retro-compatibility with deprecated _apply_theme API
+        if type(self)._apply_theme != Component._apply_theme:
+            console.deprecate(
+                f"{self.__class__.__name__}._apply_theme",
+                reason="use add_style instead",
+                deprecation_version="0.5.0",
+                removal_version="0.6.0",
+            )
+            self._apply_theme(theme)
+
+        # 4. style dict and css props passed to the component instance.
+        new_style.update(self.style)
+        style_vars.append(self.style._var_data)
+
+        new_style._var_data = VarData.merge(*style_vars)
 
-            # Add the style to the component.
-            self._add_style(component_style)
+        # Assign the new style
+        self.style = new_style
 
         # Recursively add style to the children.
         for child in self.children:
             # Skip BaseComponent and StatefulComponent children.
             if not isinstance(child, Component):
                 continue
-            child._add_style_recursive(style)
+            child._add_style_recursive(style, theme)
         return self
 
     def _get_style(self) -> dict:

+ 0 - 9
reflex/components/core/cond.py

@@ -102,15 +102,6 @@ class Cond(MemoizationLeaf):
             _IS_TRUE_IMPORT,
         )
 
-    def _apply_theme(self, theme: Component):
-        """Apply the theme to this component.
-
-        Args:
-            theme: The theme to apply.
-        """
-        self.comp1.apply_theme(theme)  # type: ignore
-        self.comp2.apply_theme(theme)  # type: ignore
-
 
 @overload
 def cond(condition: Any, c1: Component, c2: Any) -> Component:

+ 1 - 16
reflex/components/core/foreach.py

@@ -3,7 +3,7 @@ from __future__ import annotations
 
 import inspect
 from hashlib import md5
-from typing import Any, Callable, Iterable, Optional
+from typing import Any, Callable, Iterable
 
 from reflex.components.base.fragment import Fragment
 from reflex.components.component import Component
@@ -23,17 +23,6 @@ class Foreach(Component):
     # A function from the render args to the component.
     render_fn: Callable = Fragment.create
 
-    # The theme if set.
-    theme: Optional[Component] = None
-
-    def _apply_theme(self, theme: Component):
-        """Apply the theme to this component.
-
-        Args:
-            theme: The theme to apply.
-        """
-        self.theme = theme
-
     @classmethod
     def create(cls, iterable: Var[Iterable], render_fn: Callable, **props) -> Foreach:
         """Create a foreach component.
@@ -97,10 +86,6 @@ class Foreach(Component):
         tag = self._render()
         component = tag.render_component()
 
-        # Apply the theme to the children.
-        if self.theme is not None:
-            component.apply_theme(self.theme)
-
         return dict(
             tag.add_props(
                 **self.event_triggers,

+ 0 - 15
reflex/components/core/match.py

@@ -273,18 +273,3 @@ class Match(MemoizationLeaf):
             super()._get_imports(),
             getattr(self.cond._var_data, "imports", {}),
         )
-
-    def _apply_theme(self, theme: Component):
-        """Apply the theme to this component.
-
-        Args:
-            theme: The theme to apply.
-        """
-        # apply theme to return components.
-        for match_case in self.match_cases:
-            if isinstance(match_case[-1], Component):
-                match_case[-1].apply_theme(theme)
-
-        # apply theme to default component
-        if isinstance(self.default, Component):
-            self.default.apply_theme(theme)

+ 5 - 2
reflex/components/datadisplay/code.py

@@ -1,4 +1,6 @@
 """A code component."""
+from __future__ import annotations
+
 import re
 from typing import Dict, Literal, Optional, Union
 
@@ -491,8 +493,9 @@ class CodeBlock(Component):
         else:
             return code_block
 
-    def _add_style(self, style):
-        self.custom_style.update(style)  # type: ignore
+    def add_style(self) -> Style | None:
+        """Add style to the component."""
+        self.custom_style.update(self.style)
 
     def _render(self):
         out = super()._render()

+ 1 - 0
reflex/components/datadisplay/code.pyi

@@ -1111,5 +1111,6 @@ class CodeBlock(Component):
             The text component.
         """
         ...
+    def add_style(self) -> Style | None: ...
     @staticmethod
     def convert_theme_name(theme) -> str: ...

+ 2 - 2
reflex/components/markdown/markdown.py

@@ -18,7 +18,6 @@ from reflex.components.radix.themes.typography.heading import Heading
 from reflex.components.radix.themes.typography.link import Link
 from reflex.components.radix.themes.typography.text import Text
 from reflex.components.tags.tag import Tag
-from reflex.style import Style
 from reflex.utils import console, imports, types
 from reflex.utils.imports import ImportVar
 from reflex.vars import Var
@@ -230,7 +229,8 @@ class Markdown(Component):
         component = self.component_map[tag](*children, **props).set(
             special_props=special_props
         )
-        component._add_style(Style(self.custom_styles.get(tag, {})))
+        component.style.update(self.custom_styles.get(tag, {}))
+
         return component
 
     def format_component(self, tag: str, **props) -> str:

+ 0 - 1
reflex/components/markdown/markdown.pyi

@@ -22,7 +22,6 @@ from reflex.components.radix.themes.typography.heading import Heading
 from reflex.components.radix.themes.typography.link import Link
 from reflex.components.radix.themes.typography.text import Text
 from reflex.components.tags.tag import Tag
-from reflex.style import Style
 from reflex.utils import console, imports, types
 from reflex.utils.imports import ImportVar
 from reflex.vars import Var

+ 225 - 404
reflex/components/radix/primitives/accordion.py

@@ -5,302 +5,42 @@ from __future__ import annotations
 from typing import Any, Dict, List, Literal, Optional, Union
 
 from reflex.components.component import Component, ComponentNamespace
-from reflex.components.core.match import Match
+from reflex.components.core.colors import color
 from reflex.components.lucide.icon import Icon
 from reflex.components.radix.primitives.base import RadixPrimitiveComponent
 from reflex.components.radix.themes.base import LiteralAccentColor
-from reflex.style import (
-    Style,
-    convert_dict_to_style_and_format_emotion,
-    format_as_emotion,
-)
+from reflex.style import Style
 from reflex.utils import imports
-from reflex.vars import BaseVar, Var, VarData, get_uuid_string_var
+from reflex.vars import Var, get_uuid_string_var
 
 LiteralAccordionType = Literal["single", "multiple"]
 LiteralAccordionDir = Literal["ltr", "rtl"]
 LiteralAccordionOrientation = Literal["vertical", "horizontal"]
-LiteralAccordionRootVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
-LiteralAccordionRootColorScheme = Literal["primary", "accent"]
+LiteralAccordionVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
 
 DEFAULT_ANIMATION_DURATION = 250
 
 
-def get_theme_accordion_root(variant: Var[str], color_scheme: Var[str]) -> BaseVar:
-    """Get the theme for the accordion root component.
-
-    Args:
-        variant: The variant of the accordion.
-        color_scheme: The color of the accordion.
-
-    Returns:
-        The theme for the accordion root component.
-    """
-    return Match.create(  # type: ignore
-        variant,
-        (
-            "soft",
-            convert_dict_to_style_and_format_emotion(
-                {
-                    "border_radius": "6px",
-                    "background_color": f"var(--{color_scheme}-3)",
-                    "box_shadow": "0 2px 10px var(--black-a1)",
-                }
-            ),
-        ),
-        (
-            "outline",
-            convert_dict_to_style_and_format_emotion(
-                {
-                    "border_radius": "6px",
-                    "border": f"1px solid var(--{color_scheme}-6)",
-                    "box_shadow": "0 2px 10px var(--black-a1)",
-                }
-            ),
-        ),
-        (
-            "surface",
-            convert_dict_to_style_and_format_emotion(
-                {
-                    "border_radius": "6px",
-                    "border": f"1px solid var(--{color_scheme}-6)",
-                    "background_color": f"var(--{color_scheme}-3)",
-                    "box_shadow": "0 2px 10px var(--black-a1)",
-                }
-            ),
-        ),
-        (
-            "ghost",
-            convert_dict_to_style_and_format_emotion(
-                {
-                    "border_radius": "6px",
-                    "background_color": "none",
-                    "box_shadow": "None",
-                }
-            ),
-        ),
-        convert_dict_to_style_and_format_emotion(
-            {
-                "border_radius": "6px",
-                "background_color": f"var(--{color_scheme}-9)",
-                "box_shadow": "0 2px 10px var(--black-a4)",
-            }
-        ),
-        # defaults to classic
-    )
-
-
-def get_theme_accordion_item():
-    """Get the theme for the accordion item component.
-
-    Returns:
-        The theme for the accordion item component.
-    """
-    return convert_dict_to_style_and_format_emotion(
-        {
-            "overflow": "hidden",
-            "width": "100%",
-            "margin_top": "1px",
-            "&:first-child": {
-                "margin_top": 0,
-                "border_top_left_radius": "4px",
-                "border_top_right_radius": "4px",
-            },
-            "&:last-child": {
-                "border_bottom_left_radius": "4px",
-                "border_bottom_right_radius": "4px",
-            },
-            "&:focus-within": {
-                "position": "relative",
-                "z_index": 1,
-            },
-        }
-    )
-
+class AccordionComponent(RadixPrimitiveComponent):
+    """Base class for all @radix-ui/accordion components."""
 
-def get_theme_accordion_header() -> dict[str, str]:
-    """Get the theme for the accordion header component.
+    library = "@radix-ui/react-accordion@^1.1.2"
 
-    Returns:
-        The theme for the accordion header component.
-    """
-    return {
-        "display": "flex",
-    }
+    # The color scheme of the component.
+    color_scheme: Var[LiteralAccentColor]
 
+    # The variant of the component.
+    variant: Var[LiteralAccordionVariant] = Var.create_safe("classic")
 
-def get_theme_accordion_trigger(variant: str | Var, color_scheme: str | Var) -> BaseVar:
-    """Get the theme for the accordion trigger component.
+    def add_style(self) -> Style | None:
+        """Add style to the component."""
+        if self.color_scheme is not None:
+            self.custom_attrs["data-accent-color"] = self.color_scheme
 
-    Args:
-        variant: The variant of the accordion.
-        color_scheme: The color of the accordion.
+        self.custom_attrs["data-variant"] = self.variant
 
-    Returns:
-        The theme for the accordion trigger component.
-    """
-    return Match.create(  # type: ignore
-        variant,
-        (
-            "soft",
-            convert_dict_to_style_and_format_emotion(
-                {
-                    "color": f"var(--{color_scheme}-11)",
-                    "&:hover": {
-                        "background_color": f"var(--{color_scheme}-4)",
-                    },
-                    "& > .AccordionChevron": {
-                        "color": f"var(--{color_scheme}-11)",
-                        "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)",
-                    },
-                    "&[data-state='open'] > .AccordionChevron": {
-                        "transform": "rotate(180deg)",
-                    },
-                    "font_family": "inherit",
-                    "width": "100%",
-                    "padding": "0 20px",
-                    "height": "45px",
-                    "flex": 1,
-                    "display": "flex",
-                    "align_items": "center",
-                    "justify_content": "space-between",
-                    "font_size": "15px",
-                    "line_height": 1,
-                }
-            ),
-        ),
-        (
-            "outline",
-            "surface",
-            "ghost",
-            convert_dict_to_style_and_format_emotion(
-                {
-                    "color": f"var(--{color_scheme}-11)",
-                    "&:hover": {
-                        "background_color": f"var(--{color_scheme}-4)",
-                    },
-                    "& > .AccordionChevron": {
-                        "color": f"var(--{color_scheme}-11)",
-                        "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)",
-                    },
-                    "&[data-state='open'] > .AccordionChevron": {
-                        "transform": "rotate(180deg)",
-                    },
-                    "font_family": "inherit",
-                    "width": "100%",
-                    "padding": "0 20px",
-                    "height": "45px",
-                    "flex": 1,
-                    "display": "flex",
-                    "align_items": "center",
-                    "justify_content": "space-between",
-                    "font_size": "15px",
-                    "line_height": 1,
-                }
-            ),
-        ),
-        # defaults to classic
-        convert_dict_to_style_and_format_emotion(
-            {
-                "color": f"var(--{color_scheme}-9-contrast)",
-                "box_shadow": f"var(--{color_scheme}-11)",
-                "&:hover": {
-                    "background_color": f"var(--{color_scheme}-10)",
-                },
-                "& > .AccordionChevron": {
-                    "color": f"var(--{color_scheme}-9-contrast)",
-                    "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)",
-                },
-                "&[data-state='open'] > .AccordionChevron": {
-                    "transform": "rotate(180deg)",
-                },
-                "font_family": "inherit",
-                "width": "100%",
-                "padding": "0 20px",
-                "height": "45px",
-                "flex": 1,
-                "display": "flex",
-                "align_items": "center",
-                "justify_content": "space-between",
-                "font_size": "15px",
-                "line_height": 1,
-            }
-        ),
-    )
-
-
-def get_theme_accordion_content(variant: str | Var, color_scheme: str | Var) -> BaseVar:
-    """Get the theme for the accordion content component.
-
-    Args:
-        variant: The variant of the accordion.
-        color_scheme: The color of the accordion.
-
-    Returns:
-        The theme for the accordion content component.
-    """
-    return Match.create(  # type: ignore
-        variant,
-        (
-            "outline",
-            "ghost",
-            convert_dict_to_style_and_format_emotion(
-                {
-                    "overflow": "hidden",
-                    "font_size": "10px",
-                    "color": f"var(--{color_scheme}-11)",
-                    "padding": "15px 20px",
-                    "&[data-state='open']": {
-                        "animation": Var.create(
-                            f"${{slideDown}} {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)",
-                            _var_is_string=True,
-                        ),
-                    },
-                    "&[data-state='closed']": {
-                        "animation": Var.create(
-                            f"${{slideUp}} {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)",
-                            _var_is_string=True,
-                        ),
-                    },
-                }
-            ),
-        ),
-        convert_dict_to_style_and_format_emotion(
-            {
-                "overflow": "hidden",
-                "font_size": "10px",
-                "color": Match.create(
-                    variant,
-                    ("classic", f"var(--{color_scheme}-9-contrast)"),
-                    f"var(--{color_scheme}-11)",
-                ),
-                "background_color": Match.create(
-                    variant,
-                    ("classic", f"var(--{color_scheme}-9)"),
-                    f"var(--{color_scheme}-3)",
-                ),
-                "padding": "15px 20px",
-                "&[data-state='open']": {
-                    "animation": Var.create(
-                        f"${{slideDown}} {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)",
-                        _var_is_string=True,
-                    ),
-                },
-                "&[data-state='closed']": {
-                    "animation": Var.create(
-                        f"${{slideUp}} {DEFAULT_ANIMATION_DURATION}ms cubic-bezier(0.87, 0, 0.13, 1)",
-                        _var_is_string=True,
-                    ),
-                },
-            }
-        ),
-    )
-
-
-class AccordionComponent(RadixPrimitiveComponent):
-    """Base class for all @radix-ui/accordion components."""
-
-    library = "@radix-ui/react-accordion@^1.1.2"
+    def _exclude_props(self) -> list[str]:
+        return ["color_scheme", "variant"]
 
 
 class AccordionRoot(AccordionComponent):
@@ -332,16 +72,7 @@ class AccordionRoot(AccordionComponent):
     orientation: Var[LiteralAccordionOrientation]
 
     # The variant of the accordion.
-    variant: Var[LiteralAccordionRootVariant] = "classic"  # type: ignore
-
-    # The color scheme of the accordion.
-    color_scheme: Var[LiteralAccentColor]  # type: ignore
-
-    # dynamic themes of the accordion generated at compile time.
-    _dynamic_themes: Var[dict] = Var.create({})  # type: ignore
-
-    # The var_data associated with the component.
-    _var_data: VarData = VarData()  # type: ignore
+    variant: Var[LiteralAccordionVariant] = Var.create_safe("classic")
 
     _valid_children: List[str] = ["AccordionItem"]
 
@@ -356,81 +87,12 @@ class AccordionRoot(AccordionComponent):
         Returns:
             The Accordion root Component.
         """
-        comp = super().create(*children, **props)
-
-        if comp.color_scheme is not None and not comp.color_scheme._var_state:  # type: ignore
-            # mark the vars of color string literals as strings so they can be formatted properly when performing a var operation.
-            comp.color_scheme._var_is_string = True  # type: ignore
-
-        if comp.variant is not None and not comp.variant._var_state:  # type: ignore
-            # mark the vars of variant string literals as strings so they are formatted properly in the match condition.
-            comp.variant._var_is_string = True  # type: ignore
-
-        return comp
-
-    def _get_style(self) -> dict:
-        """Get the style for the component.
-
-        Returns:
-            The dictionary of the component style as value and the style notation as key.
-        """
-        return {"css": self._dynamic_themes._merge(format_as_emotion(self.style))}  # type: ignore
-
-    def _apply_theme(self, theme: Component):
-        global_color_scheme = getattr(theme, "accent_color", None)
-
-        if global_color_scheme is None and self.color_scheme is None:
-            raise ValueError(
-                "`color_scheme` cannot be None. Either set the `color_scheme` prop on the accordion "
-                "component or set the `accent_color` prop in your global theme."
-            )
-
-        # prepare the color_scheme var to be used in an f-string(strip off the wrapping curly brace)
-        color_scheme = Var.create(
-            self.color_scheme if self.color_scheme is not None else global_color_scheme
-        )._replace(  # type: ignore
-            _var_is_string=False
-        )
+        for child in children:
+            if isinstance(child, AccordionItem):
+                child.color_scheme = props.get("color_scheme")  # type: ignore
+                child.variant = props.get("variant")  # type: ignore
 
-        accordion_theme_root = get_theme_accordion_root(
-            variant=self.variant, color_scheme=color_scheme
-        )
-        accordion_theme_content = get_theme_accordion_content(
-            variant=self.variant, color_scheme=color_scheme
-        )
-        accordion_theme_trigger = get_theme_accordion_trigger(
-            variant=self.variant, color_scheme=color_scheme
-        )
-
-        # extract var_data from dynamic themes.
-        self._var_data = (
-            self._var_data.merge(  # type: ignore
-                accordion_theme_trigger._var_data,
-                accordion_theme_content._var_data,
-                accordion_theme_root._var_data,
-            )
-            or self._var_data
-        )
-
-        self._dynamic_themes = Var.create(  # type: ignore
-            convert_dict_to_style_and_format_emotion(
-                {
-                    "& .AccordionItem": get_theme_accordion_item(),
-                    "& .AccordionHeader": get_theme_accordion_header(),
-                    "& .AccordionTrigger": accordion_theme_trigger,
-                    "& .AccordionContent": accordion_theme_content,
-                }
-            )
-        )._merge(  # type: ignore
-            accordion_theme_root
-        )
-
-    def _get_imports(self):
-        return imports.merge_imports(
-            super()._get_imports(),
-            self._var_data.imports if self._var_data else {},
-            {"@emotion/react": [imports.ImportVar(tag="keyframes")]},
-        )
+        return super().create(*children, **props)
 
     def get_event_triggers(self) -> Dict[str, Any]:
         """Get the events triggers signatures for the component.
@@ -443,28 +105,36 @@ class AccordionRoot(AccordionComponent):
             "on_value_change": lambda e0: [e0],
         }
 
-    def _get_custom_code(self) -> str:
-        return """
-const slideDown = keyframes`
-from {
-  height: 0;
-}
-to {
-  height: var(--radix-accordion-content-height);
-}
-`
-const slideUp = keyframes`
-from {
-  height: var(--radix-accordion-content-height);
-}
-to {
-  height: 0;
-}
-`
-"""
+    def add_style(self):
+        """Add style to the component.
 
-    def _exclude_props(self) -> list[str]:
-        return ["color_scheme", "variant"]
+        Returns:
+            The style of the component.
+        """
+        return Style(
+            {
+                "border_radius": "6px",
+                "box_shadow": f"0 2px 10px {color('black', 1, alpha=True)}",
+                "&[data-variant='classic']": {
+                    "background_color": color("accent", 9),
+                    "box_shadow": f"0 2px 10px {color('black', 4, alpha=True)}",
+                },
+                "&[data-variant='soft']": {
+                    "background_color": color("accent", 3),
+                },
+                "&[data-variant='outline']": {
+                    "border": f"1px solid {color('accent', 6)}",
+                },
+                "&[data-variant='surface']": {
+                    "border": f"1px solid {color('accent', 6)}",
+                    "background_color": color("accent", 3),
+                },
+                "&[data-variant='ghost']": {
+                    "background_color": "none",
+                    "box_shadow": "None",
+                },
+            }
+        )
 
 
 class AccordionItem(AccordionComponent):
@@ -488,13 +158,6 @@ class AccordionItem(AccordionComponent):
 
     _valid_parents: List[str] = ["AccordionRoot"]
 
-    def _apply_theme(self, theme: Component):
-        self.style = Style(
-            {
-                **self.style,
-            }
-        )
-
     @classmethod
     def create(
         cls,
@@ -506,9 +169,9 @@ class AccordionItem(AccordionComponent):
         """Create an accordion item.
 
         Args:
+            *children: The list of children to use if header and content are not provided.
             header: The header of the accordion item.
             content: The content of the accordion item.
-            *children: The list of children to use if header and content are not provided.
             **props: Additional properties to apply to the accordion item.
 
         Returns:
@@ -527,14 +190,55 @@ class AccordionItem(AccordionComponent):
                 AccordionHeader.create(
                     AccordionTrigger.create(
                         header,
-                        AccordionIcon.create(),
+                        AccordionIcon.create(
+                            color_scheme=props.get("color_scheme"),
+                            variant=props.get("variant"),
+                        ),
+                        color_scheme=props.get("color_scheme"),
+                        variant=props.get("variant"),
                     ),
+                    color_scheme=props.get("color_scheme"),
+                    variant=props.get("variant"),
+                ),
+                AccordionContent.create(
+                    content, color_scheme=props.get("color_scheme")
                 ),
-                AccordionContent.create(content),
             ]
 
         return super().create(*children, value=value, **props, class_name=cls_name)
 
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        for child in self.children:
+            if isinstance(child, (AccordionHeader, AccordionContent)):
+                child.color_scheme = self.color_scheme
+                child.variant = self.variant
+
+        return Style(
+            {
+                "overflow": "hidden",
+                "width": "100%",
+                "margin_top": "1px",
+                "&:first-child": {
+                    "margin_top": 0,
+                    "border_top_left_radius": "4px",
+                    "border_top_right_radius": "4px",
+                },
+                "&:last-child": {
+                    "border_bottom_left_radius": "4px",
+                    "border_bottom_right_radius": "4px",
+                },
+                "&:focus-within": {
+                    "position": "relative",
+                    "z_index": 1,
+                },
+            }
+        )
+
 
 class AccordionHeader(AccordionComponent):
     """An accordion component."""
@@ -561,8 +265,21 @@ class AccordionHeader(AccordionComponent):
 
         return super().create(*children, class_name=cls_name, **props)
 
-    def _apply_theme(self, theme: Component):
-        self.style = Style({**self.style})
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        for child in self.children:
+            if isinstance(child, AccordionTrigger):
+                child.color_scheme = self.color_scheme
+                child.variant = self.variant
+
+        return Style({"display": "flex"})
+
+
+cubic_bezier = "cubic-bezier(0.87, 0, 0.13, 1)"
 
 
 class AccordionTrigger(AccordionComponent):
@@ -590,8 +307,52 @@ class AccordionTrigger(AccordionComponent):
 
         return super().create(*children, class_name=cls_name, **props)
 
-    def _apply_theme(self, theme: Component):
-        self.style = Style({**self.style})
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        for child in self.children:
+            if isinstance(child, AccordionIcon):
+                child.color_scheme = self.color_scheme
+                child.variant = self.variant
+
+        return Style(
+            {
+                "color": color("accent", 11),
+                "line_height": 1,
+                "font_size": "15px",
+                "justify_content": "space-between",
+                "align_items": "center",
+                "flex": 1,
+                "display": "flex",
+                "padding": "0 20px",
+                "height": "45px",
+                "font_family": "inherit",
+                "width": "100%",
+                "&[data-state='open'] > .AccordionChevron": {
+                    "transform": "rotate(180deg)",
+                },
+                "&:hover": {
+                    "background_color": color("accent", 4),
+                },
+                "& > .AccordionChevron": {
+                    "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}",
+                },
+                "&[data-variant='classic']": {
+                    "color": color("accent", 12),
+                    "box_shadow": color("accent", 11),
+                    "&:hover": {
+                        "background_color": color("accent", 10),
+                    },
+                    "& > .AccordionChevron": {
+                        "color": color("accent", 12),
+                        "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}",
+                    },
+                },
+            }
+        )
 
 
 class AccordionIcon(Icon):
@@ -623,6 +384,14 @@ class AccordionContent(AccordionComponent):
 
     alias = "RadixAccordionContent"
 
+    def add_imports(self) -> imports.ImportDict:
+        """Add imports to the component.
+
+        Returns:
+            The imports of the component.
+        """
+        return {"@emotion/react": [imports.ImportVar(tag="keyframes")]}
+
     @classmethod
     def create(cls, *children, **props) -> Component:
         """Create the Accordion content component.
@@ -641,14 +410,66 @@ class AccordionContent(AccordionComponent):
 
         return super().create(*children, class_name=cls_name, **props)
 
-    def _apply_theme(self, theme: Component):
-        self.style = Style({**self.style})
+    def add_custom_code(self) -> list[str]:
+        """Add custom code to the component.
 
-    # def _get_imports(self):
-    #     return {
-    #         **super()._get_imports(),
-    #         "@emotion/react": [imports.ImportVar(tag="keyframes")],
-    #     }
+        Returns:
+            The custom code of the component.
+        """
+        return [
+            """
+const slideDown = keyframes`
+from {
+  height: 0;
+}
+to {
+  height: var(--radix-accordion-content-height);
+}
+`
+const slideUp = keyframes`
+from {
+  height: var(--radix-accordion-content-height);
+}
+to {
+  height: 0;
+}
+`
+"""
+        ]
+
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        slideDown = Var.create(
+            f"${{slideDown}} {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}",
+            _var_is_string=True,
+        )
+
+        slideUp = Var.create(
+            f"${{slideUp}} {DEFAULT_ANIMATION_DURATION}ms {cubic_bezier}",
+            _var_is_string=True,
+        )
+
+        return Style(
+            {
+                "overflow": "hidden",
+                "font_size": "10px",
+                "color": color("accent", 11),
+                "background_color": color("accent", 3),
+                "padding": "0 15px",
+                "&[data-state='open']": {"animation": slideDown},
+                "&[data-state='closed']": {"animation": slideUp},
+                "&[data-variant='classic']": {
+                    "color": color("accent", 12),
+                    "background_color": color("accent", 9),
+                },
+                "&[data-variant='outline']": {"background_color": "transparent"},
+                "&[data-variant='ghost']": {"background_color": "transparent"},
+            }
+        )
 
 
 class Accordion(ComponentNamespace):

+ 367 - 26
reflex/components/radix/primitives/accordion.pyi

@@ -9,41 +9,95 @@ from reflex.event import EventChain, EventHandler, EventSpec
 from reflex.style import Style
 from typing import Any, Dict, List, Literal, Optional, Union
 from reflex.components.component import Component, ComponentNamespace
-from reflex.components.core.match import Match
+from reflex.components.core.colors import color
 from reflex.components.lucide.icon import Icon
 from reflex.components.radix.primitives.base import RadixPrimitiveComponent
 from reflex.components.radix.themes.base import LiteralAccentColor
-from reflex.style import (
-    Style,
-    convert_dict_to_style_and_format_emotion,
-    format_as_emotion,
-)
+from reflex.style import Style
 from reflex.utils import imports
-from reflex.vars import BaseVar, Var, VarData, get_uuid_string_var
+from reflex.vars import Var, get_uuid_string_var
 
 LiteralAccordionType = Literal["single", "multiple"]
 LiteralAccordionDir = Literal["ltr", "rtl"]
 LiteralAccordionOrientation = Literal["vertical", "horizontal"]
-LiteralAccordionRootVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
-LiteralAccordionRootColorScheme = Literal["primary", "accent"]
+LiteralAccordionVariant = Literal["classic", "soft", "surface", "outline", "ghost"]
 DEFAULT_ANIMATION_DURATION = 250
 
-def get_theme_accordion_root(variant: Var[str], color_scheme: Var[str]) -> BaseVar: ...
-def get_theme_accordion_item(): ...
-def get_theme_accordion_header() -> dict[str, str]: ...
-def get_theme_accordion_trigger(
-    variant: str | Var, color_scheme: str | Var
-) -> BaseVar: ...
-def get_theme_accordion_content(
-    variant: str | Var, color_scheme: str | Var
-) -> BaseVar: ...
-
 class AccordionComponent(RadixPrimitiveComponent):
+    def add_style(self) -> Style | None: ...
     @overload
     @classmethod
     def create(  # type: ignore
         cls,
         *children,
+        color_scheme: Optional[
+            Union[
+                Var[
+                    Literal[
+                        "tomato",
+                        "red",
+                        "ruby",
+                        "crimson",
+                        "pink",
+                        "plum",
+                        "purple",
+                        "violet",
+                        "iris",
+                        "indigo",
+                        "blue",
+                        "cyan",
+                        "teal",
+                        "jade",
+                        "green",
+                        "grass",
+                        "brown",
+                        "orange",
+                        "sky",
+                        "mint",
+                        "lime",
+                        "yellow",
+                        "amber",
+                        "gold",
+                        "bronze",
+                        "gray",
+                    ]
+                ],
+                Literal[
+                    "tomato",
+                    "red",
+                    "ruby",
+                    "crimson",
+                    "pink",
+                    "plum",
+                    "purple",
+                    "violet",
+                    "iris",
+                    "indigo",
+                    "blue",
+                    "cyan",
+                    "teal",
+                    "jade",
+                    "green",
+                    "grass",
+                    "brown",
+                    "orange",
+                    "sky",
+                    "mint",
+                    "lime",
+                    "yellow",
+                    "amber",
+                    "gold",
+                    "bronze",
+                    "gray",
+                ],
+            ]
+        ] = None,
+        variant: Optional[
+            Union[
+                Var[Literal["classic", "soft", "surface", "outline", "ghost"]],
+                Literal["classic", "soft", "surface", "outline", "ghost"],
+            ]
+        ] = None,
         as_child: Optional[Union[Var[bool], bool]] = None,
         style: Optional[Style] = None,
         key: Optional[Any] = None,
@@ -102,6 +156,8 @@ class AccordionComponent(RadixPrimitiveComponent):
 
         Args:
             *children: The children of the component.
+            color_scheme: The color scheme of the component.
+            variant: The variant of the component.
             as_child: Change the default rendered element for the one passed as a child.
             style: The style of the component.
             key: A unique key for the component.
@@ -208,8 +264,6 @@ class AccordionRoot(AccordionComponent):
                 ],
             ]
         ] = None,
-        _dynamic_themes: Optional[Union[Var[dict], dict]] = None,
-        _var_data: Optional[VarData] = None,
         as_child: Optional[Union[Var[bool], bool]] = None,
         style: Optional[Style] = None,
         key: Optional[Any] = None,
@@ -278,10 +332,8 @@ class AccordionRoot(AccordionComponent):
             disabled: Whether or not the accordion is disabled.
             dir: The reading direction of the accordion when applicable.
             orientation: The orientation of the accordion.
-            variant: The variant of the accordion.
-            color_scheme: The color scheme of the accordion.
-            _dynamic_themes: dynamic themes of the accordion generated at compile time.
-            _var_data: The var_data associated with the component.
+            variant: The variant of the component.
+            color_scheme: The color scheme of the component.
             as_child: Change the default rendered element for the one passed as a child.
             style: The style of the component.
             key: A unique key for the component.
@@ -296,6 +348,7 @@ class AccordionRoot(AccordionComponent):
         """
         ...
     def get_event_triggers(self) -> Dict[str, Any]: ...
+    def add_style(self): ...
 
 class AccordionItem(AccordionComponent):
     @overload
@@ -307,6 +360,74 @@ class AccordionItem(AccordionComponent):
         content: Optional[Union[Component, Var]] = None,
         value: Optional[Union[Var[str], str]] = None,
         disabled: Optional[Union[Var[bool], bool]] = None,
+        color_scheme: Optional[
+            Union[
+                Var[
+                    Literal[
+                        "tomato",
+                        "red",
+                        "ruby",
+                        "crimson",
+                        "pink",
+                        "plum",
+                        "purple",
+                        "violet",
+                        "iris",
+                        "indigo",
+                        "blue",
+                        "cyan",
+                        "teal",
+                        "jade",
+                        "green",
+                        "grass",
+                        "brown",
+                        "orange",
+                        "sky",
+                        "mint",
+                        "lime",
+                        "yellow",
+                        "amber",
+                        "gold",
+                        "bronze",
+                        "gray",
+                    ]
+                ],
+                Literal[
+                    "tomato",
+                    "red",
+                    "ruby",
+                    "crimson",
+                    "pink",
+                    "plum",
+                    "purple",
+                    "violet",
+                    "iris",
+                    "indigo",
+                    "blue",
+                    "cyan",
+                    "teal",
+                    "jade",
+                    "green",
+                    "grass",
+                    "brown",
+                    "orange",
+                    "sky",
+                    "mint",
+                    "lime",
+                    "yellow",
+                    "amber",
+                    "gold",
+                    "bronze",
+                    "gray",
+                ],
+            ]
+        ] = None,
+        variant: Optional[
+            Union[
+                Var[Literal["classic", "soft", "surface", "outline", "ghost"]],
+                Literal["classic", "soft", "surface", "outline", "ghost"],
+            ]
+        ] = None,
         as_child: Optional[Union[Var[bool], bool]] = None,
         style: Optional[Style] = None,
         key: Optional[Any] = None,
@@ -364,11 +485,13 @@ class AccordionItem(AccordionComponent):
         """Create an accordion item.
 
         Args:
+            *children: The list of children to use if header and content are not provided.
             header: The header of the accordion item.
             content: The content of the accordion item.
-            *children: The list of children to use if header and content are not provided.
             value: A unique identifier for the item.
             disabled: When true, prevents the user from interacting with the item.
+            color_scheme: The color scheme of the component.
+            variant: The variant of the component.
             as_child: Change the default rendered element for the one passed as a child.
             style: The style of the component.
             key: A unique key for the component.
@@ -382,6 +505,7 @@ class AccordionItem(AccordionComponent):
             The accordion item.
         """
         ...
+    def add_style(self) -> Style | None: ...
 
 class AccordionHeader(AccordionComponent):
     @overload
@@ -389,6 +513,74 @@ class AccordionHeader(AccordionComponent):
     def create(  # type: ignore
         cls,
         *children,
+        color_scheme: Optional[
+            Union[
+                Var[
+                    Literal[
+                        "tomato",
+                        "red",
+                        "ruby",
+                        "crimson",
+                        "pink",
+                        "plum",
+                        "purple",
+                        "violet",
+                        "iris",
+                        "indigo",
+                        "blue",
+                        "cyan",
+                        "teal",
+                        "jade",
+                        "green",
+                        "grass",
+                        "brown",
+                        "orange",
+                        "sky",
+                        "mint",
+                        "lime",
+                        "yellow",
+                        "amber",
+                        "gold",
+                        "bronze",
+                        "gray",
+                    ]
+                ],
+                Literal[
+                    "tomato",
+                    "red",
+                    "ruby",
+                    "crimson",
+                    "pink",
+                    "plum",
+                    "purple",
+                    "violet",
+                    "iris",
+                    "indigo",
+                    "blue",
+                    "cyan",
+                    "teal",
+                    "jade",
+                    "green",
+                    "grass",
+                    "brown",
+                    "orange",
+                    "sky",
+                    "mint",
+                    "lime",
+                    "yellow",
+                    "amber",
+                    "gold",
+                    "bronze",
+                    "gray",
+                ],
+            ]
+        ] = None,
+        variant: Optional[
+            Union[
+                Var[Literal["classic", "soft", "surface", "outline", "ghost"]],
+                Literal["classic", "soft", "surface", "outline", "ghost"],
+            ]
+        ] = None,
         as_child: Optional[Union[Var[bool], bool]] = None,
         style: Optional[Style] = None,
         key: Optional[Any] = None,
@@ -447,6 +639,8 @@ class AccordionHeader(AccordionComponent):
 
         Args:
             *children: The children of the component.
+            color_scheme: The color scheme of the component.
+            variant: The variant of the component.
             as_child: Change the default rendered element for the one passed as a child.
             style: The style of the component.
             key: A unique key for the component.
@@ -460,6 +654,9 @@ class AccordionHeader(AccordionComponent):
             The Accordion header Component.
         """
         ...
+    def add_style(self) -> Style | None: ...
+
+cubic_bezier = "cubic-bezier(0.87, 0, 0.13, 1)"
 
 class AccordionTrigger(AccordionComponent):
     @overload
@@ -467,6 +664,74 @@ class AccordionTrigger(AccordionComponent):
     def create(  # type: ignore
         cls,
         *children,
+        color_scheme: Optional[
+            Union[
+                Var[
+                    Literal[
+                        "tomato",
+                        "red",
+                        "ruby",
+                        "crimson",
+                        "pink",
+                        "plum",
+                        "purple",
+                        "violet",
+                        "iris",
+                        "indigo",
+                        "blue",
+                        "cyan",
+                        "teal",
+                        "jade",
+                        "green",
+                        "grass",
+                        "brown",
+                        "orange",
+                        "sky",
+                        "mint",
+                        "lime",
+                        "yellow",
+                        "amber",
+                        "gold",
+                        "bronze",
+                        "gray",
+                    ]
+                ],
+                Literal[
+                    "tomato",
+                    "red",
+                    "ruby",
+                    "crimson",
+                    "pink",
+                    "plum",
+                    "purple",
+                    "violet",
+                    "iris",
+                    "indigo",
+                    "blue",
+                    "cyan",
+                    "teal",
+                    "jade",
+                    "green",
+                    "grass",
+                    "brown",
+                    "orange",
+                    "sky",
+                    "mint",
+                    "lime",
+                    "yellow",
+                    "amber",
+                    "gold",
+                    "bronze",
+                    "gray",
+                ],
+            ]
+        ] = None,
+        variant: Optional[
+            Union[
+                Var[Literal["classic", "soft", "surface", "outline", "ghost"]],
+                Literal["classic", "soft", "surface", "outline", "ghost"],
+            ]
+        ] = None,
         as_child: Optional[Union[Var[bool], bool]] = None,
         style: Optional[Style] = None,
         key: Optional[Any] = None,
@@ -525,6 +790,8 @@ class AccordionTrigger(AccordionComponent):
 
         Args:
             *children: The children of the component.
+            color_scheme: The color scheme of the component.
+            variant: The variant of the component.
             as_child: Change the default rendered element for the one passed as a child.
             style: The style of the component.
             key: A unique key for the component.
@@ -538,6 +805,7 @@ class AccordionTrigger(AccordionComponent):
             The Accordion trigger Component.
         """
         ...
+    def add_style(self) -> Style | None: ...
 
 class AccordionIcon(Icon):
     @overload
@@ -618,11 +886,80 @@ class AccordionIcon(Icon):
         ...
 
 class AccordionContent(AccordionComponent):
+    def add_imports(self) -> imports.ImportDict: ...
     @overload
     @classmethod
     def create(  # type: ignore
         cls,
         *children,
+        color_scheme: Optional[
+            Union[
+                Var[
+                    Literal[
+                        "tomato",
+                        "red",
+                        "ruby",
+                        "crimson",
+                        "pink",
+                        "plum",
+                        "purple",
+                        "violet",
+                        "iris",
+                        "indigo",
+                        "blue",
+                        "cyan",
+                        "teal",
+                        "jade",
+                        "green",
+                        "grass",
+                        "brown",
+                        "orange",
+                        "sky",
+                        "mint",
+                        "lime",
+                        "yellow",
+                        "amber",
+                        "gold",
+                        "bronze",
+                        "gray",
+                    ]
+                ],
+                Literal[
+                    "tomato",
+                    "red",
+                    "ruby",
+                    "crimson",
+                    "pink",
+                    "plum",
+                    "purple",
+                    "violet",
+                    "iris",
+                    "indigo",
+                    "blue",
+                    "cyan",
+                    "teal",
+                    "jade",
+                    "green",
+                    "grass",
+                    "brown",
+                    "orange",
+                    "sky",
+                    "mint",
+                    "lime",
+                    "yellow",
+                    "amber",
+                    "gold",
+                    "bronze",
+                    "gray",
+                ],
+            ]
+        ] = None,
+        variant: Optional[
+            Union[
+                Var[Literal["classic", "soft", "surface", "outline", "ghost"]],
+                Literal["classic", "soft", "surface", "outline", "ghost"],
+            ]
+        ] = None,
         as_child: Optional[Union[Var[bool], bool]] = None,
         style: Optional[Style] = None,
         key: Optional[Any] = None,
@@ -681,6 +1018,8 @@ class AccordionContent(AccordionComponent):
 
         Args:
             *children: The children of the component.
+            color_scheme: The color scheme of the component.
+            variant: The variant of the component.
             as_child: Change the default rendered element for the one passed as a child.
             style: The style of the component.
             key: A unique key for the component.
@@ -694,6 +1033,8 @@ class AccordionContent(AccordionComponent):
             The Accordion content Component.
         """
         ...
+    def add_custom_code(self) -> list[str]: ...
+    def add_style(self) -> Style | None: ...
 
 class Accordion(ComponentNamespace):
     content = staticmethod(AccordionContent.create)

+ 30 - 26
reflex/components/radix/primitives/form.py

@@ -4,10 +4,11 @@ from __future__ import annotations
 
 from typing import Any, Dict, Literal
 
-from reflex.components.component import Component, ComponentNamespace
+from reflex.components.component import ComponentNamespace
 from reflex.components.el.elements.forms import Form as HTMLForm
 from reflex.components.radix.themes.components.text_field import TextFieldRoot
 from reflex.constants.event import EventTriggers
+from reflex.style import Style
 from reflex.vars import Var
 
 from .base import RadixPrimitiveComponentWithClassName
@@ -37,11 +38,13 @@ class FormRoot(FormComponent, HTMLForm):
             EventTriggers.ON_CLEAR_SERVER_ERRORS: lambda: [],
         }
 
-    def _apply_theme(self, theme: Component):
-        return {
-            "width": "260px",
-            **self.style,
-        }
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        return Style({"width": "260px"})
 
 
 class FormField(FormComponent):
@@ -57,12 +60,13 @@ class FormField(FormComponent):
     # Flag to mark the form field as invalid, for server side validation.
     server_invalid: Var[bool]
 
-    def _apply_theme(self, theme: Component):
-        return {
-            "display": "grid",
-            "margin_bottom": "10px",
-            **self.style,
-        }
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        return Style({"display": "grid", "margin_bottom": "10px"})
 
 
 class FormLabel(FormComponent):
@@ -72,13 +76,13 @@ class FormLabel(FormComponent):
 
     alias = "RadixFormLabel"
 
-    def _apply_theme(self, theme: Component):
-        return {
-            "font_size": "15px",
-            "font_weight": "500",
-            "line_height": "35px",
-            **self.style,
-        }
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        return Style({"font_size": "15px", "font_weight": "500", "line_height": "35px"})
 
 
 class FormControl(FormComponent):
@@ -145,13 +149,13 @@ class FormMessage(FormComponent):
     # Forces the message to be shown. This is useful when using server-side validation.
     force_match: Var[bool]
 
-    def _apply_theme(self, theme: Component):
-        return {
-            "font_size": "13px",
-            "opacity": "0.8",
-            "color": "white",
-            **self.style,
-        }
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        return Style({"font_size": "13px", "opacity": "0.8", "color": "white"})
 
 
 class FormValidityState(FormComponent):

+ 6 - 1
reflex/components/radix/primitives/form.pyi

@@ -8,10 +8,11 @@ from reflex.vars import Var, BaseVar, ComputedVar
 from reflex.event import EventChain, EventHandler, EventSpec
 from reflex.style import Style
 from typing import Any, Dict, Literal
-from reflex.components.component import Component, ComponentNamespace
+from reflex.components.component import ComponentNamespace
 from reflex.components.el.elements.forms import Form as HTMLForm
 from reflex.components.radix.themes.components.text_field import TextFieldRoot
 from reflex.constants.event import EventTriggers
+from reflex.style import Style
 from reflex.vars import Var
 from .base import RadixPrimitiveComponentWithClassName
 
@@ -95,6 +96,7 @@ class FormComponent(RadixPrimitiveComponentWithClassName):
 
 class FormRoot(FormComponent, HTMLForm):
     def get_event_triggers(self) -> Dict[str, Any]: ...
+    def add_style(self) -> Style | None: ...
     @overload
     @classmethod
     def create(  # type: ignore
@@ -273,6 +275,7 @@ class FormRoot(FormComponent, HTMLForm):
         ...
 
 class FormField(FormComponent):
+    def add_style(self) -> Style | None: ...
     @overload
     @classmethod
     def create(  # type: ignore
@@ -355,6 +358,7 @@ class FormField(FormComponent):
         ...
 
 class FormLabel(FormComponent):
+    def add_style(self) -> Style | None: ...
     @overload
     @classmethod
     def create(  # type: ignore
@@ -528,6 +532,7 @@ LiteralMatcher = Literal[
 ]
 
 class FormMessage(FormComponent):
+    def add_style(self) -> Style | None: ...
     @overload
     @classmethod
     def create(  # type: ignore

+ 17 - 9
reflex/components/radix/primitives/progress.py

@@ -28,20 +28,24 @@ class ProgressRoot(ProgressComponent):
     # Override theme radius for progress bar: "none" | "small" | "medium" | "large" | "full"
     radius: Var[LiteralRadius]
 
-    def _apply_theme(self, theme: Component):
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
         if self.radius is not None:
             self.custom_attrs["data-radius"] = self.radius
 
-        self.style = Style(
+        return Style(
             {
                 "position": "relative",
                 "overflow": "hidden",
-                "background": "var(--gray-a3)",
+                "background": color("gray", 3, alpha=True),
                 "border_radius": "max(var(--radius-2), var(--radius-full))",
                 "width": "100%",
                 "height": "20px",
-                "boxShadow": "inset 0 0 0 1px var(--gray-a5)",
-                **self.style,
+                "boxShadow": f"inset 0 0 0 1px {color('gray', 5, alpha=True)}",
             }
         )
 
@@ -65,22 +69,26 @@ class ProgressIndicator(ProgressComponent):
     # The color scheme of the progress indicator.
     color_scheme: Var[LiteralAccentColor]
 
-    def _apply_theme(self, theme: Component):
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
         if self.color_scheme is not None:
             self.custom_attrs["data-accent-color"] = self.color_scheme
 
-        self.style = Style(
+        return Style(
             {
                 "background_color": color("accent", 9),
                 "width": "100%",
                 "height": "100%",
-                f"transition": f"transform {DEFAULT_ANIMATION_DURATION}ms linear",
+                "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms linear",
                 "&[data_state='loading']": {
                     "transition": f"transform {DEFAULT_ANIMATION_DURATION}ms linear",
                 },
                 "transform": f"translateX(calc(-100% + ({self.value} / {self.max} * 100%)))",  # type: ignore
                 "boxShadow": "inset 0 0 0 1px var(--gray-a5)",
-                **self.style,
             }
         )
 

+ 2 - 0
reflex/components/radix/primitives/progress.pyi

@@ -95,6 +95,7 @@ class ProgressComponent(RadixPrimitiveComponentWithClassName):
         ...
 
 class ProgressRoot(ProgressComponent):
+    def add_style(self) -> Style | None: ...
     @overload
     @classmethod
     def create(  # type: ignore
@@ -180,6 +181,7 @@ class ProgressRoot(ProgressComponent):
         ...
 
 class ProgressIndicator(ProgressComponent):
+    def add_style(self) -> Style | None: ...
     @overload
     @classmethod
     def create(  # type: ignore

+ 30 - 18
reflex/components/radix/primitives/slider.py

@@ -59,8 +59,13 @@ class SliderRoot(SliderComponent):
             "on_value_commit": lambda e0: [e0],  # trigger when thumb is released
         }
 
-    def _apply_theme(self, theme: Component):
-        self.style = Style(
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        return Style(
             {
                 "position": "relative",
                 "display": "flex",
@@ -74,7 +79,6 @@ class SliderRoot(SliderComponent):
                     "width": "20px",
                     "height": "100px",
                 },
-                **self.style,
             }
         )
 
@@ -85,18 +89,20 @@ class SliderTrack(SliderComponent):
     tag = "Track"
     alias = "RadixSliderTrack"
 
-    def _apply_theme(self, theme: Component):
-        self.style = Style(
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        return Style(
             {
                 "position": "relative",
                 "flex_grow": "1",
                 "background_color": "black",
                 "border_radius": "9999px",
                 "height": "3px",
-                "&[data-orientation='vertical']": {
-                    "width": "3px",
-                },
-                **self.style,
+                "&[data-orientation='vertical']": {"width": "3px"},
             }
         )
 
@@ -107,16 +113,18 @@ class SliderRange(SliderComponent):
     tag = "Range"
     alias = "RadixSliderRange"
 
-    def _apply_theme(self, theme: Component):
-        self.style = Style(
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        return Style(
             {
                 "position": "absolute",
                 "background_color": "white",
                 "height": "100%",
-                "&[data-orientation='vertical']": {
-                    "width": "100%",
-                },
-                **self.style,
+                "&[data-orientation='vertical']": {"width": "100%"},
             }
         )
 
@@ -127,8 +135,13 @@ class SliderThumb(SliderComponent):
     tag = "Thumb"
     alias = "RadixSliderThumb"
 
-    def _apply_theme(self, theme: Component):
-        self.style = Style(
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        return Style(
             {
                 "display": "block",
                 "width": "20px",
@@ -143,7 +156,6 @@ class SliderThumb(SliderComponent):
                     "outline": "none",
                     "box_shadow": "0 0 0 4px gray",
                 },
-                **self.style,
             }
         )
 

+ 4 - 0
reflex/components/radix/primitives/slider.pyi

@@ -96,6 +96,7 @@ class SliderComponent(RadixPrimitiveComponentWithClassName):
 
 class SliderRoot(SliderComponent):
     def get_event_triggers(self) -> Dict[str, Any]: ...
+    def add_style(self) -> Style | None: ...
     @overload
     @classmethod
     def create(  # type: ignore
@@ -196,6 +197,7 @@ class SliderRoot(SliderComponent):
         ...
 
 class SliderTrack(SliderComponent):
+    def add_style(self) -> Style | None: ...
     @overload
     @classmethod
     def create(  # type: ignore
@@ -274,6 +276,7 @@ class SliderTrack(SliderComponent):
         ...
 
 class SliderRange(SliderComponent):
+    def add_style(self) -> Style | None: ...
     @overload
     @classmethod
     def create(  # type: ignore
@@ -352,6 +355,7 @@ class SliderRange(SliderComponent):
         ...
 
 class SliderThumb(SliderComponent):
+    def add_style(self) -> Style | None: ...
     @overload
     @classmethod
     def create(  # type: ignore

+ 13 - 11
reflex/components/radix/themes/components/icon_button.py

@@ -1,4 +1,5 @@
 """Interactive components provided by @radix-ui/themes."""
+from __future__ import annotations
 
 from typing import Literal
 
@@ -6,6 +7,7 @@ from reflex import el
 from reflex.components.component import Component
 from reflex.components.core.match import Match
 from reflex.components.lucide import Icon
+from reflex.style import Style
 from reflex.vars import Var
 
 from ..base import (
@@ -68,25 +70,25 @@ class IconButton(el.Button, RadixLoadingProp, RadixThemesComponent):
                 "IconButton requires a child icon. Pass a string as the first child or a rx.icon."
             )
         if "size" in props:
+            RADIX_TO_LUCIDE_SIZE = {"1": "12px", "2": "24px", "3": "36px", "4": "48px"}
+
             if isinstance(props["size"], str):
-                RADIX_TO_LUCIDE_SIZE = {
-                    "1": "12px",
-                    "2": "24px",
-                    "3": "36px",
-                    "4": "48px",
-                }
                 children[0].size = RADIX_TO_LUCIDE_SIZE[props["size"]]
             else:
                 children[0].size = Match.create(
                     props["size"],
-                    ("1", "12px"),
-                    ("2", "24px"),
-                    ("3", "36px"),
-                    ("4", "48px"),
+                    *[(size, px) for size, px in RADIX_TO_LUCIDE_SIZE.items()],
                     "12px",
                 )
-        props.setdefault("padding", "6px")
         return super().create(*children, **props)
 
+    def add_style(self):
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        return Style({"padding": "6px"})
+
 
 icon_button = IconButton.create

+ 2 - 0
reflex/components/radix/themes/components/icon_button.pyi

@@ -12,6 +12,7 @@ from reflex import el
 from reflex.components.component import Component
 from reflex.components.core.match import Match
 from reflex.components.lucide import Icon
+from reflex.style import Style
 from reflex.vars import Var
 from ..base import (
     LiteralAccentColor,
@@ -280,5 +281,6 @@ class IconButton(el.Button, RadixLoadingProp, RadixThemesComponent):
             The IconButton component.
         """
         ...
+    def add_style(self): ...
 
 icon_button = IconButton.create

+ 8 - 3
reflex/components/radix/themes/layout/center.py

@@ -2,7 +2,7 @@
 
 from __future__ import annotations
 
-from reflex.components.component import Component
+from reflex.style import Style
 
 from .flex import Flex
 
@@ -10,8 +10,13 @@ from .flex import Flex
 class Center(Flex):
     """A center component."""
 
-    def _apply_theme(self, theme: Component):
-        self.style.update(
+    def add_style(self) -> Style | None:
+        """Add style that center the content.
+
+        Returns:
+            The style of the component.
+        """
+        return Style(
             {
                 "display": "flex",
                 "align_items": "center",

+ 2 - 1
reflex/components/radix/themes/layout/center.pyi

@@ -7,10 +7,11 @@ from typing import Any, Dict, Literal, Optional, Union, overload
 from reflex.vars import Var, BaseVar, ComputedVar
 from reflex.event import EventChain, EventHandler, EventSpec
 from reflex.style import Style
-from reflex.components.component import Component
+from reflex.style import Style
 from .flex import Flex
 
 class Center(Flex):
+    def add_style(self) -> Style | None: ...
     @overload
     @classmethod
     def create(  # type: ignore

+ 8 - 3
reflex/components/radix/themes/layout/list.py

@@ -1,4 +1,5 @@
 """List components."""
+from __future__ import annotations
 
 from typing import Iterable, Literal, Optional, Union
 
@@ -77,12 +78,16 @@ class BaseList(Component):
             style["gap"] = props["gap"]
         return super().create(*children, **props)
 
-    def _apply_theme(self, theme: Component):
-        self.style = Style(
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        return Style(
             {
                 "direction": "column",
                 "list_style_position": "inside",
-                **self.style,
             }
         )
 

+ 3 - 19
reflex/components/radix/themes/layout/list.pyi

@@ -157,6 +157,7 @@ class BaseList(Component):
 
         """
         ...
+    def add_style(self) -> Style | None: ...
 
 class UnorderedList(BaseList, Ul):
     @overload
@@ -165,7 +166,7 @@ class UnorderedList(BaseList, Ul):
         cls,
         *children,
         items: Optional[Union[Var[Iterable], Iterable]] = None,
-        list_style_type: Optional[Literal["none", "disc", "circle", "square"]] = "disc",
+        list_style_type: Optional[LiteralListStyleTypeUnordered] = "disc",
         access_key: Optional[
             Union[Var[Union[str, int, bool]], Union[str, int, bool]]
         ] = None,
@@ -302,24 +303,7 @@ class OrderedList(BaseList, Ol):
         cls,
         *children,
         items: Optional[Union[Var[Iterable], Iterable]] = None,
-        list_style_type: Optional[
-            Literal[
-                "none",
-                "decimal",
-                "decimal-leading-zero",
-                "lower-roman",
-                "upper-roman",
-                "lower-greek",
-                "lower-latin",
-                "upper-latin",
-                "armenian",
-                "georgian",
-                "lower-alpha",
-                "upper-alpha",
-                "hiragana",
-                "katakana",
-            ]
-        ] = "decimal",
+        list_style_type: Optional[LiteralListStyleTypeOrdered] = "decimal",
         reversed: Optional[
             Union[Var[Union[str, int, bool]], Union[str, int, bool]]
         ] = None,

+ 8 - 3
reflex/components/radix/themes/layout/spacer.py

@@ -2,7 +2,7 @@
 
 from __future__ import annotations
 
-from reflex.components.component import Component
+from reflex.style import Style
 
 from .flex import Flex
 
@@ -10,8 +10,13 @@ from .flex import Flex
 class Spacer(Flex):
     """A spacer component."""
 
-    def _apply_theme(self, theme: Component):
-        self.style.update(
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
+        return Style(
             {
                 "flex": 1,
                 "justify_self": "stretch",

+ 2 - 1
reflex/components/radix/themes/layout/spacer.pyi

@@ -7,10 +7,11 @@ from typing import Any, Dict, Literal, Optional, Union, overload
 from reflex.vars import Var, BaseVar, ComputedVar
 from reflex.event import EventChain, EventHandler, EventSpec
 from reflex.style import Style
-from reflex.components.component import Component
+from reflex.style import Style
 from .flex import Flex
 
 class Spacer(Flex):
+    def add_style(self) -> Style | None: ...
     @overload
     @classmethod
     def create(  # type: ignore

+ 2 - 0
reflex/constants/colors.py

@@ -37,6 +37,8 @@ ColorType = Literal[
     "bronze",
     "gray",
     "accent",
+    "black",
+    "white",
 ]
 
 ShadeType = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

+ 24 - 6
reflex/experimental/layout.py

@@ -1,5 +1,7 @@
 """To experiment with layout component, move them to reflex/components later."""
 
+from __future__ import annotations
+
 from reflex import color, cond
 from reflex.components.base.fragment import Fragment
 from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
@@ -9,6 +11,7 @@ from reflex.components.radix.themes.layout import Box, Container, HStack
 from reflex.event import call_script
 from reflex.experimental import hooks
 from reflex.state import ComponentState
+from reflex.style import Style
 from reflex.vars import Var
 
 
@@ -26,23 +29,38 @@ class Sidebar(Box, MemoizationLeaf):
         Returns:
             The sidebar component.
         """
-        props.setdefault("border_right", f"1px solid {color('accent', 12)}")
-        props.setdefault("background_color", color("accent", 1))
-        props.setdefault("width", "20vw")
-        props.setdefault("height", "100vh")
-        props.setdefault("position", "fixed")
+        # props.setdefault("border_right", f"1px solid {color('accent', 12)}")
+        # props.setdefault("background_color", color("accent", 1))
+        # props.setdefault("width", "20vw")
+        # props.setdefault("height", "100vh")
+        # props.setdefault("position", "fixed")
 
         return super().create(
             Box.create(*children, **props),  # sidebar for content
             Box.create(width=props.get("width")),  # spacer for layout
         )
 
-    def _apply_theme(self, theme: Component | None):
+    def add_style(self) -> Style | None:
+        """Add style to the component.
+
+        Returns:
+            The style of the component.
+        """
         sidebar: Component = self.children[-2]  # type: ignore
         spacer: Component = self.children[-1]  # type: ignore
         open = self.State.open if self.State else Var.create("open")  # type: ignore
         sidebar.style["display"] = spacer.style["display"] = cond(open, "block", "none")
 
+        return Style(
+            {
+                "position": "fixed",
+                "border_right": f"1px solid {color('accent', 12)}",
+                "background_color": color("accent", 1),
+                "width": "20vw",
+                "height": "100vh",
+            }
+        )
+
     def _get_hooks(self) -> Var | None:
         return hooks.useState("open", "true") if not self.State else None
 

+ 6 - 1
reflex/style.py

@@ -159,12 +159,17 @@ def format_style_key(key: str) -> Tuple[str, ...]:
 class Style(dict):
     """A style dictionary."""
 
-    def __init__(self, style_dict: dict | None = None):
+    def __init__(self, style_dict: dict | None = None, **kwargs):
         """Initialize the style.
 
         Args:
             style_dict: The style dictionary.
+            kwargs: Other key value pairs to apply to the dict update.
         """
+        if style_dict:
+            style_dict.update(kwargs)
+        else:
+            style_dict = kwargs
         style_dict, self._var_data = convert(style_dict or {})
         super().__init__(style_dict)
 

+ 1 - 10
tests/components/core/test_foreach.py

@@ -2,7 +2,7 @@ from typing import Dict, List, Set, Tuple, Union
 
 import pytest
 
-from reflex.components import box, foreach, text, theme
+from reflex.components import box, foreach, text
 from reflex.components.core import Foreach
 from reflex.state import BaseState
 from reflex.vars import Var
@@ -220,15 +220,6 @@ def test_foreach_render(state_var, render_fn, render_dict):
     seen_index_vars.add(arg_index._var_name)
 
 
-def test_foreach_apply_theme():
-    """Test that the foreach component applies the theme."""
-    tag = Foreach.create(ForEachState.colors_list, display_color)  # type: ignore
-    _theme = theme()
-    tag.apply_theme(_theme)
-    assert tag.theme == _theme
-    tag.render()
-
-
 def test_foreach_bad_annotations():
     """Test that the foreach component raises a TypeError if the iterable is of type Any."""
     with pytest.raises(TypeError):

+ 3 - 4
tests/components/lucide/test_icon.py

@@ -1,7 +1,6 @@
 import pytest
 
 from reflex.components.lucide.icon import LUCIDE_ICON_LIST, RENAMED_ICONS_05, Icon
-from reflex.components.radix.themes.base import Theme
 from reflex.utils import format
 
 
@@ -17,7 +16,7 @@ RENAMED_TAGS = [tag for tag in RENAMED_ICONS_05.items()]
 @pytest.mark.parametrize("tag, new_tag", RENAMED_TAGS)
 def test_icon_renamed_tags(tag, new_tag):
     Icon.create(tag)
-    # need a PR so we can pass the following test. Currently it fails and uses the old tag as the import.
+    # TODO: need a PR so we can pass the following test. Currently it fails and uses the old tag as the import.
     # assert icon.alias == f"Lucide{format.to_title_case(new_tag)}Icon"
 
 
@@ -36,6 +35,6 @@ def test_icon_multiple_children():
         _ = Icon.create("activity", "child1", "child2")
 
 
-def test_icon_apply_theme():
+def test_icon_add_style():
     ic = Icon.create("activity")
-    ic._apply_theme(Theme())
+    assert ic.add_style() is None

+ 4 - 2
tests/components/radix/test_icon_button.py

@@ -1,19 +1,21 @@
 import pytest
 
 from reflex.components.lucide.icon import Icon
-from reflex.components.radix.themes.base import Theme
 from reflex.components.radix.themes.components.icon_button import IconButton
+from reflex.style import Style
 from reflex.vars import Var
 
 
 def test_icon_button():
     ib1 = IconButton.create("activity")
-    ib1._apply_theme(Theme.create())
     assert isinstance(ib1, IconButton)
 
     ib2 = IconButton.create(Icon.create("activity"))
     assert isinstance(ib2, IconButton)
 
+    assert isinstance(ib1.add_style(), Style)
+    assert isinstance(ib2.add_style(), Style)
+
 
 def test_icon_button_no_child():
     with pytest.raises(ValueError):