How to use global css styles in shadow dom - javascript

Shadow dom encapsulate css styles, selectors don't cross the shadow boundary.
Question: How to use global common css styles in shadow dom?
(suppose there are some common css styles which will be used across all pages (e.g.: font-family, h1, h2, clear, reset ...), how to make it works in shadow dom?)

I've just struggled with the same problem as an original issue, namely: defining once some global rule for, say, <h3> element and benefit from that within any/many ShadowDOMs.
No, css-variables are not suited well for this thing, since even if I've defined once, say, font and color variables for <h3>, I'll still need to go over each and every shadowed stylesheet and add a CSS rule consuming them.
At the point of writing this (yes, we are 2019 now) the shortest standardized solution is indeed importing some global common CSS. Works perfectly in Chrome, Firefox and Anaheim (Edge on Chromium).
It still requires to add an #import rule in each and every component, so still costy (from coding/maintenance POV, the stylesheet fetched only once), but that's the lowest price we can pay now.

Some solutions:
CSS variables:
http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/
https://developers.google.com/web/updates/2016/02/css-variables-why-should-you-care?hl=en
http://blog.chromium.org/2016/02/chrome-49-beta-css-custom-properties.html
:host-context:
http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/
Also, I haven't tested, but someone suggested #import url('/common-style.css');, here: Polymer share styles across elements
Please note, one of the articles listed above was also pointed out by Amid. By the time that article was written, Chrome had no CSS variables. But now it already works natively with the recently launched Google Chrome 49.

Non of the provided links work for me in Chrome 66 (as of 2018) so I ended up with this to customize custom element from outside:
<custom-element tabindex=10>
<style>
:host div {
--color: red;
}
</style>
</custom-element>
class Element extends HTMLElement {
constructor() {
super();
var shadow = this.attachShadow({mode: 'open'});
var user_style = shadow.host.innerHTML.match(/<style>([\s\S]*?)<\/style>/);
var style = document.createElement('style');
style.innerHTML = `
:host div {
color: var(--color, #aaa);
}
` + user_style ? user_style[1] : '';
shadow.appendChild(style);
shadow.host.querySelector('style').remove();
}
}
customElements.define(
"custom-element",
Element
)

It is 2022
ShadowDOM is styled by:
<style> within shadowDOM
Inheritable styles
https://lamplightdev.com/blog/2019/03/26/why-is-my-web-component-inheriting-styles/
(cascading) CSS properties
shadowParts (and Themes)
https://meowni.ca/posts/part-theme-explainer/
<slot> are reflected, they are NOT styled by the shadowDOM, but by its container.
See: ::slotted content
(feb 2022) Constructible StyleSheets is still a Chromium only party
https://caniuse.com/mdn-api_cssstylesheet_cssstylesheet
your custom directive

Define styles in a base element and have all elements that need the style inherit from that base element.
With lit, something like this
export class AppComponentBase extends LitElement {
static styles = css`
:host {
font-family: sans-serif;
font-size: 21px;
}
`;
}
And then in stead of inheriting from LitElement, make all components in your application inherit from AppComponentBase like this:
export class HomeRouteComponent extends AppComponentBase {
render() {
return html`
<h1>Home</h1>
<p>
Welcome to my website
</p>
`;
}
}
You can also add or some styles
export class HomeRouteComponent extends AppComponentBase {
static styles = [super.styles, css`
h1 {
color: red;
}
`]
render() {
return html`
<h1>Home</h1>
<p>
Welcome to my website
</p>
`;
}
}
Having a common component to inherit from may have other advantages. For instance to share some logic, although this might better be achieved via controllers.
This is all lit, but the same concept could be implemented with "bare" customElmements with relative ease.

You do it via ::shadow pseudo-element. Like this:
::shadow .redColor
{
background-color: red;
}
That will apply styling to all elements inside shadow trees with .redColor class.
More info + other styling possibilities in this great article: Shadow DOM 201

Related

:host-context not working as expected in Lit-Element web component

