Skip to content

Add theme field to context, it would contain only type field for now "light" or "dark"#10972

Merged
kajarenc merged 37 commits intodevelopfrom
add-theme-field-to-context-one-field
Jun 5, 2025
Merged

Add theme field to context, it would contain only type field for now "light" or "dark"#10972
kajarenc merged 37 commits intodevelopfrom
add-theme-field-to-context-one-field

Conversation

@kajarenc
Copy link
Copy Markdown
Contributor

@kajarenc kajarenc commented Mar 31, 2025

Describe your changes

Add theme field to context, it would contain only type field for now, "light" or "dark".

GitHub Issue Link (if applicable)

Closes #5009

Testing Plan

  • Explanation of why no additional tests are needed
  • Unit Tests (JS and/or Python)
  • E2E Tests
  • Any manual testing needed?

Contribution License Agreement

By submitting this pull request you agree that all contributions to this project are made under the Apache 2.0 license.

@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Mar 31, 2025

🎉 Snyk checks have passed. No issues have been found so far.

security/snyk check is complete. No issues have been found. (View Details)

license/snyk check is complete. No issues have been found. (View Details)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2025

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-10972/streamlit-1.45.1-py3-none-any.whl
🕹️ Preview app pr-10972.streamlit.app (☁️ Deploy here if not accessible)

