Repository: reflex-dev/reflex
Pull request: add option to disable Tailwind
This PR provides an option to disable the out-of-the-box supported Tailwind. To do that, simply set the `tailwind` config to `None` :

`rxconfig.py`
---------------------------------
```python
import reflex as rx

config = ExampleConfig(
    app_name="example",
    tailwind= None
)
```

fixes #1829 

Docs: https://github.com/reflex-dev/reflex-web/pull/205

Problem statement:
Don't have Tailwind defaulty included in reflex app
Currently Tailwind is added to each fresh initialization of Reflex app. This may interfere with some other css libraries.

The `tailwindcss` module is installed defaulty. Also the is a default style style file in `.web\styles\tailwind.css`.

It would be nice, if it was included only optionally when specified in packages or installed manually.

Hints from issue discussion:

Commit message: fix precommit

Patch to review:
diff --git a/reflex/.templates/web/package.json b/reflex/.templates/web/package.json
index 191e7a4e50d..09c44220fa4 100644
--- a/reflex/.templates/web/package.json
+++ b/reflex/.templates/web/package.json
@@ -24,7 +24,6 @@
   },
   "devDependencies": {
     "autoprefixer": "^10.4.14",
-    "postcss": "^8.4.24",
-    "tailwindcss": "^3.3.2"
+    "postcss": "^8.4.24"
   }
 }
\ No newline at end of file
diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py
index 16df3c81ac6..d3e8f208cab 100644
--- a/reflex/compiler/compiler.py
+++ b/reflex/compiler/compiler.py
@@ -8,6 +8,7 @@
 from reflex import constants
 from reflex.compiler import templates, utils
 from reflex.components.component import Component, ComponentStyle, CustomComponent
+from reflex.config import get_config
 from reflex.state import State
 from reflex.utils import imports
 from reflex.vars import ImportVar
@@ -148,7 +149,12 @@ def _compile_root_stylesheet(stylesheets: List[str]) -> str:
     Raises:
         FileNotFoundError: If a specified stylesheet in assets directory does not exist.
     """
-    sheets = [constants.TAILWIND_ROOT_STYLE_PATH]
+    # Add tailwind css if enabled.
+    sheets = (
+        [constants.TAILWIND_ROOT_STYLE_PATH]
+        if get_config().tailwind is not None
+        else []
+    )
     for stylesheet in stylesheets:
         if not utils.is_valid_url(stylesheet):
             # check if stylesheet provided exists.
diff --git a/reflex/config.py b/reflex/config.py
index 397dea10731..232406c6c34 100644
--- a/reflex/config.py
+++ b/reflex/config.py
@@ -169,7 +169,7 @@ class Config:
     cors_allowed_origins: List[str] = ["*"]
 
     # Tailwind config.
-    tailwind: Optional[Dict[str, Any]] = None
+    tailwind: Optional[Dict[str, Any]] = {}
 
     # Timeout when launching the gunicorn server. TODO(rename this to backend_timeout?)
     timeout: int = 120
diff --git a/reflex/constants.py b/reflex/constants.py
index c2d2fc95a11..e3e9e7a9c77 100644
--- a/reflex/constants.py
+++ b/reflex/constants.py
@@ -134,6 +134,8 @@ def get_fnm_name() -> str | None:
 TAILWIND_CONTENT = ["./pages/**/*.{js,ts,jsx,tsx}"]
 # Relative tailwind style path to root stylesheet in STYLES_DIR.
 TAILWIND_ROOT_STYLE_PATH = "./tailwind.css"
+# The Tailwindcss version
+TAILWIND_VERSION = "tailwindcss@^3.3.2"
 # The NextJS config file
 NEXT_CONFIG_FILE = "next.config.js"
 # The sitemap config file.
diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py
index 28c6d268eca..bad58a5fa02 100644
--- a/reflex/utils/prerequisites.py
+++ b/reflex/utils/prerequisites.py
@@ -432,13 +432,23 @@ def install_frontend_packages(packages: set[str]):
     processes.show_status("Installing base frontend packages", process)
 
     config = get_config()
-    if config.tailwind is not None and "plugins" in config.tailwind:
+    if config.tailwind is not None:
+        # install tailwind
         process = processes.new_process(
-            [get_install_package_manager(), "add", *config.tailwind["plugins"]],
+            [get_install_package_manager(), "add", constants.TAILWIND_VERSION, "-d"],
             cwd=constants.WEB_DIR,
             shell=constants.IS_WINDOWS,
         )
-        processes.show_status("Installing tailwind packages", process)
+        processes.show_status("Installing tailwind", process)
+
+        # install tailwind plugins
+        if "plugins" in config.tailwind:
+            process = processes.new_process(
+                [get_install_package_manager(), "add", *config.tailwind["plugins"]],
+                cwd=constants.WEB_DIR,
+                shell=constants.IS_WINDOWS,
+            )
+            processes.show_status("Installing tailwind packages", process)
 
     # Install custom packages defined in frontend_packages
     if len(packages) > 0:
diff --git a/tests/compiler/test_compiler.py b/tests/compiler/test_compiler.py
index 0e51fd67da9..0b6cd6625d8 100644
--- a/tests/compiler/test_compiler.py
+++ b/tests/compiler/test_compiler.py
@@ -121,7 +121,6 @@ def test_compile_stylesheets(tmp_path, mocker):
     assets_dir.mkdir()
 
     (assets_dir / "styles.css").touch()
-
     mocker.patch("reflex.compiler.compiler.Path.cwd", return_value=project)
 
     stylesheets = [
@@ -141,6 +140,36 @@ def test_compile_stylesheets(tmp_path, mocker):
     )
 
 
+def test_compile_stylesheets_exclude_tailwind(tmp_path, mocker):
+    """Test that Tailwind is excluded if tailwind config is explicitly set to None.
+
+    Args:
+        tmp_path: The test directory.
+        mocker: Pytest mocker object.
+    """
+    project = tmp_path / "test_project"
+    project.mkdir()
+
+    assets_dir = project / "assets"
+    assets_dir.mkdir()
+    mock = mocker.Mock()
+
+    mocker.patch.object(mock, "tailwind", None)
+    mocker.patch("reflex.compiler.compiler.get_config", return_value=mock)
+
+    (assets_dir / "styles.css").touch()
+    mocker.patch("reflex.compiler.compiler.Path.cwd", return_value=project)
+
+    stylesheets = [
+        "/styles.css",
+    ]
+
+    assert compiler.compile_root_stylesheet(stylesheets) == (
+        os.path.join(".web", "styles", "styles.css"),
+        "@import url('@/styles.css'); \n",
+    )
+
+
 def test_compile_nonexistent_stylesheet(tmp_path, mocker):
     """Test that an error is thrown for non-existent stylesheets.
 


Code review comments:
diff_hunk: @@ -432,13 +432,23 @@ def install_frontend_packages(packages: set[str]):
     processes.show_status("Installing base frontend packages", process)
 
     config = get_config()
-    if config.tailwind is not None and "plugins" in config.tailwind:
+    if config.tailwind is not None:
+        # install tailwind
         process = processes.new_process(
-            [get_install_package_manager(), "add", *config.tailwind["plugins"]],
+            [get_install_package_manager(), "add", constants.TAILWIND_VERSION, "-d"],
path: reflex/utils/prerequisites.py
text: @user1:
```suggestion
            [get_install_package_manager(), "add", "-d", constants.TAILWIND_VERSION, *config.tailwind.get("plugins", [])],
```

does this work? seems a little more compact.