I've got two Lit-element web components - one is units-list, which contains many units-list-item elements. The units-list-item elements have two different display modes: compact and detailed. Because the list element supports infinite scroll (and thus could contain several thousand units), we need any mechanism that toggles between the two modes to be as performant as possible.
That's why I thought an ideal solution would be to use the :host-context() pseudo-selector in the styles for the units-list-item element, as that way every units-list-item element could switch between the two display modes just by changing the class applied to an ancestor (which would be within the shadow DOM of the units-list element).
To elaborate, here's the relevant markup from the units-list element. Note that the "trigger" classes are being applied to the #list-contents div, which is part of the units-list template.
<div id="list-contents" class="${showDetails ? 'detail-view table' : 'compact-view table'}">
${units.map(unit => html`<units-list-item .unit="${unit}"></units-list-item>`)}
</div>
As you can see, the showDetails flag controls whether the "detail-view" or "compact-view" class is applied to the div containing all of the units-list-item elements. Those classes are definitely being applied correctly.
Here's the full render method from the units-list-item element (unnecessary markup removed):
render() {
const {unit} = this;
// the style token below injects the processed stylesheet contents into the template
return html`
${style}
<div class="row compact">
<!-- compact row markup here -->
</div>
<div class="row detail">
<!-- detail row markup here -->
</div>
`;
}
Then I have the following in the units-list-item element's styles (we're using SCSS, so the single-line comments are not a problem):
// This SHOULD hide the compact version of the row when the
// unit list has a "detail" class applied
:host-context(.detail-view) div.row.compact {
display: none !important;
}
// This SHOULD hide the detail version of the row when the
// unit list has a "compact" class applied
:host-context(.compact-view) div.row.detail {
display: none !important;
}
My understanding of the :host-context selector says that this should work, but Chrome just renders both versions of the row every time, and the Chrome dev tools show that the selectors are never matching with either of the rows.
I know there are several alternatives that would work, but this is the only one I'm aware of that would allow the entire list of units to switch modes by changing a single class on a parent element. Every other solution I've considered would require, at the least, updating the class attribute on every units-list-item element in the list. I'd like to avoid that if possible.
Of course, my primary concern is simply to make this work, if possible, but I'm also curious about a couple of things and can't find any info about them. The two questions I can't seem to find an answer for are
When :host-context is used within an element that is itself part of a shadow DOM, does it consider that parent element's shadow DOM to be the "host context", or does it jump "all the way out" to the document DOM?
If it's the former, will :host-context jump multiple shadow DOM boundaries? Say I have a custom page element that contains a custom list element, which itself contains many custom item elements. If that item element has a :host-context rule, will the browser first scan up the shadow DOM of the list element, then, if matching nothing, scan up the shadow DOM of the page element, and if still matching nothing, then scan up the main document DOM to the <html> tag?
There is no support for :host-context in FireFox or Safari
last update from a month ago is both Mozilla and Apple are not going to implement it.
Looks like it is going to be removed from the spec:
https://github.com/w3c/csswg-drafts/issues/1914
One alternative is to use CSS Properties (those trickle down into
shadowDOM)
JSFiddle: https://jsfiddle.net/WebComponents/hpd6yvxt/
using host-context for Chrome and Edge
using CSS properties for other Browsers
Update Feb 2022
Apple quietly changed their mind? now in Safari TP:
https://caniuse.com/?search=host-context
An example of using css porperties, as Danny Engelman says, to get your goal
customElements.define('list-item', class extends HTMLElement {
constructor() {
const style = document.createElement('style');
const divcompact = document.createElement('div');
divcompact.innerHTML = "compact";
divcompact.className = "compact";
const divdetail = document.createElement('div');
divdetail.innerHTML = "detail";
divdetail.className = "detail";
let shadow = super().attachShadow({
mode: 'open'
});
shadow.append(style, divcompact, divdetail);
style.innerHTML = `
.compact {
background-color: red;
display: var(--display-compact, block);
}
.detail {
background-color: green;
display: var(--display-detail, block);
}
`
}
});
.compact-view {
--display-detail: none;
}
.detail-view {
--display-compact: none;
}
.box {
width: 200px;
height: 50px;
border: solid 1px black;
margin: 5px;
}
<div class="box">
no class applied
<list-item>test</list-item>
</div>
<div class="compact-view box">
compact view
<list-item>test</list-item>
</div>
<div class="detail-view box">
detail view
<list-item>test</list-item>
</div>

How default view encapsulation works in Angular

