Are web components capable of managing changes in CSS (maybe through cssChangedCallback)?

Is there a way to control the application of CSS to a web component similar to using attributes through attributeChangedCallback.

I am currently developing a couple of web components that could benefit from being styled with CSS classes. However, I require multiple style changes for it to display correctly (for example, changing the color of the control should also update the border color of one element and the font color of another within the shadow DOM).


Is it possible to make the toggle switch in the following basic web component example change its color to red by using .usingCSS { color: red; }?

// based on https://www.w3schools.com/howto/howto_css_switch.asp

class W3schoolsToggleSwitch extends HTMLElement {
  constructor() {
    super();
    var shadow = this.attachShadow({ mode: "open" });
    this.span = document.createElement("span");
    this.span.innerHTML = `
    <style>
      /* The switch - the box around the slider */
      .switch {
        --color: #2196F3;
        position: relative;
        display: inline-block;
        width: 60px;
        height: 34px;
      }

      /* Hide default HTML checkbox */
      .switch input {
        opacity: 0;
        width: 0;
        height: 0;
      }

      /* The slider */
      .slider {
        position: absolute;
        cursor: pointer;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        background-color: #ccc;
        -webkit-transition: .4s;
        transition: .4s;
      }

      .slider:before {
        position: absolute;
        content: "";
        height: 26px;
        width: 26px;
        left: 4px;
        bottom: 4px;
        background-color: white;
        -webkit-transition: .4s;
        transition: .4s;
      }

      input:checked + .slider {
        background-color: var(--color);
      }

      input:focus + .slider {
        box-shadow: 0 0 1px #2196F3;
      }

      input:checked + .slider:before {
        -webkit-transform: translateX(26px);
        -ms-transform: translateX(26px);
        transform: translateX(26px);
      }

      /* Rounded sliders */
      .slider.round {
        border-radius: 34px;
      }

      .slider.round:before {
        border-radius: 50%;
      }
    </style>
    <label class="switch">
        <input type="checkbox" checked>
        <span class="slider round"></span>
    </label>
    `;
    shadow.appendChild(this.span);
  }

  static get observedAttributes() {
    return ["color"];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    console.log(name, newValue);
    if ("color" === name) {
      this.shadowRoot
        .querySelector(".switch")
        .style.setProperty("--color", newValue);
    }
  }

  get color() {
    return this.getAttribute("color");
  }

  set color(value) {
    return this.setAttribute("color", value);
  }
}

customElements.define("w3schools-toggle-switch", W3schoolsToggleSwitch);
.usingCSS {
  color: red; 
}
default:
<w3schools-toggle-switch></w3schools-toggle-switch>

<br><br> Changing the color attribute to green:
<w3schools-toggle-switch color="green"></w3schools-toggle-switch>

<br><br> Can you change the color using CSS?:
<w3schools-toggle-switch class="usingCSS"></w3schools-toggle-switch>

Answer №1

View from the exterior using <link>

You have the ability to implement CSS styling to a Web Component utilizing a <link> element within the Shadow DOM.

#shadow-root
  <link rel="stylesheet" href="default.css">

attributeChangedCallback( name, old, value ) {
   if (name === 'class') 
      this.shadowRoot.querySelector( 'link' ).href = value + ".css"

} 

Utilizing style declaration within Shadow DOM :host() pseudo-class function

You have the ability to apply diverse styles based on the context. You can blend multiple classes together.

customElements.define( 'custom-element', class extends HTMLElement {
  constructor() {
    super()
    this.attachShadow( { mode: 'open' } )
        .innerHTML = `
          <style>
            :host( .red ) { color: red }
            :host( .blue ) { color: blue }
            :host( .border ) { border: 1px solid }
          </style>
          Hello`
  }
} )

ce1.onclick = ev => ev.target.classList.add( 'border' )
<custom-element class="red" id="ce1"></custom-element>
<custom-element class="blue border"></custom-element>

On Chrome / Opera: employing Constructable stylesheets

Create one (or more) Stylesheet(s) and apply it(them) to the Shadow DOM. You can attach numerous stylesheets to the same Shadow DOM.

var ss = []
ss['red'] = new CSSStyleSheet
ss.red.replaceSync( 'span { color: red }' ) 
ss['green'] = new CSSStyleSheet
ss.green.replaceSync( 'span { color: green }' ) 
ss['border'] = new CSSStyleSheet
ss.border.replaceSync( 'span { border: 1px solid }' ) 

customElements.define( 'custom-element', class extends HTMLElement {
  constructor() {
    super()
    this.attachShadow( { mode: 'open' } )
        .innerHTML = `<span>Hello</span>`
  }
  
  static get observedAttributes() { return [ 'class' ] }
  
  attributeChangedCallback() {
    this.shadowRoot.adoptedStyleSheets = [ ...this.classList ].map( cl => ss[ cl ] )
  }
} )

ce1.onclick = ev => ev.target.classList.add( 'border' )
<custom-element class="red" id="ce1"></custom-element>
<custom-element class="green border"></custom-element>

Answer №2

Building upon the response by Supersharps.

In situations where Constructable Stylesheets are not applicable yet:

You have the option to (albeit forcefully) import an entire STYLE definition from the Host document.

onload=this.disabled=true can be used to disable styling of the document DOM

Alternatively, you can create a

<my-themes></my-themes>
Component that contains (and delivers) the STYLE elements

