Documentation versions (currently viewingVaadin 23)

You are viewing documentation for Vaadin 23. View latest documentation

Creating a Custom Theme

Instructions for creating a custom theme for your Vaadin application.

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.

Designer resources

Designers can create pixel-perfect UI designs and prototypes, that are easy for developers to implement, using the popular design tool Figma and the official Vaadin Figma libraries.
Learn more

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:

└── themes              (1)
    └── my-theme        (2)
        ├── components/ (3)
        └── 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’s 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 a class that implements AppShellConfigurator. 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:

public class Application extends SpringBootServletInitializer
                         implements AppShellConfigurator {

You can find more information on the usage of the annotation in the @Theme annotation guide.

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 if 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.

Dismissing the error message
When you encounter a “no such file or directory” error reported by Vite, 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.

@import 'other-styles.css';     /* <1> */

html, :host {                   /* <2> */
  --lumo-border-radius-m: 0.5em;
  --my-brand-color: purple;

.application-header {           /* <3> */
  background: white;
  border-bottom: 1px solid gray;

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.

Restriction on style sheet imports
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.

└── themes
    └── my-theme
        ├── components
        │   ├── vaadin-button.css
        │   └── vaadin-text-field.css
        └── 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 don’t 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).

└── themes
    └── my-theme
        ├── components/
        ├── img                (1)
        │   ├── logo.png
        │   └── background.jpg
        ├── my-font.woff       (2)
        └── styles.css

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

@font-face {
  font-family: "My Font";
  src: url('./my-font.woff') format("woff");

.application-logo {
  background-image: url('./img/logo.png');

Document Root Style Sheet

When Flow UIs are embedded into other web pages, some styles need to be in the root of the parent page instead of the Shadow DOM wrapper in the embedded UI. You can load the styles into the parent page by placing them in a stylesheet called document.css.

This is necessary for the following two:

  1. @font-face declarations as they are not supported in Shadow DOM;

  2. Global CSS that has to work in Vaadin component overlays such as Dialogs;

To avoid copy-pasting styles into two places, move them to 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. Application-specific bundled CSS added with a @CssImport

  3. Styles added on Java side via Page::addStylesheet()

  4. Application-specific unbundled CSS added with @StyleSheet

  5. Parent styles

  6. Custom theme styles

Theme Resolving Order

The following logic is used to determine which theme is used:

  1. If the @Theme annotation is found on the application shell class, the theme set in the annotation is used

  2. If the @NoTheme annotation is found on the application shell class, no theme style sheets are loaded

  3. If the com.vaadin.flow.theme.lumo.Lumo class is available in the classpath, the Lumo theme is used

Resolving stops when a match is found. No theme is used if none of the conditions are met.


The following limitations apply to custom themes:

  • The theme can’t be switched run-time.

  • Using the built-in Material theme isn’t 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.