As we know, default view encapsulation for a component in angular application is Emulated,ie
encapsulation: ViewEncapsulation.Emulated
I really do not understand how it works behind the hood if it is not a shadow dom.
There are three types of encapsulation in angular
ViewEncapsulation.Emulated and this is set by default
ViewEncapsulation.None
ViewEncapsulation.Native
Emulated mode
Assume that you have two different components comp-first and comp-second , For example you define in both of them
<p> Some paragraph </p>
So if you apply some styling for paragraph in comp-first.css
p {
color: blue;
}
and then inspect p element on comp-first.html and look for its styling will find something like this
p[_ngcontent-ejo-1] {
color: blue;
}
"_ngcontent-ejo-1" is just a simple key for differentiate such an element from others components elements
None mode
If you apply this mode to such a component for instance comp-first and then you go and inspect any element it will not provide any attribute like "_ngcontent-ejo-1" to any element , So applying any styling or class it will be provided globally .
Native mode
This should give the same result as if you are using emulated mode but it comes with Shadow DOM technology in browsers which support it
When you write
<div class="XXX"></div>
With the style
XXX { color: red; }
The compiler will translate it to
<div class="XXX" ng_host_c22></div>
With the style
XXX[ng_host_c22] { color: red; }
it simply adds an unique (randomly generated) attribute to your elements and style, to avoid them colluding with other styles.
This means if you declare the class XXX in 2 different components, then they will have a different attribute, and not collude.

Can I add a classname to a CSS variable?

Is it possible to add a classname to a CSS variable or is there some other way to set it up so that I don't have to manipulate each individual variable directly via javascript? I'd like to keep all my styles in CSS and simply turn on relevant classes with JS. For example, If something like this was possible in CSS:
:root.white{ --bgcol:#FFF; --col:#000; }
:root.black{ --bgcol:#000; --col:#FFF; }
Then I could then just toggle the .black or .white class from javascript to trigger all vars to change. What's the best approach for this type of setup?
That's frankly the best (as in most idiomatic) approach — the use of class names, if not altogether separate stylesheets (as has been tradition for many, many years), to theme entire layouts via custom properties. It's the most "fundamentally CSS" approach with JavaScript merely being the glue that makes the theme switching work. You really can't do much better than that.
For those unaware what :root means and wondering where exactly the class names are being applied, it's the html element (the parent of body). So there is nothing special going on here — you're simply switching class names on the html element. It just happens that global custom properties are conventionally defined for the document root element since it's at the top level of the inheritance chain.
If you have any theme-agnostic custom properties, as well as style properties (i.e. not custom properties) that apply to the root element, keep them in their own unqualified :root rule, separate from your themed custom properties, so they won't be affected by theme switching. Here's an example:
const root = document.documentElement;
// Default theme - should assign declaratively in markup, not JS
// For a classless default theme, move its custom properties to unqualified :root
// Again, keep it separate from the other :root rule that contains non-theme props
// Remember, the cascade is your friend, not the enemy
root.classList.add('white');
document.querySelector('button').addEventListener('click', function() {
root.classList.toggle('white');
root.classList.toggle('black');
});
:root {
--spacing: 1rem;
color: var(--col);
background-color: var(--bgcol);
}
:root.white {
--bgcol: #FFF;
--col: #000;
}
:root.black {
--bgcol: #000;
--col: #FFF;
}
p {
margin: var(--spacing);
border: thin dashed;
padding: var(--spacing);
}
<button>Switch themes</button>
<p>Hello world!
Using :root selector is identical to using html, except its specifity is higher, thus there is no issues in using this approach.
For example:
:root {
--bg: red;
}
:root.blue {
--bg: blue;
}
// ...
div {
background: var(--bg);
}
Later, you should just change html's class and variables will change.
You can see an example in this fiddle.

Correct way to apply global styles into Shadow DOM

This questions is similar to some other on StackOverflow, but I couldn't find any answer describing applicable to my situation and non-deprecated method (and I'm starting thinking that maybe there is no any good solution for that situation).
Let's say we have some main.css file which includes common styles for buttons, lists, links and so on. So it's just some standard .css file which contains common styles that we want to reuse across the application. And we want to apply the same styles to Web Components with Shadow DOM.
There are a few ways, that I know about, to accomplish that:
Using one of deprecated approaches: ::shadow, >>>, /deep/ selectors. But those selectors are deprecated by now, so I guess it's not good approach to move forward with.
Using css variables. This approach is good for customization purposes, if we need to set a few properties. But it's too complex if we want to migrate 10-20 common styles from main.css file.
Using #import statement or "link" tags inside of Shadow DOM. It will work, but it will duplicate all styles for every component. If we have 10 web components we will end up with 10 duplicates of exactly the same styles. It doesn't sound like good enough solution too. Especially if we have a lot of common styles, sounds like it can be bad solution from performance point of view.
Don't use Shadow DOM at all and use global styles :) But it's not solution for current problem.
I also checked how the same problem resolved in Angular Framework (I checked version 5 of Angular). When I set encapsulation behavior to Native, it's just actually duplicating styles (like in #3 described above), what I think isn't the best way (but maybe the best currently existing way).
So, does anyone know if there is any other way to solve this problem without described above drawbacks? It just sounds like current drawbacks of Shadow DOM bring even more problems than it tries to solve.
There's no real drawback with solution 3:
Whether you apply a CSS style to n elements in a main document, or to 1 element in n Shadow DOM, the style will be duplicated to the whole n elements anyways.
If you import a document n times in n Shadow DOM, il will be actually be loaded only one time and reused through the browser cache.
After that, it will depend on the browser implementation of Shadow DOM and CSS styles, and you should see a performance degradation only the thousands of Shadow DOM.
2019 update for Chrome 73+ and Opera 60+
Now you can directly instanciate a CSSStyleSheet object and assign it to different Shadow DOMs.
This way the HTML won't be duplicated.
var css = new CSSStyleSheet()
css.replaceSync( "#import url( main.css )" )
host.shadowRoot.adoptedStyleSheets = [css]
host2.shadowRoot.adoptedStyleSheets = [css]
You can also apply it to the global document:
document.adoptedStyleSheets = [css]
The other advantage is that an update on the stylesheet will be applied to all Shadow DOMs (and document) that adopted it.
css.replaceSync( '.color { color: red }' )
I managed to do it using javascript modules but I doubt it's the cleanest solution.
You can create a GlobalStyles.js file that will contain the css styling that is common throughout various components. Changing the language mode on your editor to 'html' will provide syntax highlighting for the css.
const GlobalStyles = {
main: `
<style>
body {
overflow: hidden;
margin: 0;
font-family: 'Poppins';
}
h3 {
font-size: 39px;
}
</style>
`,
button: `
<style>
button {
display: block;
cursor: pointer;
outline: none;
font-family: 'Poppins Medium';
line-height: 17px;
padding: 9px 13px;
font-size: 15px;
background-color: #9f28d8;
color: white;
border: 2px solid;
border-radius: 5px;
border-color: #9f28d8;
width: max-content;
}
</style>
`
}
export default GlobalStyles;
Afterwards, you can import it into another js file that contains the code to the shadow dom of your custom element.
import GlobalStyles from './GlobalStyles.js';
const template = document.createElement('template');
template.innerHTML = `
${GlobalStyles.button}
<style>
ul {
font-family: Helvetica, Arial, sans-serif;
font-size: 13px;
width: 20em;
list-style-type: none;
}
</style>
<ul></ul>
<button>Click me</button>
`;
export class CustomList extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.appendChild(document.importNode(template.content, true));
}
}
The drawback with this approach is it only works if you are working purely with js files.