@kajarenc kajarenc added security-assessment-completed impact:internal PR changes only affect internal code change:chore PR contains maintenance or housekeeping change labels Apr 29, 2025
@kajarenc kajarenc changed the title [WIP] Add theme field to context, one field solution Add theme field to context, one field solution Apr 29, 2025
@kajarenc kajarenc changed the title Add theme field to context, one field solution Add theme field to context, it would contain only type field for now "light" or "dark" Apr 29, 2025
@kajarenc kajarenc marked this pull request as ready for review April 29, 2025 10:44
@kajarenc kajarenc requested a review from a team as a code owner April 29, 2025 10:44
Comment on lines +580 to +584
if (
_prevProps.theme.activeTheme.name !== this.props.theme.activeTheme.name
) {
this.sendRerunBackMsg()
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Does this also cover scenarios like the theme getting changed via host communication or via theme editor? And do we want to do something when the system theme changes during runtime?

Also, is there a risk of redundant reruns -> e.g. does this lead to always having another rerun when an themed app sessions starts?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Does this also cover scenarios like the theme getting changed via host communication or via theme editor? And do we want to do something when the system theme changes during runtime?

Yes, tested it manually and also added e2e playwright for sending a dark theme host to guest message, and it works as expected.

I'm not sure about redundant reruns, I think componentDidUpdate lifecycle method should correctly handle initial render.

kajarenc added 6 commits May 29, 2025 16:56
always return StreamlitTheme, type could be None
## Describe your changes

Move AttributeDictionary to main util file, because now it is also
imported in context.py

<!-- If it's a visual change, please include a screenshot or video! -->

## GitHub Issue Link (if applicable)

## Testing Plan

Simple refactoring of moving class from one file to another, covered by
existing tests

- Explanation of why no additional tests are needed
- Unit Tests (JS and/or Python)
- E2E Tests
- Any manual testing needed?

---

**Contribution License Agreement**

By submitting this pull request you agree that all contributions to this
project are made under the Apache 2.0 license.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented May 31, 2025

📉 Python coverage change detected

The Python unit test coverage has decreased by 0.0277%

  • Current PR: 92.2115% (18001 statements, 1402 missed)
  • Latest develop: 92.2393% (17988 statements, 1396 missed)

✅ Coverage change is within normal range.

Coverage by files
Name Stmts Miss Cover
streamlit/__init__.py 139 0 100%
streamlit/__main__.py 3 3 0%
streamlit/auth_util.py 100 25 75%
streamlit/cli_util.py 39 6 85%
streamlit/column_config.py 3 0 100%
streamlit/commands/__init__.py 0 0 100%
streamlit/commands/echo.py 54 11 80%
streamlit/commands/execution_control.py 59 19 68%
streamlit/commands/experimental_query_params.py 40 2 95%
streamlit/commands/logo.py 40 6 85%
streamlit/commands/navigation.py 100 2 98%
streamlit/commands/page_config.py 97 4 96%
streamlit/components/__init__.py 0 0 100%
streamlit/components/lib/__init__.py 0 0 100%
streamlit/components/lib/local_component_registry.py 35 2 94%
streamlit/components/types/__init__.py 0 0 100%
streamlit/components/types/base_component_registry.py 14 0 100%
streamlit/components/types/base_custom_component.py 47 5 89%
streamlit/components/v1/__init__.py 5 0 100%
streamlit/components/v1/component_arrow.py 33 8 76%
streamlit/components/v1/component_registry.py 38 3 92%
streamlit/components/v1/components.py 4 4 0%
streamlit/components/v1/custom_component.py 91 7 92%
streamlit/config.py 327 11 97%
streamlit/config_option.py 79 3 96%
streamlit/config_util.py 81 1 99%
streamlit/connections/__init__.py 6 0 100%
streamlit/connections/base_connection.py 45 0 100%
streamlit/connections/snowflake_connection.py 60 13 78%
streamlit/connections/snowpark_connection.py 44 3 93%
streamlit/connections/sql_connection.py 55 6 89%
streamlit/connections/util.py 33 0 100%
streamlit/cursor.py 82 2 98%
streamlit/dataframe_util.py 506 45 91%
streamlit/delta_generator.py 203 6 97%
streamlit/delta_generator_singletons.py 76 8 89%
streamlit/deprecation_util.py 57 4 93%
streamlit/development.py 1 0 100%
streamlit/elements/__init__.py 0 0 100%
streamlit/elements/alert.py 60 0 100%
streamlit/elements/arrow.py 170 12 93%
streamlit/elements/balloons.py 10 0 100%
streamlit/elements/bokeh_chart.py 25 0 100%
streamlit/elements/code.py 18 0 100%
streamlit/elements/deck_gl_json_chart.py 97 6 94%
streamlit/elements/dialog_decorator.py 58 2 97%
streamlit/elements/doc_string.py 226 9 96%
streamlit/elements/empty.py 16 4 75%
streamlit/elements/exception.py 101 10 90%
streamlit/elements/form.py 51 2 96%
streamlit/elements/graphviz_chart.py 29 1 97%
streamlit/elements/heading.py 50 0 100%
streamlit/elements/html.py 45 0 100%
streamlit/elements/iframe.py 32 1 97%
streamlit/elements/image.py 32 1 97%
streamlit/elements/json.py 39 2 95%
streamlit/elements/layouts.py 124 2 98%
streamlit/elements/lib/__init__.py 0 0 100%
streamlit/elements/lib/built_in_chart_utils.py 359 27 92%
streamlit/elements/lib/color_util.py 102 4 96%
streamlit/elements/lib/column_config_utils.py 169 1 99%
streamlit/elements/lib/column_types.py 129 0 100%
streamlit/elements/lib/dialog.py 54 2 96%
streamlit/elements/lib/dicttools.py 39 2 95%
streamlit/elements/lib/file_uploader_utils.py 22 0 100%
streamlit/elements/lib/form_utils.py 26 0 100%
streamlit/elements/lib/image_utils.py 179 25 86%
streamlit/elements/lib/js_number.py 28 3 89%
streamlit/elements/lib/layout_utils.py 53 0 100%
streamlit/elements/lib/mutable_status_container.py 73 4 95%
streamlit/elements/lib/options_selector_utils.py 97 0 100%
streamlit/elements/lib/pandas_styler_utils.py 68 1 99%
streamlit/elements/lib/policies.py 56 1 98%
streamlit/elements/lib/streamlit_plotly_theme.py 49 0 100%
streamlit/elements/lib/subtitle_utils.py 76 13 83%
streamlit/elements/lib/utils.py 77 5 94%
streamlit/elements/map.py 108 4 96%
streamlit/elements/markdown.py 57 2 96%
streamlit/elements/media.py 183 8 96%
streamlit/elements/metric.py 79 5 94%
streamlit/elements/plotly_chart.py 95 3 97%
streamlit/elements/progress.py 37 0 100%
streamlit/elements/pyplot.py 34 2 94%
streamlit/elements/snow.py 10 0 100%
streamlit/elements/spinner.py 31 0 100%
streamlit/elements/text.py 13 0 100%
streamlit/elements/toast.py 17 0 100%
streamlit/elements/vega_charts.py 208 3 99%
streamlit/elements/widgets/__init__.py 0 0 100%
streamlit/elements/widgets/audio_input.py 63 11 83%
streamlit/elements/widgets/button.py 196 45 77%
streamlit/elements/widgets/button_group.py 169 0 100%
streamlit/elements/widgets/camera_input.py 63 10 84%
streamlit/elements/widgets/chat.py 170 42 75%
streamlit/elements/widgets/checkbox.py 49 0 100%
streamlit/elements/widgets/color_picker.py 53 3 94%
streamlit/elements/widgets/data_editor.py 232 13 94%
streamlit/elements/widgets/file_uploader.py 108 19 82%
streamlit/elements/widgets/multiselect.py 105 4 96%
streamlit/elements/widgets/number_input.py 154 6 96%
streamlit/elements/widgets/radio.py 85 6 93%
streamlit/elements/widgets/select_slider.py 101 1 99%
streamlit/elements/widgets/selectbox.py 102 2 98%
streamlit/elements/widgets/slider.py 248 9 96%
streamlit/elements/widgets/text_widgets.py 131 7 95%
streamlit/elements/widgets/time_widgets.py 255 17 93%
streamlit/elements/write.py 170 30 82%
streamlit/emojis.py 4 0 100%
streamlit/env_util.py 21 3 86%
streamlit/error_util.py 33 2 94%
streamlit/errors.py 151 28 81%
streamlit/external/__init__.py 0 0 100%
streamlit/external/langchain/__init__.py 2 0 100%
streamlit/external/langchain/streamlit_callback_handler.py 141 82 42%
streamlit/file_util.py 84 7 92%
streamlit/git_util.py 100 63 37%
streamlit/logger.py 54 0 100%
streamlit/material_icon_names.py 1 0 100%
streamlit/navigation/__init__.py 0 0 100%
streamlit/navigation/page.py 76 2 97%
streamlit/net_util.py 55 3 95%
streamlit/platform.py 10 1 90%
streamlit/runtime/__init__.py 8 0 100%
streamlit/runtime/app_session.py 375 58 85%
streamlit/runtime/caching/__init__.py 19 0 100%
streamlit/runtime/caching/cache_data_api.py 169 3 98%
streamlit/runtime/caching/cache_errors.py 45 1 98%
streamlit/runtime/caching/cache_resource_api.py 126 0 100%
streamlit/runtime/caching/cache_type.py 11 1 91%
streamlit/runtime/caching/cache_utils.py 160 9 94%
streamlit/runtime/caching/cached_message_replay.py 103 2 98%
streamlit/runtime/caching/hashing.py 301 22 93%
streamlit/runtime/caching/legacy_cache_api.py 13 0 100%
streamlit/runtime/caching/storage/__init__.py 2 0 100%
streamlit/runtime/caching/storage/cache_storage_protocol.py 31 2 94%
streamlit/runtime/caching/storage/dummy_cache_storage.py 21 0 100%
streamlit/runtime/caching/storage/in_memory_cache_storage_wrapper.py 60 0 100%
streamlit/runtime/caching/storage/local_disk_cache_storage.py 86 4 95%
streamlit/runtime/connection_factory.py 85 9 89%
streamlit/runtime/context.py 140 41 71%
streamlit/runtime/context_util.py 18 0 100%
streamlit/runtime/credentials.py 140 8 94%
streamlit/runtime/forward_msg_cache.py 23 2 91%
streamlit/runtime/forward_msg_queue.py 63 4 94%
streamlit/runtime/fragment.py 124 2 98%
streamlit/runtime/media_file_manager.py 69 7 90%
streamlit/runtime/media_file_storage.py 15 0 100%
streamlit/runtime/memory_media_file_storage.py 68 0 100%
streamlit/runtime/memory_session_storage.py 15 0 100%
streamlit/runtime/memory_uploaded_file_manager.py 41 1 98%
streamlit/runtime/metrics_util.py 194 12 94%
streamlit/runtime/pages_manager.py 59 2 97%
streamlit/runtime/runtime.py 235 16 93%
streamlit/runtime/runtime_util.py 30 1 97%
streamlit/runtime/script_data.py 16 0 100%
streamlit/runtime/scriptrunner/__init__.py 5 0 100%
streamlit/runtime/scriptrunner/exec_code.py 49 5 90%
streamlit/runtime/scriptrunner/magic.py 83 1 99%
streamlit/runtime/scriptrunner/magic_funcs.py 10 1 90%
streamlit/runtime/scriptrunner/script_cache.py 27 0 100%
streamlit/runtime/scriptrunner/script_runner.py 228 27 88%
streamlit/runtime/scriptrunner_utils/__init__.py 0 0 100%
streamlit/runtime/scriptrunner_utils/exceptions.py 11 1 91%
streamlit/runtime/scriptrunner_utils/script_requests.py 106 5 95%
streamlit/runtime/scriptrunner_utils/script_run_context.py 136 2 99%
streamlit/runtime/secrets.py 242 25 90%
streamlit/runtime/session_manager.py 60 1 98%
streamlit/runtime/state/__init__.py 7 0 100%
streamlit/runtime/state/common.py 49 2 96%
streamlit/runtime/state/query_params.py 109 3 97%
streamlit/runtime/state/query_params_proxy.py 81 0 100%
streamlit/runtime/state/safe_session_state.py 73 14 81%
streamlit/runtime/state/session_state.py 359 13 96%
streamlit/runtime/state/session_state_proxy.py 62 8 87%
streamlit/runtime/state/widgets.py 12 1 92%
streamlit/runtime/stats.py 42 0 100%
streamlit/runtime/uploaded_file_manager.py 37 2 95%
streamlit/runtime/websocket_session_manager.py 62 0 100%
streamlit/source_util.py 36 1 97%
streamlit/string_util.py 74 2 97%
streamlit/temporary_directory.py 16 1 94%
streamlit/testing/__init__.py 0 0 100%
streamlit/testing/v1/__init__.py 2 0 100%
streamlit/testing/v1/app_test.py 239 6 97%
streamlit/testing/v1/element_tree.py 1326 84 94%
streamlit/testing/v1/local_script_runner.py 71 2 97%
streamlit/testing/v1/util.py 17 0 100%
streamlit/time_util.py 32 1 97%
streamlit/type_util.py 143 9 94%
streamlit/url_util.py 40 5 88%
streamlit/user_info.py 87 8 91%
streamlit/util.py 35 1 97%
streamlit/version.py 3 0 100%
streamlit/watcher/__init__.py 3 0 100%
streamlit/watcher/event_based_path_watcher.py 156 20 87%
streamlit/watcher/folder_black_list.py 14 1 93%
streamlit/watcher/local_sources_watcher.py 125 8 94%
streamlit/watcher/path_watcher.py 43 3 93%
streamlit/watcher/polling_path_watcher.py 50 2 96%
streamlit/watcher/util.py 51 1 98%
streamlit/web/__init__.py 0 0 100%
streamlit/web/bootstrap.py 145 19 87%
streamlit/web/cache_storage_manager_config.py 5 0 100%
streamlit/web/cli.py 176 17 90%
streamlit/web/server/__init__.py 5 0 100%
streamlit/web/server/app_static_file_handler.py 29 3 90%
streamlit/web/server/authlib_tornado_integration.py 18 1 94%
streamlit/web/server/browser_websocket_handler.py 109 27 75%
streamlit/web/server/component_request_handler.py 64 6 91%
streamlit/web/server/media_file_handler.py 65 9 86%
streamlit/web/server/oauth_authlib_routes.py 118 18 85%
streamlit/web/server/oidc_mixin.py 44 0 100%
streamlit/web/server/routes.py 87 7 92%
streamlit/web/server/server.py 177 11 94%
streamlit/web/server/server_util.py 63 5 92%
streamlit/web/server/stats_request_handler.py 53 4 92%
streamlit/web/server/upload_file_request_handler.py 53 9 83%
streamlit/web/server/websocket_headers.py 19 1 95%
TOTAL 18001 1402 92%

📊 View detailed coverage comparison

kajarenc added 3 commits May 31, 2025 19:12
# Conflicts:
#	e2e_playwright/__snapshots__/linux/hostframe_app_test/hostframe_app-theme_message_after[chromium].png
#	e2e_playwright/__snapshots__/linux/hostframe_app_test/hostframe_app-theme_message_before[chromium].png
#	e2e_playwright/__snapshots__/linux/hostframe_app_test/hostframe_app-toolbar_items[chromium].png
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This snapshot change most probably happened because I added a new toolbar button (for sending dark theme host to guest message), and the hostframe toolbar is now a couple of pixel wider

Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch left a comment

Choose a reason for hiding this comment

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

LGTM 👍

Comment on lines +61 to +63
def test_theme_type(themed_app: Page, request: pytest.FixtureRequest):
"""Test that the theme.type is correctly set."""
app_theme = request.getfixturevalue("app_theme")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: I think you can also just directly use app_theme: str as a fixture.

attribute-style access.
"""

type: str | None
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: a small typing improvement might be to use Literal["dark", "light"] | None here. But maybe mypy will complain

@lukasmasuch lukasmasuch requested a review from Copilot June 4, 2025 17:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces a new theme field in the app context so that clients can detect whether the active theme is "light" or "dark".

  • Added a color_scheme field to ContextInfo in the protobuf definition.
  • Introduced StreamlitTheme on the Python side (using AttributeDictionary) and exposed it via st.context.theme.
  • Updated the frontend (App.tsx) to send the theme to the backend, rerun on theme changes, and augmented tests to cover the new field.

Reviewed Changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
proto/streamlit/proto/ClientState.proto Added optional string color_scheme to ContextInfo.
lib/streamlit/util.py Introduced AttributeDictionary for attribute-style dicts.
lib/streamlit/runtime/context.py Added StreamlitTheme and context.theme property.
lib/streamlit/elements/vega_charts.py Replaced internal AttributeDictionary import with util.
lib/streamlit/elements/plotly_chart.py Ditto for AttributeDictionary.
lib/streamlit/elements/lib/event_utils.py Removed deprecated AttributeDictionary file.
lib/streamlit/elements/deck_gl_json_chart.py Updated import to use util’s AttributeDictionary.
lib/streamlit/elements/arrow.py Refactored import and cleaned up **kwargs annotation.
frontend/app/src/App.tsx Sends colorScheme, handles reruns on theme changes.
frontend/app/src/App.test.tsx Added colorScheme to mocked context in tests.
e2e_playwright/test_assets/hostframe.html Added sendDarkThemeMessage button for dark theme.
e2e_playwright/st_context_test.py New test for Theme type: after rerun.
e2e_playwright/st_context.py Writes Theme type: in the sample app.
e2e_playwright/hostframe_app_test.py Incremented toolbar button count and added dark theme test.
e2e_playwright/hostframe_app.py Writes Theme type: in the hostframe sample.
Comments suppressed due to low confidence (4)

proto/streamlit/proto/ClientState.proto:30

  • [nitpick] Consider whether the field name color_scheme should use a JSON-friendly camelCase alias (colorScheme) via protobuf options, to match the frontend naming and avoid mapping issues.
optional string color_scheme = 6;

lib/streamlit/runtime/context.py:67

  • [nitpick] The StreamlitTheme docstring largely duplicates AttributeDictionary docs. Consider simplifying to focus on the theme-specific fields (type) and behavior.
class StreamlitTheme(AttributeDictionary):

lib/streamlit/runtime/context.py:77

  • [nitpick] Using type as an attribute name may be confusing (it shadows the built-in). Consider renaming to scheme or color_scheme on the StreamlitTheme object for clarity.
type: str | None

frontend/app/src/App.tsx:1653

  • The frontend sends colorScheme (camelCase), but the proto field is defined as color_scheme (snake_case). Ensure the JSON-to-proto mapping aligns or rename to match the protobuf field to avoid the value being dropped.
colorScheme: this.getThemeColorScheme(),

@kajarenc kajarenc merged commit ca1b764 into develop Jun 5, 2025
37 checks passed
@kajarenc kajarenc deleted the add-theme-field-to-context-one-field branch June 5, 2025 19:23
@bew
Copy link
Copy Markdown

bew commented Jun 19, 2025

Hello, the documentation at https://docs.streamlit.io/develop/api-reference/caching-and-state/st.context is not up-to-date and doesn't include theme after ~2 weeks, is that kind of delay normal? 🤔

Ah it seems that 1.45.2 is still not released actually, so that might explain.. But it's included in 1.46.0 which was released yesterday, could you tell when do you think the docs will be updated with this?

@sfc-gh-lwilby
Copy link
Copy Markdown
Collaborator

https://docs.streamlit.io/develop/api-reference/caching-and-state/st.context

This is probably missed because we tagged it impact:internal and it wouldn't have been in the list for docs updates. cc @sfc-gh-dmatthews .

Thanks for letting us know @bew .

@jrieke jrieke added impact:users PR changes affect end users and removed impact:internal PR changes only affect internal code labels Jun 19, 2025
@jrieke
Copy link
Copy Markdown
Collaborator

jrieke commented Jun 19, 2025

It's updated now! Also changed the label here.

@lukasmasuch lukasmasuch added change:feature PR contains new feature or enhancement implementation and removed change:chore PR contains maintenance or housekeeping change labels Jun 19, 2025
@bew
Copy link
Copy Markdown

bew commented Jun 20, 2025

great thank you 👍

@sfc-gh-dmatthews
Copy link
Copy Markdown
Contributor

I can confirm: st.context.theme.type went out with 1.46.0 and is in the release notes and docs. https://docs.streamlit.io/develop/api-reference/caching-and-state/st.context#contexttheme

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:feature PR contains new feature or enhancement implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Detect if the app is in Light/Dark mode at runtime

8 participants