I've been trying to make a custom HTML Element by extending the HTMLElement
class. I try adding some style to it by linking a CSS file that is in the same directory as my other two files - index.html
and custom.css
.
Main folder
- index.html
- custom.css
- custom.js
index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="nofollow" type="text/css" href=''>
</head>
<body>
<script src="./custom.js"></script>
<smooth-button text="Smooth button" no-1 = 1 no-2 = 2></smooth-button>
</body>
</html>
custom.css
:
smooth-button{
display: block;
color: blue;
background-color: orange;
}
custom.js
:
class SmoothButton extends HTMLElement{
constructor(){
super();
this.shadow = this.attachShadow({mode: "open"})
}
connectedCallback(){
this.render();
}
render(){
this.SumOfNo1AndNo2 = null;
if(this.getAttribute("no-1")!=null && this.getAttribute("no-2")!=null){
this.SumOfNo1AndNo2 = parseInt(this.getAttribute("no-1"))
parseInt(this.getAttribute("no-2"));
}
else{
console.log("Invalid attribute.")
}
this.shadow.innerHTML = `<button>` this.getAttribute("text") " " this.SumOfNo1AndNo2
"</button>"
}
}
customElements.define("smooth-button", SmoothButton);
With this, I get a button as expected, with the text, but the style is applied to the element as a whole and not to the elements it's made of. How can I apply the styles separately to each of its elements (just a <button>
for now) with an external CSS file? I'm using external CSS because it's somehow better as I read it here.
CodePudding user response:
With this, I get a button as expected, with the text, but the style is applied to the element as a whole and not to the elements it's made of.
This is actually how the custom element is supposed to work. You can't apply styles to the shadow DOM from the outer document. If you could, you'd have a high likelihood of breaking the custom element styling through external modification.
All is not lost however! The reason the button is a different color from its background is due to the user agent stylesheet. You can actually set some CSS to tell the background to inherit the parent background color. Try adding this to your custom element:
const style = document.createElement('style');
style.textContent = `
button {
background: inherit;
}
`;
this.shadow.append(style);
JSFiddle: https://jsfiddle.net/5t2m3bku/
(Also note that it's not really a great idea to interpolate/concatenate text directly into HTML. That text gets interpreted as HTML, which can lead to invalid HTML if reserved characters are used, and even potential XSS vulnerabilities. You might modify that line where you set innerHTML
to set the text, or switch to a template engine.)
CodePudding user response:
In addition to the answers from Brad and Emiel,
- (Brad) Bluntly add a
<style>
element inside shadowDOM- Do read about adopted StylesSheets (Chromium only)
- (Emiel) use cascading CSS properties
- There are more options to style shadowDOM:
Learn about Inheritable Styles
use shadow parts
<style>
::part(smoothButton){
display: block;
color: blue;
background-color: orange;
}
</style>
<smooth-button></smooth-button>
<smooth-button></smooth-button>
<script>
customElements.define("smooth-button", class extends HTMLElement {
constructor(){
super()
.attachShadow({mode:"open"})
.innerHTML = `<button part="smoothButton">LABEL</button>`;
}
});
</script>
- https://developer.mozilla.org/en-US/docs/Web/CSS/::part
- https://meowni.ca/posts/part-theme-explainer/
- https://css-tricks.com/styling-in-the-shadow-dom-with-css-shadow-parts/
- https://dev.to/webpadawan/css-shadow-parts-are-coming-mi5
- https://caniuse.com/mdn-html_global_attributes_exportparts
But...
The first question you should ask yourself:
Do I really need shadowDOM?
If you don't want its encapsulating behavior, then do not use shadowDOM
<style>
.smoothButton{
display: block;
color: blue;
background-color: orange;
}
</style>
<smooth-button></smooth-button>
<smooth-button></smooth-button>
<script>
customElements.define("smooth-button", class extends HTMLElement {
connectedCallback(){
this.innerHTML = `<button >LABEL</button>`;
}
});
</script>
shadowDOM <slot>
Another alternative is to use shadowDOM <slot>
elements, because they are styled by its container element
<style>
.smoothButton{
display: block;
color: blue;
background-color: orange;
}
</style>
<smooth-button><button >LABEL</button></smooth-button>
<smooth-button><button >LABEL</button></smooth-button>
<script>
customElements.define("smooth-button", class extends HTMLElement {
constructor(){
super()
.attachShadow({mode:"open"})
.innerHTML = `<slot></slot>`;
}
});
</script>
When you go down the <slot>
rabbithole, be sure to read the (very long) post:
::slotted CSS selector for nested children in shadowDOM slot
CodePudding user response:
Additionally to Brad's answer. One of the ways you can apply styles from the Light DOM to the Shadow DOM is with CSS Variables.
smooth-button{
display: block;
--button-color: blue;
--button-background-color: orange;
}
render() {
this.shadow.innerHTML = `
<style>
button {
color: var(--button-color);
background-color: var(--button-background-color);
}
</style>
<button>
${this.getAttribute("text")} ${this.SumOfNo1AndNo2}
</button>
`;
)