Themes and Theming Applications

Themes define the look and feel of the Vaadin components. The built-in themes have been the base for the application specific theme. Vaadin 7 introduced the themes in Sass format and the parameterized Valo theme, which made it possible to customize the UI by tweaking the parameters.

New CSS Based Themes, - No Sass

Since introducing Sass as a helper to Vaadin Application theming, browsers have started to support CSS Custom Properties (IE11 is polyfilled for production mode.), which brings the customizability gains from Sass to basic CSS, without the overhead of needing to compile the Sass to CSS.

Thus, Vaadin platform itself isn’t using Sass, but you can of course use it for your own application theming if you want to. You’ll have to setup the sass-compiler workflow yourself, but there are Maven Plugins available for this.

None of the old themes are available for Vaadin platform. By customizing the new Vaadin platform themes you can easily achieve the same look and feel your application you had before. The DOM is, however, different for the new components, so this is not a copy-paste step.

Theming in Vaadin platform

In Vaadin platform the theming is built inside the Web Components. There is a different variant of the Web Component for each available theme. Themes cannot typically be mixed and matched, and for coherent user experience you should always use the same theme for all of the components.

The following example shows all the tricks for theming applications, covered in the next topics:

@Theme(Lumo.class) // the theme for Vaadin Components. You can omit it for Lumo
@HtmlImport("styles/shared-styles.html") // the application specific styles
@Viewport("width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes")
public class MainLayout extends Div implements RouterLayout,
        AfterNavigationObserver, PageConfigurator {


@Theme Annotation

Flow needs to know which theme it should use for the components in Vaadin platform. Similarly to Vaadin 8 and previously, this is done with a @Theme annotation. This should be applied on the root layout of the application. It can also be in an abstract class if you have multiple root layouts.

When no @Theme is used, the Lumo theme is used by default (if present in the classpath).

Instead of a magic string, you need to provide a Class reference to an theme class that extends AbstractTheme. There is one ready-made theme available for V10: Lumo. Another theme, Material, is also available starting with V12 and it is possible to use it also with Vaadin components. You should always use a theme, since the unthemed versions of the components are only meant as a baseline for creating a new theme from scratch!

The theme class will handle two things:

  • It tells Flow what theme it should use for the Vaadin Components and where the files can be found

  • It specifies a set of shared styles like fonts etc. that will be automatically loaded to the initial page by Flow for the theme.

The Lumo theme can be customized, see documentation for more information.

Application Theming

@HtmlImport("styles/shared-styles.html") imports the application’s style file.

As the @Theme annotation only specifies the theme for the components, you should have a separate style module for the styling that only applies for the application. The recommended default is to have that inside the frontend folder, e.g. src/main/webapp/frontend/shared-styles.html.

One reason why you should always use this location is that this style is automatically imported to templates when using Vaadin Designer to design UIs.

The reason for using a HTML file with <custom-style> element is that you can then theme the Web Components properly by taking advantage of Shady DOM for style encapsulation, custom properties, and custom mixins.

You can also use and import a CSS file with the @StyleSheet annotation. Note, that regular stylesheets are not processed by ShadyCSS, so you should avoid using custom properties or mixins in them if you want to support Internet Explorer 11.

When working with HTML templates in the application, it makes sense to apply the theming that only applies to that template directly in the template and scope it to only affect that.

Specifying the Viewport and <body> element styles

In the previous Vaadin versions, the @Viewport styling was applied to the UI, but now it should be done for the root layout. Otherwise the usage has not changed from V8:

@Viewport("width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes")

Vaadin platform maps the UI directly to the <body> element and gives you more fine grained control easily on what styles it gets with the @BodyStyles annotation. In Vaadin platform the body has only the margin: 0; style applied, whereas in V8 there was the following styles:

overflow: hidden;
margin: 0;
padding: 0;
border: 0;

Are you planning on migrating your application? Get help from Vaadin experts.

Open in a
new tab
export class RenderBanner extends HTMLElement {
  connectedCallback() {

  renderBanner() {
    let bannerWrapper = document.getElementById('tocBanner');

    if (bannerWrapper) {

    let tocEl = document.getElementById('toc');

    // Add an empty ToC div in case page doesn't have one.
    if (!tocEl) {
      const pageTitle = document.querySelector(
        'main > article > header[class^=PageHeader-module--pageHeader]'
      tocEl = document.createElement('div');

      pageTitle?.insertAdjacentElement('afterend', tocEl);

    // Prepare banner container
    bannerWrapper = document.createElement('div'); = 'tocBanner';

    // Banner elements
    const text = document.querySelector('.toc-banner-source-text')?.innerHTML;
    const link = document.querySelector('.toc-banner-source-link')?.textContent;

    const bannerHtml = `<div class='toc-banner'>
          <a href='${link}'>
            <div class="toc-banner--img"></div>
            <div class='toc-banner--content'>${text}</div>

    bannerWrapper.innerHTML = bannerHtml;

    // Add banner image
    const imgSource = document.querySelector('.toc-banner-source .image');
    const imgTarget = bannerWrapper.querySelector('.toc-banner--img');

    if (imgSource && imgTarget) {