Skip to content

Conversation

@mayagbarnes
Copy link
Collaborator

@mayagbarnes mayagbarnes commented Oct 2, 2025

Describe your changes

Part 1 of Custom Dark Theme Feature
PR adds the following new sections under [theme] configuration options (via config.py & NewSession.proto):

  • [theme.light]
  • [theme.dark]
  • [theme.light.sidebar]
  • [theme.dark.sidebar]

Invalid sections (ex: [theme.sidebar.light]) throw an error.

Note: Logic in this PR does not fully handle setting of values in the theme input of new session message / proper section precedence between a base theme .toml file, config.toml, CLI/env vars). The next PR will handle the rest of the backend logic to fully support these new light/dark section configurations.

Testing Plan

  • Python Unit Tests: ✅ Added
  • Manual Testing: ✅

@snyk-io
Copy link
Contributor

snyk-io bot commented Oct 2, 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
Contributor

github-actions bot commented Oct 2, 2025

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-12680/streamlit-1.50.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-12680.streamlit.app (☁️ Deploy here if not accessible)

@github-actions
Copy link
Contributor

github-actions bot commented Oct 3, 2025

📈 Python coverage change detected

The Python unit test coverage has increased by 0.0282%

  • Current PR: 93.0853% (18786 statements, 1299 missed)
  • Latest develop: 93.0571% (18753 statements, 1302 missed)

✅ Coverage change is within normal range.

