Creating a Custom Theme

A custom theme is the easiest way to provide a custom look and feel for your entire application. It can be packaged as a dependency for reuse in multiple applications, as described in Packaging a Theme for Reuse. The CSS in a custom theme is always applied on top of the default Lumo theme.

Note
Custom theme feature is only for NPM mode and will not be applied for compatibility mode.

Theme Folder Structure

For use in a single application, a custom theme is implemented as a folder inside frontend/themes, with the following minimal structure:

filesystem

  1. frontend
  2. └── themes (1)
  3. └── my-theme (2)
  4. ├── components/ (3)
  5. └── styles.css (4)
  1. The themes folder can contain multiple custom themes (but only one can be applied to the application at a time).

  2. Each theme is in its own sub-folder. The name of this folder is provided as a parameter to the @Theme annotation to apply the theme to the application.

  3. The components sub-folder is for component style sheets that target the (local CSS) internals of Vaadin components.

  4. styles.css is the theme’s master style sheet that is automatically loaded when the theme is applied.

See Style Scopes for details on global vs local CSS.

Applying a Custom Theme

You can apply a different theme using the @Theme annotation on the root navigation class or the router layout defined in the @Route annotation.

An application may only have one such class and you need to define any similar configuration annotations in the same class, such as @PWA or PageTitle.

For example, in a Spring Boot application, you could have an application configuration class such as the following:

Java

  1. @Route("")
  2. @Theme(themeFolder = "my-theme")
  3. public class LumoApplication extends Div {
  4. ...
  5. }

Changes to the theme folder’s contents are automatically picked up during development. If the application has live reload enabled, the UI should reload automatically. Otherwise a page reload may be needed to see the changes.

Sometimes, compilation throws an error (“no such file or directory”) when files or folders are removed from the theme folder while the application is running. This might happen due to the following reasons:

  1. The file being removed is a style sheet referenced from my-theme/styles.css, or from other style sheet in the theme folder. In that case, to avoid re-compilation errors, remove the style sheet import first, and then remove the file.

  2. The file being removed is a component style sheet in the my-theme/components folder, or the whole my-theme/components folder. The files inside the components folder are referenced from an autogenerated file, such as my-theme.generated.js, and re-compilation fails because webpack cannot find them. To avoid errors, remove your component style sheet simultaneously (in one go) with the theme-generated file.

Note
Dismissing the error message
When you encounter a “no such file or directory” error reported by webpack, shown in an overlay in your application, click on the overlay (or refresh the browser page), and it should disappear. You can then continue working on your application and theming. If it doesn’t help, you need to restart the application.

Master Style Sheet

When a custom theme is applied to the application, the master style sheet styles.css is loaded automatically as global CSS. Other style sheets, except for Vaadin component styles in the components folder, need to be included through the master style sheet using CSS @import statements.

The master style sheet typically contains:

  1. Imports of other global style sheets within the theme folder.

  2. Overrides of default Lumo properties and declarations of custom CSS properties.

  3. Styles applied to UI elements through class names and other CSS selectors.

styles.css

  1. @import other-styles.css (1)
  2. html, :host { (2)
  3. --lumo-border-radius: 0.5em;
  4. --my-brand-color: purple;
  5. }
  6. .application-header { (3)
  7. background: white;
  8. border-bottom: 1px solid gray;
  9. }

CSS custom properties (for example, for overriding Lumo defaults) are recommended to use the selector “html,:host“, as in the example above, to ensure that they are applied when the theme is applied to an embedded Flow application or component, or when used with the Design System Publisher tool.

Note
Restriction on @import statements
At the moment all @import statements need to be in the theme root folder. See Vaadin Flow issue 9794.

Vaadin Component Styles

As the internal styling of Vaadin components is isolated from the global CSS inside the component’s shadow DOM, the easiest way to customize their styles is through the CSS properties available in the built-in themes (see Foundation section for details), and by injecting custom CSS directly into the shadow DOM of the components by placing them in the components sub-folder.

To inject CSS into the shadow DOM of a Vaadin component, create a style sheet whose name matches the web component HTML element name of the component in the components sub-folder. As an example, to apply styling to the vaadin-button component, create a style sheet called vaadin-button.css.

filesystem

  1. frontend
  2. └── themes
  3. └── my-theme
  4. ├── components
  5. ├── vaadin-button.css
  6. └── vaadin-text-field.css
  7. └── styles.css

See Styling Components for details on writing CSS for Vaadin components.

This style injection is based on a feature used by Vaadin components called Themable Mixin. You can create your own web components that use the same feature to make them support this theming mechanism.

Components that do not use shadow DOM, such as custom Flow-based components, can be styled using regular global CSS placed in styles.css or any other style sheet imported through it.

Other Theme Assets

In addition to style sheets, themes often need other assets like fonts, images, and icons. These can be included in the theme folder, either in the root or in sub-folders as desired.

In the following example, a couple of images are included in an img sub-folder (1), and a font file is included in the theme root (2).

filesystem

  1. frontend
  2. └── themes
  3. └── my-theme
  4. ├── components/
  5. ├── img (1)
  6. ├── logo.png
  7. └── background.jpg
  8. ├── my-font.woff (2)
  9. └── styles.css

These assets can be used in the theme’s style sheets through URIs relative to the style sheet’s location:

styles.css

  1. @font-face {
  2. font-family: "My Font";
  3. src: url('./my-font.woff') format("woff");
  4. }
  5. .application-logo {
  6. background-image: url('./img/logo.png');
  7. }

Document Root Style Sheet

To ensure that certain styles are always applied to the document root rather than the shadow root of an embedded Flow application or component, they can be placed in a special style sheet in the theme folder root called document.css. This is mainly needed for @font-face declarations that are not supported inside web component shadow DOM, and only when the theme is going to be used with embedded Flow applications or components inside another application or used with Design System Publisher.

Another example is when the theme should be applied to an embedded Flow application or component which is shown in an overlay inside another application. Since the overlay cannot access the styles from web component shadow DOM, the styles must also be added to the document.css. To avoid copy-pasting such styles in two places, move them into a separate style sheet and use @import to include them in both styles.css and document.css.

Style Loading Order

When using a custom theme, CSS is loaded in a Vaadin application in the following order:

  1. Lumo styles

  2. Custom theme styles

  3. Manually loaded additional style sheets (for example, using @CssImport in Flow)

Limitations

The following limitations apply to custom themes:

  • The theme cannot be switched run-time.

  • Using the built-in Material theme is not currently supported. Custom themes are always loaded on top of the Lumo theme.

  • At the moment all @import statements need to be in style sheets in the theme root folder.

See Vaadin Flow issue 9794.