-
Notifications
You must be signed in to change notification settings - Fork 8.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement system
option for theme:darkMode
uiSetting
#173044
Implement system
option for theme:darkMode
uiSetting
#173044
Conversation
0b13551
to
f65a833
Compare
/ci |
4efde7f
to
87fb6db
Compare
/ci |
/ci |
/ci |
/ci |
/ci |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes in user_settings_service.ts LGTM.
darkMode, | ||
}: { | ||
buildNum: number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional nit: buildNum
is unused.
Blocked by #173529 |
@pgayvallet now that this is resolved, are there any other blockers for this PR? |
No, I just couldn't find the time to get back to it and resolve the conflicts, but it's on my TODO and will hopefully be done very soon! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good to me! Thanks for all the comments. I left a minor comment about possibly unstyled content.
|
||
private applyTheme(darkMode: boolean) { | ||
this.stylesheets.forEach((stylesheet) => { | ||
stylesheet.remove(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know we are not doing this on-the-fly yet, but I am a bit concerned this will result in a "flash of unstyled content". It's a tricky one to solve for though. For now this is totally fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I shared the concerns, but in practice this is performed before the current app gets mounted, and during my tests I couldn't reproduce any css blinks. But yeah this is something we need to keep in mind
💛 Build succeeded, but was flaky
Failed CI StepsTest Failures
Metrics [docs]Module Count
Public APIs missing comments
Async chunks
Public APIs missing exports
Page load bundle
Unknown metric groupsAPI count
ESLint disabled in files
References to deprecated APIs
Total ESLint disabled count
Unreferenced deprecated APIs
History
To update your PR or re-run it, just comment with: |
) ## Summary Fix elastic#89340 Implements a third option, `system`, for the `theme:darkMode` uiSettings, which will follow the system's theme preference (light/dark) when Kibana loads. https://github.com/elastic/kibana/assets/1532934/82078697-8bf5-41df-add1-4ecfed6e1dea **Note: system theme refresh still requires the user to reload Kibana - please see the next section for the reasons if you're interested** ## How theming works in Kibana, again? This is an excellent question, thanks for asking. And the answer is, "well, it's complicated". We have multiples sources of "themed" styles in Kibana, ordered from "best" to "worse": #### 1. the EUI/JSS Theming It was initially implemented in elastic#117368. All react applications and react mountpoints are supposed to be wrapped by a `KibanaThemeProvider` that bridges core's `theme$` values to the `EuiProvider`. https://github.com/elastic/kibana/blob/477505a2dd86d8f103f3aef16b3b4b76dff0b27a/packages/core/theme/core-theme-browser-internal/src/core_theme_provider.tsx#L11 This one was already dynamic and just works perfectly. If `core.theme.theme$` changes, the new values is received by the theme provider, which automatically changes the styles accordingly, creating this sexy "it just works" effect: https://github.com/elastic/kibana/assets/1532934/f3e61ca7-f3ed-4c37-aa46-76ee68c1a628 If everything theme-related was using this approach, dynamic theme reload would have been possible. However, Kibana has a lot of legacy, so as you can imagine, it wasn't that easy. So, **don't get false hopes** (as I did when I tried it...) from this video. Dynamic theme swap **could not** be implemented in this PR. And the reasons are just below. #### 2. Per-theme css files https://github.com/elastic/kibana/blob/6443b571642c453a9232a04297fddfb0a918c0dc/packages/core/rendering/core-rendering-server-internal/src/render_utils.ts#L40-L54 We have a bunch of dark/light variations of some css files that are computed in the rendering service, server-side, to be injected into the page template. Of course, this doesn't play well with dynamic theming, given the UI then doesn't know which css should be swapped, and which one should be added instead. However, porting the responsibilities of which theme css files to load to the browser was achievable, and done in this PR. core's browser-side `theme` provider now receives the list of theme files for both light and dark theme (via the injected metadata), and inject them in the document (instead of having them pre-injected in the page template by the rendering service server-side). So this one wasn't a blocker for dynamic theme reload. #### 3. Plugin styles This is where the problems start. Plugins can ship their own styles too, by importing them from components or from their entrypoint. E.g this component https://github.com/elastic/kibana/blob/f1dc1e1869566c1d61a08bb67eb3d4ac2f47834e/src/plugins/controls/public/control_group/component/control_group_component.tsx#L9 importing this file: https://github.com/elastic/kibana/blob/bafb23580b18781e711575cd8c0e17a6e3f4f740/src/plugins/controls/public/control_group/control_group.scss#L107-L110 Which relies on a theme variable, `$euiFormBackgroundColor` So how does it works under the hood? How is that `$euiFormBackgroundColor` variable interpolated? Using which theme? Well, technically, how the styles are effectively loaded differs slightly between dev and production (different webpack loaders/config), but in both cases, it depends on the `__kbnThemeTag__` variable that is injected to the global scope by the `bootstrap.js` script. This `__kbnThemeTag__` tag (apparently) **can** be modified after page load. However it doesn't magically reload everything, so styles already loaded for one theme will not reload. If a component and its imported styles were already compiled / injected, then they won't reload As a short video is better than 3 blocks of text, just see: https://github.com/elastic/kibana/assets/1532934/3087ffd6-80d8-42bf-ab17-691ec408ea6f That was the first blocker for supporting "dynamic" reloads of the system theme. #### 4. Static inline styles Last but not least, we have some static style injected in the template, that also depend on the current theme. https://github.com/elastic/kibana/blob/6443b571642c453a9232a04297fddfb0a918c0dc/packages/core/rendering/core-rendering-server-internal/src/views/styles.tsx#L52-L54 Of course this plays very badly with dynamic theming. And worth noting, those styles are used by the "Loading Elastic" screen, where Core (and therefore Core's theming service) isn't loaded yet, which made the things even more complicated. This was the second blocker for supporting "dynamic" reloads of the system theme. #### 5. `euiThemeVars` Actually TIL (not that I was asking for it) We're exposing the EUI theme variable via the `euiThemeVars` of the `@kbn/ui-theme` package: E.g. https://github.com/elastic/kibana/blob/c7e785383a87f7e18509c601a5c089c755ac028e/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx#L41 https://github.com/elastic/kibana/blob/c7e785383a87f7e18509c601a5c089c755ac028e/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx#L50 So I did my best, and made it that this export was a proxy, and that Core's theme service was dynamically swapping the target of the proxy depending on the system's theme... https://github.com/elastic/kibana/blob/b0a0017811d7402f76709af7ab1b8e12334e64a5/packages/kbn-ui-theme/src/theme.ts#L30-L42 Unfortunately it doesn't fully work for dynamic system theme switch, given modules/code can keep references of the property directly (e.g. the snippet a few lines on top), and there's nothing to dynamically reload components when the proxy changes. So yet another blocker for dynamic theme switch. ## Release Notes Add a new option, `system`, to the `theme:darkMode` Kibana advanced setting, that can be used to have Kibana's theme follow the system's (light or dark) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
@sebelga @pgayvallet - there's an incongruity between "system" and the toggle on the user menu only offering Dark/Light mode settings. Should we have an issue to reconcile this? If I set "system" in advanced settings, what should the behavior of the toggle on User Menu be? Should the option disappear from the menu? |
I think the user menu options should match the advanced settings and offer the third option "Sync with system". |
@petrklapka @sebelga this is being tracked by #173895 |
Thanks both. @JasonStoltz looks like this is already covered by the Core and Security folks. |
Summary
Fix #89340
Implements a third option,
system
, for thetheme:darkMode
uiSettings, which will follow the system's theme preference (light/dark) when Kibana loads.Screen.Recording.2023-12-12.at.16.45.15.mov
Note: system theme refresh still requires the user to reload Kibana - please see the next section for the reasons if you're interested
How theming works in Kibana, again?
This is an excellent question, thanks for asking. And the answer is, "well, it's complicated".
We have multiples sources of "themed" styles in Kibana, ordered from "best" to "worse":
1. the EUI/JSS Theming
It was initially implemented in #117368. All react applications and react mountpoints are supposed to be wrapped by a
KibanaThemeProvider
that bridges core'stheme$
values to theEuiProvider
.kibana/packages/core/theme/core-theme-browser-internal/src/core_theme_provider.tsx
Line 11 in 477505a
This one was already dynamic and just works perfectly. If
core.theme.theme$
changes, the new values is received by the theme provider, which automatically changes the styles accordingly, creating this sexy "it just works" effect:Screen.Recording.2023-12-11.at.13.25.59.mov
If everything theme-related was using this approach, dynamic theme reload would have been possible. However, Kibana has a lot of legacy, so as you can imagine, it wasn't that easy.
So, don't get false hopes (as I did when I tried it...) from this video. Dynamic theme swap could not be implemented in this PR. And the reasons are just below.
2. Per-theme css files
kibana/packages/core/rendering/core-rendering-server-internal/src/render_utils.ts
Lines 40 to 54 in 6443b57
We have a bunch of dark/light variations of some css files that are computed in the rendering service, server-side, to be injected into the page template.
Of course, this doesn't play well with dynamic theming, given the UI then doesn't know which css should be swapped, and which one should be added instead.
However, porting the responsibilities of which theme css files to load to the browser was achievable, and done in this PR. core's browser-side
theme
provider now receives the list of theme files for both light and dark theme (via the injected metadata), and inject them in the document (instead of having them pre-injected in the page template by the rendering service server-side).So this one wasn't a blocker for dynamic theme reload.
3. Plugin styles
This is where the problems start.
Plugins can ship their own styles too, by importing them from components or from their entrypoint.
E.g this component
kibana/src/plugins/controls/public/control_group/component/control_group_component.tsx
Line 9 in f1dc1e1
importing this file:
kibana/src/plugins/controls/public/control_group/control_group.scss
Lines 107 to 110 in bafb235
Which relies on a theme variable,
$euiFormBackgroundColor
So how does it works under the hood? How is that
$euiFormBackgroundColor
variable interpolated? Using which theme?Well, technically, how the styles are effectively loaded differs slightly between dev and production (different webpack loaders/config), but in both cases, it depends on the
__kbnThemeTag__
variable that is injected to the global scope by thebootstrap.js
script.This
__kbnThemeTag__
tag (apparently) can be modified after page load. However it doesn't magically reload everything, so styles already loaded for one theme will not reload. If a component and its imported styles were already compiled / injected, then they won't reloadAs a short video is better than 3 blocks of text, just see:
Screen.Recording.2023-12-11.at.20.34.52.mov
That was the first blocker for supporting "dynamic" reloads of the system theme.
4. Static inline styles
Last but not least, we have some static style injected in the template, that also depend on the current theme.
kibana/packages/core/rendering/core-rendering-server-internal/src/views/styles.tsx
Lines 52 to 54 in 6443b57
Of course this plays very badly with dynamic theming. And worth noting, those styles are used by the "Loading Elastic" screen, where Core (and therefore Core's theming service) isn't loaded yet, which made the things even more complicated.
This was the second blocker for supporting "dynamic" reloads of the system theme.
5.
euiThemeVars
Actually TIL (not that I was asking for it)
We're exposing the EUI theme variable via the
euiThemeVars
of the@kbn/ui-theme
package:E.g.
kibana/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx
Line 41 in c7e7853
kibana/src/plugins/chart_expressions/expression_metric/public/components/metric_vis.tsx
Line 50 in c7e7853
So I did my best, and made it that this export was a proxy, and that Core's theme service was dynamically swapping the target of the proxy depending on the system's theme...
kibana/packages/kbn-ui-theme/src/theme.ts
Lines 30 to 42 in b0a0017
Unfortunately it doesn't fully work for dynamic system theme switch, given modules/code can keep references of the property directly (e.g. the snippet a few lines on top), and there's nothing to dynamically reload components when the proxy changes.
So yet another blocker for dynamic theme switch.
Release Notes
Add a new option,
system
, to thetheme:darkMode
Kibana advanced setting, that can be used to have Kibana's theme follow the system's (light or dark)