Coverage by files
Name Stmts Miss Cover
streamlit/__init__.py 138 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 10 83%
streamlit/commands/experimental_query_params.py 40 2 95%
streamlit/commands/logo.py 41 6 85%
streamlit/commands/navigation.py 107 2 98%
streamlit/commands/page_config.py 102 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 49 6 88%
streamlit/components/v1/__init__.py 5 0 100%
streamlit/components/v1/component_arrow.py 33 8 76%
streamlit/components/v1/component_registry.py 41 3 93%
streamlit/components/v1/components.py 4 4 0%
streamlit/components/v1/custom_component.py 85 7 92%
streamlit/config.py 412 12 97%
streamlit/config_option.py 79 3 96%
streamlit/config_util.py 277 5 98%
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 56 6 89%
streamlit/connections/util.py 33 0 100%
streamlit/cursor.py 82 2 98%
streamlit/dataframe_util.py 502 45 91%
streamlit/delta_generator.py 204 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 199 15 92%
streamlit/elements/balloons.py 10 0 100%
streamlit/elements/bokeh_chart.py 27 0 100%
streamlit/elements/code.py 20 1 95%
streamlit/elements/deck_gl_json_chart.py 105 10 90%
streamlit/elements/dialog_decorator.py 37 0 100%
streamlit/elements/doc_string.py 227 9 96%
streamlit/elements/empty.py 16 4 75%
streamlit/elements/exception.py 101 10 90%
streamlit/elements/form.py 54 2 96%
streamlit/elements/graphviz_chart.py 36 1 97%
streamlit/elements/heading.py 57 0 100%
streamlit/elements/html.py 48 0 100%
streamlit/elements/iframe.py 29 0 100%
streamlit/elements/image.py 33 0 100%
streamlit/elements/json.py 39 2 95%
streamlit/elements/layouts.py 141 3 98%
streamlit/elements/lib/__init__.py 0 0 100%
streamlit/elements/lib/built_in_chart_utils.py 388 26 93%
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 186 4 98%
streamlit/elements/lib/dialog.py 67 1 99%
streamlit/elements/lib/dicttools.py 39 2 95%
streamlit/elements/lib/file_uploader_utils.py 30 0 100%
streamlit/elements/lib/form_utils.py 26 0 100%
streamlit/elements/lib/image_utils.py 177 21 88%
streamlit/elements/lib/js_number.py 28 3 89%
streamlit/elements/lib/layout_utils.py 95 1 99%
streamlit/elements/lib/mutable_status_container.py 73 4 95%
streamlit/elements/lib/options_selector_utils.py 90 0 100%
streamlit/elements/lib/pandas_styler_utils.py 80 2 98%
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 110 1 99%
streamlit/elements/markdown.py 63 2 97%
streamlit/elements/media.py 182 8 96%
streamlit/elements/metric.py 92 0 100%
streamlit/elements/pdf.py 50 2 96%
streamlit/elements/plotly_chart.py 114 4 96%
streamlit/elements/progress.py 37 0 100%
streamlit/elements/pyplot.py 39 2 95%
streamlit/elements/snow.py 10 0 100%
streamlit/elements/spinner.py 34 0 100%
streamlit/elements/text.py 16 0 100%
streamlit/elements/toast.py 26 0 100%
streamlit/elements/vega_charts.py 219 3 99%
streamlit/elements/widgets/__init__.py 0 0 100%
streamlit/elements/widgets/audio_input.py 69 11 84%
streamlit/elements/widgets/button.py 215 3 99%
streamlit/elements/widgets/button_group.py 162 1 99%
streamlit/elements/widgets/camera_input.py 63 10 84%
streamlit/elements/widgets/chat.py 169 40 76%
streamlit/elements/widgets/checkbox.py 52 0 100%
streamlit/elements/widgets/color_picker.py 56 2 96%
streamlit/elements/widgets/data_editor.py 240 14 94%
streamlit/elements/widgets/file_uploader.py 104 18 83%
streamlit/elements/widgets/multiselect.py 105 4 96%
streamlit/elements/widgets/number_input.py 144 5 97%
streamlit/elements/widgets/radio.py 83 6 93%
streamlit/elements/widgets/select_slider.py 98 1 99%
streamlit/elements/widgets/selectbox.py 91 2 98%
streamlit/elements/widgets/slider.py 242 9 96%
streamlit/elements/widgets/text_widgets.py 130 6 95%
streamlit/elements/widgets/time_widgets.py 249 15 94%
streamlit/elements/write.py 166 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 162 24 85%
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 8 90%
streamlit/git_util.py 100 5 95%
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 78 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 443 93 79%
streamlit/runtime/caching/__init__.py 19 0 100%
streamlit/runtime/caching/cache_data_api.py 164 3 98%
streamlit/runtime/caching/cache_errors.py 45 1 98%
streamlit/runtime/caching/cache_resource_api.py 121 0 100%
streamlit/runtime/caching/cache_type.py 11 1 91%
streamlit/runtime/caching/cache_utils.py 165 9 95%
streamlit/runtime/caching/cached_message_replay.py 108 1 99%
streamlit/runtime/caching/hashing.py 311 25 92%
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 0 100%
streamlit/runtime/context_util.py 18 0 100%
streamlit/runtime/credentials.py 139 4 97%
streamlit/runtime/forward_msg_cache.py 23 2 91%
streamlit/runtime/forward_msg_queue.py 63 4 94%
streamlit/runtime/fragment.py 111 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 190 12 94%
streamlit/runtime/pages_manager.py 59 2 97%
streamlit/runtime/runtime.py 241 18 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 230 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 110 3 97%
streamlit/runtime/state/query_params_proxy.py 71 0 100%
streamlit/runtime/state/safe_session_state.py 77 11 86%
streamlit/runtime/state/session_state.py 361 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/theme_util.py 46 1 98%
streamlit/runtime/uploaded_file_manager.py 39 3 92%
streamlit/runtime/websocket_session_manager.py 66 0 100%
streamlit/source_util.py 36 1 97%
streamlit/string_util.py 89 7 92%
streamlit/temporary_directory.py 18 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 1319 84 94%
streamlit/testing/v1/local_script_runner.py 71 2 97%
streamlit/testing/v1/util.py 17 0 100%
streamlit/time_util.py 28 1 96%
streamlit/type_util.py 139 12 91%
streamlit/url_util.py 40 5 88%
streamlit/user_info.py 87 8 91%
streamlit/util.py 38 1 97%
streamlit/version.py 3 0 100%
streamlit/watcher/__init__.py 3 0 100%
streamlit/watcher/event_based_path_watcher.py 175 24 86%
streamlit/watcher/folder_black_list.py 14 1 93%
streamlit/watcher/local_sources_watcher.py 127 9 93%
streamlit/watcher/path_watcher.py 43 3 93%
streamlit/watcher/polling_path_watcher.py 55 2 96%
streamlit/watcher/util.py 49 1 98%
streamlit/web/__init__.py 0 0 100%
streamlit/web/bootstrap.py 138 18 87%
streamlit/web/cache_storage_manager_config.py 5 0 100%
streamlit/web/cli.py 185 17 91%
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 115 31 73%
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 90 7 92%
streamlit/web/server/server.py 186 11 94%
streamlit/web/server/server_util.py 67 5 93%
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 18786 1299 93%