customElements.define( 'custom-element', class extends HTMLElement {
  constructor() {
    super()
    this.root=this.attachShadow( { mode: 'open' } );
    this.root.innerHTML = `<style>div{font-size:40px}</style>`
                         +`<style id="theme"></style><div>Click Me</div>`;
    let themes = window.themes;//duplicate IDs create a global NodeList
    let themeNr = 0;
    this.root.addEventListener('click', ev => 
       this.theme = themes[ themeNr<themes.length ? themeNr++ : themeNr=0 ].innerHTML);
  }
  set theme(css){
    this.root.getElementById('theme').innerHTML = css;
  }
} )
<style id="themes" onload="this.disabled=true">
  div{
    background:yellow;
  }
</style>
<style id="themes" onload="this.disabled=true">
  div{
    background:hotpink;
    font-size:30px;
  }
</style>
<style id="themes" onload="this.disabled=true">
  div{
    background:red;
    color:white;
  }
  div::after{
    content:" theme2"
  }
</style>
<custom-element></custom-element>
<custom-element></custom-element>
<custom-element></custom-element>
<div>Main Document</div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Vue.js: Issue with applying class binding while iterating over an object

I've been working with an object data that looks like this: object = { "2020092020-08-01":{ "value":"123", "id_number":"202009" }, "2020092020-09-01":{ "value& ...

Is there a way to dynamically alter the heading color in Shopify liquid code?

After utilizing ChatGPT to review the code, I am uncertain if it is related to the color schemes. I am unsure of what I may be missing or doing incorrectly. While I can successfully alter the heading text, I am unable to change the color. Below is the code ...

Monitoring multiple items in OPC UA with Node.js

Utilizing the node-opcua module, my goal is to efficiently monitor multiple opc ua nodes through subscriptions. The desired workflow involves a user selecting nodes in an HTML UI, clicking a Monitor button which then sends the corresponding nodeIds as para ...

Adding a div to the preceding div based on matching IDs in AngularJS

Here is a representation of my layout in HTML: <div ng-repeat="message in messages"> <div ng-class="{'messages messages--sent': userId == message.id, 'messages messages--received': userId != me ...

What is the most effective method to store temporary data within a backend panel as a sub-option?

Greetings! I have been delving into the world of programming for a while now, striving to enhance my skills. Currently, I am faced with a dilemma: Within my backend application, administrators have the capability to add courses to our website. One featur ...

Customize Vue.js: Disable Attribute Quote Removal for Individual Pages

We have a requirement to turn off the minify.removeAttributeQuotes property for certain pages. This is the content of my vue.config.js: const packageJson = require('./package.json') module.exports = { assetsDir: packageJson.name + &apos ...

Create a chessboard with a border using only HTML and CSS

I recently completed a chessboard using only HTML and CSS, but I am facing a challenge as I attempt to add a frame around the board. Since I lack design skills, I am struggling to achieve this simple task. I have tried utilizing the CSS border property wit ...

Utilizing Compass SCSS with Blueprint for Optimal Overflow Settings

I've been working with blueprint through Compass, but I'm facing an issue where longer pages don't have scroll bars. It seems like the default setting for overflow is 'hidden'. Since this appears to be the standard, I assume there ...

Ensuring Navigation Elements Remain Aligned in a Single Line

I'm in the process of developing an interactive project that will be showcased on a touch screen. One of its key features is a fixed footer navigation. Currently, I am using floats to position the main navigation elements and within each element, ther ...

distinguish different designs for individual components in Angular 5

My project is divided into two main parts: one for public web pages and the other for an admin control panel. Each part has its own set of CSS and javascript files for custom templates. If I include all CSS and js files in index.html, they all load at the ...

When displaying a collection of components, clicking a button will always select the most recent element in the array

Can you explain why this code won't work in a React environment? Every time the button is clicked, it picks up the value "name" from the last element in the array. In this example, the dialog will always display the name "John2". import React from "r ...

What are the steps to create a "gooey glide" animation using React and MUI?

I am looking to create a unique animation for my list of items on a web page. My goal is to have the items slide in one by one with rapid succession and then slightly shrink once they reach their final position, similar to how pillows might fall or like a ...

How to style a sublevel menu to appear next to its parent using CSS

I have a menu that is currently functioning well, but I would like to add a new sublevel onto it. However, if I directly add the new options, they end up hiding the existing ones on the menu. Here's an image for reference: I want the new options to ...

Tips for updating an object variable dynamically in an Angular application

const person = { "name": "jacob", "age": 22 } I am looking to dynamically update it to: { "name": "jacob", "age": 22, "dob": number } ...

Issue arising with data exchange between components using data service in Angular 5

Utilizing data service to share information between components has presented a challenge for me. References: Angular: Updating UI from child component to parent component Methods for Sharing Data Between Angular Components Despite attempting the logic o ...

Developing an unchanging structure for HTML pages

I need assistance in designing an HTML layout with a fixed toolbar at the top and bottom, along with a single centered DIV that should be responsive when the user resizes the window both vertically and horizontally. I have attached a mockup/screenshot fo ...

The background-size CSS property may experience malfunctioning on iOS devices when using Safari browser

My website has a body background size set, which works well on Android and Windows devices. The background size is intended to be 85% width and 5% height of the body. However, when loading the site on an iPhone, the height appears much larger than 5%. It s ...

Issue with displaying elements at intended layering order when parent element has fixed positioning

My web app features both upper and lower elements (div) with a position: fixed. Each element has child popups also with position: fixed. Even though I want to position the popup above its parent element, using z-index doesn't work due to inheritance ...

Refreshing dynamically added rows through ajax updates

I'm struggling to clearly articulate my problem. Currently, I have a function that retrieves values from a database using ajax and then dynamically adds each result as a row in a table. This allows users to edit or delete any row as needed. I'm ...

The concept of CSS inheritance within div elements

I've been researching extensively about the concept of CSS inheritance, but I have come across a puzzling question that remains unanswered. Consider the code snippet below: <!DOCTYPE HTML> <html> <head> <style type="text/css"> ...