Dynamic theming in Vaadin Flow
Whether for multi-tenancy or user preference, the need to dynamically change the theming of an application is a common business application need. In this post, we show how it’s done using CSS custom properties and HTML attributes.
Vaadin 14.6 brought an improved theming mechanism which makes it easy to package and reuse themes. While switching between packaged themes at runtime is not supported, a single theme can support multiple looks and feels with just a few lines of CSS. This approach also works well without the new theming mechanism.
The application used in this project was generated through start.vaadin.com. It consists of a menu, a header, and a master-detail view for editing personal information.
The distinguishing factor
To limit the scope of our style rules to a specific theme variant, we can set a class or attribute on the HTML body
element. A good candidate is the theme
attribute, which is also used for setting the Lumo theme variants.
Once the attribute is set, global style rules are easily limited to one or more theme variants.
By using the
~=
operator, the selector will match even when multiple theme variants are defined. For example,[theme~="dark"]
will match<body theme="plain dark">
. If this is not needed, just using=
is slightly more performant.
In Java, we can dynamically set the attribute at any time through the UI
class. Depending on your use case, you might fetch the theme variant from the session, the database, or even the URL.
UI.getCurrent().getElement().setAttribute("theme", "my-theme-variant");
For convenience, I have created an enum
to describe the available variants. The caption can be used to display the theme variant name to a user.
A utility class comes in handy to change the theme at runtime. By also storing it in the session, it is persisted across tabs and page reloads.
To set the initial value of the theme
attribute when the page is first loaded, we can utilize an IndexHtmlResponseListener
.
Creating a theme variant selector
A theme variant selector can be created in many ways. The code below shows one approach, using the Tabs
component. Using the Vaadin ComponentUtil
utility class, we associate a tab with a ThemeVariant
.
Efficient styling with CSS custom properties
Vaadin’s Lumo theme uses CSS custom properties to define properties that most styles are derived from. This allows us to make major changes to the look and feel of our application by only modifying some of these properties.
It is a good idea to define your own CSS custom properties for colors and other values that you will reuse.
The application being styled uses Lumo’s dark theme variant on the navbar
element, so an additional attribute selector is needed to override those styles.
As so many styles are derived from these properties, these few changes have a big effect on the look of the application.
We can also add normal style rules under the theme
attribute scope. The CSS custom properties can easily be set just for a specific component, so as not to affect other elements on the page.
Theme-attribute-based styles inside a shadow root is a trickier topic. While the host-context
pseudo-class allows for styling a shadow root based on the attribute of a parent element, the class has not been, and will not be, implemented in Safari.
So, the remaining approach is to define the style as a CSS custom property. The default value can be defined under the :root
pseudo-class, which refers to the html
element, and the value can be overridden for a theme variant as desired.
Here I define the default value for the date picker overlay background, and I override it for my clean theme variant.
I then use my property in my component-specific style sheet.
If the lack of support for Internet Explorer 11 is not a concern, elements inside a shadow root that have the part
attribute set can also be styled from the global style sheet using the ::part
pseudo-element. This does not work in nested shadow roots.
Listening to theme variant changes
Some theme-specific styles may need to be applied programmatically. One example is changing out an image, which can’t be done in CSS except by using the background-image
property.
We can use the ComponentUtil#addListener
method to add listeners. For this, we need a custom event class.
The Registration
returned from addThemeChangeListener
can be used to remove the listener. Use ComponentUtil#fireEvent
to notify the listeners when the theme variant has changed.
We can use this in the MainView.java
class to set a different logo for the Carrot Inc theme variant. We set the initial value based on the initial theme variant, and update the value whenever it changes.
That’s it, the application now supports multiple theme variants!
The complete code can be found on GitHub.