From 1952427e28f3fe0304be54cdc9c1ff13b67de1cd Mon Sep 17 00:00:00 2001 From: Anas Khan <83116240+anxkhn@users.noreply.github.com> Date: Mon, 6 Jul 2026 12:54:48 +0530 Subject: [PATCH 1/2] fix(compiler): use posix separators for nested stylesheet imports The root stylesheet is compiled by interpolating each stylesheet's relative path into a CSS `@import url('...')`. The path was rendered with `str(target_path)`, which uses the OS separator, so on Windows a nested stylesheet (e.g. `rx.App(stylesheets=["subdir/theme.css"])`, or a stylesheet directory expanded via rglob) emitted `@import url('./subdir\theme.css');`. In CSS a backslash inside url() is an escape introducer, not a path separator, so the browser requested a mangled URL and the nested stylesheet silently failed to load on Windows only. Top-level stylesheets have no separator, which is why this went unnoticed. Build the URL with `target_path.as_posix()` so the import always uses forward slashes. On POSIX this is identical to the previous output; it only fixes Windows. Add a regression test that forces a Windows-style relative path and asserts the emitted import is POSIX-normalized, and make the existing scss/sass assertions separator-independent. Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com> --- reflex/compiler/compiler.py | 6 ++- tests/units/compiler/test_compiler.py | 59 ++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 6 deletions(-) diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index f7b079ab0c7..b08e3984ae1 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -398,7 +398,11 @@ def _compile_root_stylesheet( except ImportError: failed_to_import_sass = True - str_target_path = "./" + str(target_path) + # Use POSIX separators: this string is emitted verbatim into a CSS + # `@import url(...)`, where a backslash is an escape introducer, not a + # path separator, so `str(target_path)` would break nested stylesheets + # on Windows. + str_target_path = "./" + target_path.as_posix() sheets.append(str_target_path) if str_target_path not in sheets else None if failed_to_import_sass: diff --git a/tests/units/compiler/test_compiler.py b/tests/units/compiler/test_compiler.py index c6791fc5be8..ef64862382f 100644 --- a/tests/units/compiler/test_compiler.py +++ b/tests/units/compiler/test_compiler.py @@ -1,6 +1,6 @@ import importlib.util import os -from pathlib import Path +from pathlib import Path, PureWindowsPath import pytest from pytest_mock import MockerFixture @@ -231,8 +231,8 @@ def test_compile_stylesheets_scss_sass(tmp_path: Path, mocker: MockerFixture): "@layer __reflex_base;\n" "@import url('./__reflex_style_reset.css');\n" "@import url('./style.css');\n" - f"@import url('./{Path('preprocess') / Path('styles_a.css')!s}');\n" - f"@import url('./{Path('preprocess') / Path('styles_b.css')!s}');" + "@import url('./preprocess/styles_a.css');\n" + "@import url('./preprocess/styles_b.css');" ), ) @@ -252,8 +252,8 @@ def test_compile_stylesheets_scss_sass(tmp_path: Path, mocker: MockerFixture): "@layer __reflex_base;\n" "@import url('./__reflex_style_reset.css');\n" "@import url('./style.css');\n" - f"@import url('./{Path('preprocess') / Path('styles_a.css')!s}');\n" - f"@import url('./{Path('preprocess') / Path('styles_b.css')!s}');" + "@import url('./preprocess/styles_a.css');\n" + "@import url('./preprocess/styles_b.css');" ), ) @@ -270,6 +270,55 @@ def test_compile_stylesheets_scss_sass(tmp_path: Path, mocker: MockerFixture): ).read_text() == expected_result +def test_compile_stylesheets_nested_import_uses_posix_separators( + tmp_path: Path, mocker: MockerFixture +): + """Nested stylesheet imports must use forward slashes on every platform. + + A CSS `@import url(...)` treats a backslash as an escape introducer, not a + path separator, so emitting `str(target_path)` breaks nested stylesheets on + Windows (where the relative path is backslash-separated). Simulate that by + forcing the relative path to a `PureWindowsPath` and assert the emitted + import is POSIX-normalized. + + Args: + tmp_path: The test directory. + mocker: Pytest mocker object. + """ + project = tmp_path / "test_project" + project.mkdir() + + assets_dir = project / "assets" + assets_dir.mkdir() + + nested_dir = assets_dir / "subdir" + nested_dir.mkdir() + (nested_dir / "theme.css").write_text(".root { color: red; }") + + mocker.patch("reflex.compiler.compiler.Path.cwd", return_value=project) + mocker.patch( + "reflex.compiler.compiler.get_web_dir", + return_value=project / constants.Dirs.WEB, + ) + mocker.patch( + "reflex.compiler.utils.get_web_dir", return_value=project / constants.Dirs.WEB + ) + + real_relative_to = Path.relative_to + mocker.patch.object( + Path, + "relative_to", + lambda self, *args, **kwargs: PureWindowsPath( + real_relative_to(self, *args, **kwargs) + ), + ) + + _, code = compiler.compile_root_stylesheet(["/subdir/theme.css"]) + + assert "@import url('./subdir/theme.css');" in code + assert "\\" not in code + + def test_compile_stylesheets_exclude_tailwind(tmp_path, mocker: MockerFixture): """Test that Tailwind is excluded if tailwind config is explicitly set to None. From 366ade7ff39d928f022436b729cb55501d853914 Mon Sep 17 00:00:00 2001 From: Anas Khan <83116240+anxkhn@users.noreply.github.com> Date: Tue, 7 Jul 2026 04:51:43 +0530 Subject: [PATCH 2/2] fix(compiler): add changelog fragment for nested stylesheet posix fix Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com> --- news/6725.bugfix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/6725.bugfix.md diff --git a/news/6725.bugfix.md b/news/6725.bugfix.md new file mode 100644 index 00000000000..c9351017987 --- /dev/null +++ b/news/6725.bugfix.md @@ -0,0 +1 @@ +Fixed nested/subfolder stylesheets failing to load on Windows because the generated CSS `@import` used backslash path separators (which CSS treats as escape sequences); the import URL is now always POSIX-normalized.