)
## 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>
Summary
Fix #89340
Implements a third option,
system, for thetheme:darkModeuiSettings, 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
KibanaThemeProviderthat 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
themeprovider 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,
$euiFormBackgroundColorSo how does it works under the hood? How is that
$euiFormBackgroundColorvariable 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.jsscript.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.
euiThemeVarsActually TIL (not that I was asking for it)
We're exposing the EUI theme variable via the
euiThemeVarsof the@kbn/ui-themepackage: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:darkModeKibana advanced setting, that can be used to have Kibana's theme follow the system's (light or dark)