Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/reflex-base/src/reflex_base/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from typing import TYPE_CHECKING, Annotated, Any, ClassVar, Literal

from reflex_base import constants
from reflex_base.constants.base import LogLevel
from reflex_base.constants.base import LiteralColorMode, LogLevel
from reflex_base.environment import EnvironmentVariables as EnvironmentVariables
from reflex_base.environment import EnvVar as EnvVar
from reflex_base.environment import (
Expand Down Expand Up @@ -177,6 +177,7 @@ class BaseConfig:
redis_token_expiration: Token expiration time for redis state manager.
env_file: Path to file containing key-values pairs to override in the environment; Dotenv format.
state_auto_setters: Whether to automatically create setters for state base vars.
default_color_mode: The default color mode for the app: "system" (follow the OS preference), "light", or "dark". Applies to the built-in color mode switcher and `color_mode_cond` without requiring a radix theme.
show_built_with_reflex: Whether to display the sticky "Built with Reflex" badge on all pages.
is_reflex_cloud: Whether the app is running in the reflex cloud environment.
extra_overlay_function: Extra overlay function to run after the app is built. Formatted such that `from path_0.path_1... import path[-1]`, and calling it with no arguments would work. For example, "reflex_components_moment.moment".
Expand Down Expand Up @@ -251,6 +252,8 @@ class BaseConfig:

state_auto_setters: bool = False

default_color_mode: LiteralColorMode = "system"

show_built_with_reflex: bool | None = None

is_reflex_cloud: bool = False
Expand Down
8 changes: 8 additions & 0 deletions packages/reflex-base/src/reflex_base/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@
from .base import (
APP_HARNESS_FLAG,
COOKIES,
DARK_COLOR_MODE,
IS_LINUX,
IS_MACOS,
IS_WINDOWS,
LIGHT_COLOR_MODE,
LOCAL_STORAGE,
POLLING_MAX_HTTP_BUFFER_SIZE,
PYTEST_CURRENT_TEST,
REFLEX_VAR_CLOSING_TAG,
REFLEX_VAR_OPENING_TAG,
SESSION_STORAGE,
SYSTEM_COLOR_MODE,
ColorMode,
Dirs,
Env,
LiteralColorMode,
LogLevel,
Ping,
ReactRouter,
Expand Down Expand Up @@ -68,9 +72,11 @@
"ALEMBIC_CONFIG",
"APP_HARNESS_FLAG",
"COOKIES",
"DARK_COLOR_MODE",
"IS_LINUX",
"IS_MACOS",
"IS_WINDOWS",
"LIGHT_COLOR_MODE",
"LOCAL_STORAGE",
"NOCOMPILE_FILE",
"POLLING_MAX_HTTP_BUFFER_SIZE",
Expand All @@ -83,6 +89,7 @@
"ROUTE_NOT_FOUND",
"SESSION_STORAGE",
"SETTER_PREFIX",
"SYSTEM_COLOR_MODE",
"AgentsMd",
"Bun",
"ColorMode",
Expand All @@ -103,6 +110,7 @@
"GitIgnore",
"Hooks",
"Imports",
"LiteralColorMode",
"LogLevel",
"MemoizationDisposition",
"MemoizationMode",
Expand Down
6 changes: 6 additions & 0 deletions packages/reflex-base/src/reflex_base/constants/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ class ReactRouter(Javascript):


# Color mode variables
SYSTEM_COLOR_MODE: str = "system"
LIGHT_COLOR_MODE: str = "light"
DARK_COLOR_MODE: str = "dark"
LiteralColorMode = Literal["system", "light", "dark"]


class ColorMode(SimpleNamespace):
"""Constants related to ColorMode."""

Expand Down
11 changes: 5 additions & 6 deletions packages/reflex-base/src/reflex_base/style.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
from __future__ import annotations

from collections.abc import Mapping
from typing import Any, Literal
from typing import Any

from reflex_base import constants
from reflex_base.breakpoints import Breakpoints, breakpoints_values
from reflex_base.constants.base import DARK_COLOR_MODE as DARK_COLOR_MODE
from reflex_base.constants.base import LIGHT_COLOR_MODE as LIGHT_COLOR_MODE
from reflex_base.constants.base import SYSTEM_COLOR_MODE as SYSTEM_COLOR_MODE
from reflex_base.constants.base import LiteralColorMode as LiteralColorMode
from reflex_base.event import EventChain, EventHandler, EventSpec, run_script
from reflex_base.utils import format
from reflex_base.utils.exceptions import ReflexError
Expand All @@ -17,11 +21,6 @@
from reflex_base.vars.function import FunctionVar
from reflex_base.vars.object import ObjectVar

SYSTEM_COLOR_MODE: str = "system"
LIGHT_COLOR_MODE: str = "light"
DARK_COLOR_MODE: str = "dark"
LiteralColorMode = Literal["system", "light", "dark"]

# Reference the global ColorModeContext
color_mode_imports = {
f"$/{constants.Dirs.CONTEXTS_PATH}": [ImportVar(tag="ColorModeContext")],
Expand Down
3 changes: 1 addition & 2 deletions reflex/compiler/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
from reflex_base.constants.state import FIELD_MARKER
from reflex_base.environment import environment
from reflex_base.plugins import CompileContext, CompilerHooks, PageContext, Plugin
from reflex_base.style import SYSTEM_COLOR_MODE
from reflex_base.utils import memo_paths
from reflex_base.utils.exceptions import ReflexError
from reflex_base.utils.format import to_title_case
Expand Down Expand Up @@ -188,7 +187,7 @@ def _compile_contexts(state: type[BaseState] | None, theme: Component | None) ->
"""
appearance = getattr(theme, "appearance", None)
if appearance is None or str(LiteralVar.create(appearance)) == '"inherit"':
appearance = LiteralVar.create(SYSTEM_COLOR_MODE)
appearance = LiteralVar.create(get_config().default_color_mode)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate default color mode into the preload script

When an app sets default_color_mode="dark" or "light" and the browser has no saved theme in localStorage, this only changes the compiled context/ThemeProvider default; create_document_root still unconditionally injects preload_color_theme(), whose inline script applies localStorage.getItem("theme") || "system" before hydration. For example, a dark default on a light OS first paints light and then flips to dark after React initializes, so the new config still produces a visible FOUC/mismatched initial class unless the preload path receives the same default.

Useful? React with 👍 / 👎.


return (
templates.context_template(
Expand Down
33 changes: 33 additions & 0 deletions tests/units/compiler/test_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pytest_mock import MockerFixture
from reflex_base import constants
from reflex_base.components.dynamic import bundle_library, reset_bundled_libraries
from reflex_base.constants.base import LiteralColorMode
from reflex_base.constants.compiler import PageNames
from reflex_base.utils.imports import ImportVar, ParsedImportDict
from reflex_base.vars.base import Var
Expand Down Expand Up @@ -425,6 +426,38 @@ def test_compile_contexts_has_default_color_mode_context():
assert 'resolvedColorMode: defaultColorMode === "dark" ? "dark" : "light"' in code


def test_compile_contexts_default_color_mode_is_system():
"""Without any config override, the default color mode is "system"."""
_, code = compiler.compile_contexts(None, None)

assert 'export const defaultColorMode = "system"' in code
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated


@pytest.mark.parametrize("mode", ["system", "light", "dark"])
def test_compile_contexts_uses_config_default_color_mode(
mode: LiteralColorMode, mocker: MockerFixture
):
"""The Config.default_color_mode option should drive the compiled default."""
config = rx.config.get_config()
config.default_color_mode = mode
mocker.patch("reflex.compiler.compiler.get_config", return_value=config)

_, code = compiler.compile_contexts(None, None)

assert f'export const defaultColorMode = "{mode}"' in code


def test_compile_contexts_theme_appearance_overrides_config(mocker: MockerFixture):
"""An explicit theme appearance should still win over the config default."""
config = rx.config.get_config()
config.default_color_mode = "light"
mocker.patch("reflex.compiler.compiler.get_config", return_value=config)

_, code = compiler.compile_contexts(None, rx.theme(appearance="dark"))

assert 'export const defaultColorMode = "dark"' in code


def test_compile_nonexistent_stylesheet(tmp_path, mocker: MockerFixture):
"""Test that an error is thrown for non-existent stylesheets.

Expand Down
Loading