ReactJS styles 'leaking' to other components

So I have two components... a Navbar component, and an AboutPage component.
They are both in the same directory, 'App'
App
-- Navbar --> Navbar.css, Navbar.js
-- AboutPage --> Aboutpage.css, Aboutpage.js
So as you can see, they have two separate stylesheets.
In the JS pages the correct CSS file is being imported as well.
When I do a style like this for example:
Navbar Component
p { background: red }
^^ this style also applies to the p's in the Aboutpage. I even tried to give the P in Aboutpage its on id and style it that way and it still failed.
That's the expected behaviour.
No matter which file you specify a rule like p { background: red }, it's going to be applied to all DOM.
Specifying and id attribute to won't work either. The above rule is general enough to apply to all <p>s.
If you want to specify css files for each component, you should also create component specific css classes. Like the following example.
import React from 'react';
import './DottedBox.css';
const DottedBox = () => (
<div className="DottedBox">
<p className="DottedBox_content">Get started with CSS styling</p>
</div>
);
export default DottedBox;
and its css file:
.DottedBox {
margin: 40px;
border: 5px dotted pink;
}
.DottedBox_content {
font-size: 15px;
text-align: center;
}
If you want different ways of defining css for React, this resource adds 3 more ways of doing so, in addition to the above way.
You can also use css modules. They scope your CSS locally and are awesome
Scoping styles to a component requires WebComponents which relies on several newer browser features, particularly shadowRoot "shadownDOM" which supports this separation directly. These are most easily used with lit-element and/or Polymer 3.
Sometimes we need a global CSS which could affect another component even if we use module import, I didn't find anything to answer that in the official documentation, so my workaround is to use something like the following code in the component itself, and, it works fine :)
<style>
{
`
#page {
padding:0;
margin-top:0;
margin-bottom: 0;
margin-left: 0;
margin-right:0;
}
#media print {
#page {
size: 80mm 21cm;
}
}
`
}
</style>

Categories

Resources