Create Own Component with .css StyleSheet

Hi There I am new to vaadin. I like to have an own component created for whichI like to import its own “local” css stylesheet. I tried to follow the guidelines ([here]
(https://vaadin.com/docs/themes/styling-components.html) and [here]
(https://vaadin.com/docs/v14/themes/importing-style-sheets.html)) but it is not working. What I am doing wrong? I am using Vaadin v17.0.9. Thank you in advance.

Issues and Questions

  • The @CssImport for my component seems to be ignored. Is there a way to set the text color to “red”?
  • Is there a way to set a default .css stylesheet file for my component which can be overriden?

Java Classes

@Route("/")
@Theme(value = Lumo.class)
@CssImport(value = "./styles/hello-component.css", themeFor = "hello")
public class MainView extends VerticalLayout {
	
	private static final long serialVersionUID = 1L;
	
	@PostConstruct
	private void init() {
		setSizeFull();
		add(new HelloComponent());
	}
}

@Tag("hello")
public class HelloComponent extends Div {

	private static final long serialVersionUID = 1L;
	
	public HelloComponent() {
		init();
	}
	
	private void init() {
		setSizeFull();
		setText("Test ");
	}
}

Stylesheet “frontend/styles/hello-component.css”

:host  {
	color: red;
}

You cannot apply a Local Style Scope to your custom component, HelloComponent, because it doesn’t even have a Shadow DOM. Local Style Scopes are relevant for regular Vaadin components (such as a TextField or a Button) because these components have a Shadow DOM. In your case, HelloComponent has to use the global style scope using regular CSS selectors. You can do this in Java code using the component’s getStyle() method:

HelloComponent helloComponent = new HelloComponent();
helloComponent.getStyle().set("color", "red");

Or, alternatively, you can import a global style sheet using the @CssImport annotation, then in this style sheet, target your HelloComponent using the regular CSS selectors. For example, you can add @CssImport("./shared-styles.css") to one of your classes, then in shared-styles.css place the following style:

hello {
    color: red;
}

If you want to create a custom element with its own shadow DOM, you’ll need to create a client-side component for that using Polymer or Lit-element. But even then, the @CssImport with themeFor = "MyCustomElement" will not work automatically. For that to work, your client-side custom element would also have to implement ThemableMixin(https://github.com/vaadin/vaadin-themable-mixin). The latter is the necessary ingredient that allows any component (including Vaadin own regular components) to have its shadow DOM stylable using the @CssImport.

(EDIT: Tarek has been faster than me :). So I’m removing the beginning of my answer.)

The themeFor is specific to web component that implements the javascript ThemableMixin. With that Mixin you can inject css inside a webcomponent and change the style of the dom inside a shadow root. I think there is no detailed explanation in the official documentation.

You can’t do it only with a Java class. You can create your own Polymer component. For example:

package org.vaadin.jeanchristophe.component;

import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.html.Div;

@Tag("hello-component")
@JsModule("./src/hello-component.js")
public class HelloComponent extends Div {

	private static final long serialVersionUID = 1L;
	
	public HelloComponent() {
		init();
	}
	
	private void init() {
		setSizeFull();
		getElement().setAttribute("title", "my title");
	}
}
import {html, PolymerElement} from '@polymer/polymer/polymer-element.js';
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin';
import { ElementMixin } from '@vaadin/vaadin-element-mixin';

class HelloComponent extends ElementMixin(ThemableMixin(PolymerElement)) {
    static get is() {
        return 'hello-component';
    }

    static get properties() {
        return {
            title: String
        }
    }
    static get template() {
        return html`
        <style>
        </style>
        <div>
            [[title]
]
            <span>test</span>
        </div>`;
    }

}

customElements.define(HelloComponent.is, HelloComponent);

And the css:

div {
    color:red;
}

span {
    color:green;
}

Then you can use this annotation: @CssImport(value = "./styles/hello-component.css", themeFor = "hello-component")

Remember to always use a hyphen for the name of a webcomponent. The tag hello is not allowed, hello-component is working.

An easier way of accomplishing this would be to include the CSS globally and use plain CSS classes. You can even skip defining the tag name if you don’t need it.

@CssImport(value = "./styles/hello-component.css")
public class HelloComponent extends Div {

	private static final long serialVersionUID = 1L;
	
	public HelloComponent() {
		addClassName("hello-component");
		init();
	}
	
	private void init() {
		setSizeFull();
		add(new Paragraph("my title"));
	}
}

CSS:

.hello-component p {
    color: red;
}

Thank you both. This helped me.

One question, If I use the approach with the JS Module, is there w ay to add subelements at server side and let it synchronize with the shadow DOM? Or is this when I use the js module not supported?

@Tag("hello-component")
@JsModule("./components/hello-component.js")
@CssImport(value = "./styles/hello-component.css", themeFor = "hello-component")
public class HelloComponent extends Div {
	
	private static final long serialVersionUID = 1L;

	public HelloComponent() {
		setId("hello");
		// adding further sub elements
		setText("Text 1");
		add(new Span("Text 2"));
		...
	}
}

As Marcus and Tarek said: if you don’t need to encapsulate your css/component, you can avoid shadow dom. (In my opinion, you should avoid it. It will bring more problems than solutions) But for testing/understanding, it’s a good exercise :).

If you do this: setText("Text 1");
It adds a text node inside your dom node <hello-component>Text 1</hello-component>.
If you do this: add(new Span("Text 2"));
It adds a new child: <hello-component>Text 1<span>Text 2</span></hello-component>

So it doesn’t add anything to the shadow dom, basically the shadow dom is private to the web component and should be managed by the web component (on the client side).
You can use slot if you want to add children to a web component.

I don’t recommend to use this code, but it can help you to understand :):


import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Span;

@Tag("hello-component")
@JsModule("./src/hello-component.js")
public class HelloComponent extends Div {

	private static final long serialVersionUID = 1L;
	
	public HelloComponent() {
		init();
	}
	
	private void init() {
		setSizeFull();
		getElement().setAttribute("title", "my title"); // update the title in the shadow dom
		setText("Text 1"); // added in the light dom
		add(new Span("Text 2")); // added in the light dom
	}
}
import {html, PolymerElement} from '@polymer/polymer/polymer-element.js';
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin';
import { ElementMixin } from '@vaadin/vaadin-element-mixin';

class HelloComponent extends ElementMixin(ThemableMixin(PolymerElement)) {
    static get is() {
        return 'hello-component';
    }

    static get properties() {
        return {
            title: String
        }
    }
    static get template() {
        return html`
        <style>
        </style>
        <div>
            [[title]
]
            <span>test</span>
            <slot></slot>
        </div>`;
    }

}

customElements.define(HelloComponent.is, HelloComponent);

Thank you very much, this helped a lot.