📊 View detailed coverage comparison

@mayagbarnes mayagbarnes changed the title [WIP] Custom Dark Theme - add light/dark section configurations for theme & theme.sidebar Custom Dark Theme - add light/dark section configurations for theme & theme.sidebar Oct 3, 2025
@mayagbarnes mayagbarnes marked this pull request as ready for review October 3, 2025 16:23
@mayagbarnes mayagbarnes requested a review from a team as a code owner October 3, 2025 16:23
@mayagbarnes mayagbarnes requested a review from Copilot October 3, 2025 20:09
Copy link
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 implements the first part of a custom dark theme feature by adding new light/dark theme configuration sections. It establishes the foundational structure for theme sections that can be customized separately for light and dark modes, as well as for the sidebar component in both modes.

Key changes:

  • Added new theme configuration sections: [theme.light], [theme.dark], [theme.sidebar.light], and [theme.sidebar.dark]
  • Implemented validation to prevent invalid theme section nesting patterns
  • Updated protobuf definitions to support the new theme sections

Reviewed Changes

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

Show a summary per file
File Description
proto/streamlit/proto/NewSession.proto Added light and dark theme fields to CustomThemeConfig message
lib/streamlit/errors.py Added StreamlitInvalidThemeSectionError for theme validation
lib/streamlit/config.py Extended CustomThemeCategories enum and updated theme option creation to support new sections
lib/streamlit/config_util.py Added theme nesting validation logic and deep merge functionality for nested configurations
lib/tests/streamlit/config_test.py Added comprehensive tests for new theme sections and validation
lib/tests/streamlit/config_util_test.py Added tests for nested theme configuration handling
lib/tests/streamlit/proto_compatibility_test.py Updated protobuf compatibility tests for new theme fields

Copy link
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.

Overall, LGTM 👍 Just one question about the category order to double check with @jrieke

Comment on lines 103 to 104
SIDEBAR_LIGHT = "sidebar.light"
SIDEBAR_DARK = "sidebar.dark"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just double-checking: are we sure we want to have theme.sidebar.<light/dark> instead of theme.<light/dark>.sidebar? I think both notation make sense, but theme.<light/dark>.sidebar feels slightly more semantically correct since I think the sidebar theme is a sub-aspect part of the overall dark & light theme. cc @jrieke

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fair point, I leaned toward theme.sidebar.<light/dark> myself, since I thought about [theme] and [theme.sidebar] as the main sections, with their respective mode modifiers light/dark but happy to change if you guys prefer the alternative 👍🏼

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah I think theme.dark.sidebar is more intuitive (I would consider dark and light as separate themes, and main area and sidebar are just two areas you can style differently within these themes) and it's also how it's in the product spec, so I'd go with that.

Comment on lines 2091 to 2096
"theme",
CustomThemeCategories.SIDEBAR,
CustomThemeCategories.LIGHT,
CustomThemeCategories.DARK,
CustomThemeCategories.SIDEBAR_LIGHT,
CustomThemeCategories.SIDEBAR_DARK,
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: we could put this into an ALL_THEME_CATEGORIES constant or similar...

Copy link
Contributor

@sfc-gh-mbarnes sfc-gh-mbarnes left a comment

Choose a reason for hiding this comment

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

Echo approval above

@mayagbarnes mayagbarnes merged commit 71d8364 into develop Oct 7, 2025
37 checks passed
@mayagbarnes mayagbarnes deleted the custom-dark-theme-1 branch October 7, 2025 02:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants