1

I'm trying to specify a theme as a property, and then override the static styles property on the Lit element.

This would be handy because then I could share an interface with my users such that they would have a type-safe way of composing custom themes of my web components for their own apps.

However, when I change the static styles property, Lit doesn't seem aware of the change, nothing actually happens. I've tried doing it in both the constructor and connectedCallback methods, but neither worked. In my real-world use case, it's slightly more complicated because I have a BaseComponent being inherited with its styles by other components, but let's leave it aside for this question, I guess.

Is this really possible? Or should I stick to using styleMap, classMap and regular CSS custom properties.

On the Lit's Discord server, another user mentioned that I might need to use Constructable Stylesheets — and it's apparently what Lit uses under the hood — but I'm not sure where to even start with that.

You can take a look at this example on Lit's playground here.

I think part of the problem is that I just can't seem to change the stylesheet part of components (static styles) during runtime, only at compile (transpile) time.

Lit Component:

import {html, css, unsafeCSS, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';

type Color = string;
interface CustomTheme {
  primary: Color;
}

const customTheme = {
  primary: "orange"
}
    
function themeToLitCss(theme: CustomTheme) {
  return css`
    p {
      color: ${unsafeCSS(theme.primary)}
    }
  `
}

@customElement('hello-world')
export class HelloWorld extends LitElement {
  static styles = [];

  @property({
  attribute: 'custom-theme',
  converter: (attrValue: string | undefined) =>
    attrValue ? JSON.parse(attrValue) : undefined,
  })
  customTheme?: CustomTheme;
  
  override connectedCallback() {
    super.connectedCallback();
    
    // Overriding the styles with an external theme specified by the user
    if (this.customTheme) HelloWorld.styles = [this.customTheme];
  }

  render() {
    return html`<p>Hello World</p>`;
  }
}

The HTML would look like this:

<hello-world custom-theme='{"primary": "orange"}'></hello-world>

I've also created an issue on Lit's Github repo.

Philippe Fanaro
  • 6,148
  • 6
  • 38
  • 76

1 Answers1

0

I've had a discussion with @justinfagnani on Github, and later I came up with this workaround, based on his comments.

Here's the adapted example with it:

import {html, css, unsafeCSS, LitElement} from 'lit';
import {customElement, property} from 'lit/decorators.js';

type Color = string;
interface CustomTheme {
  primary: Color;
}
    
function themeToLitCss(theme: CustomTheme) {
  return css`
     p {
       color: ${unsafeCSS(theme.primary)}
     }
  `
}

@customElement('hello-world')
export class HelloWorld extends LitElement {
  @property({
  attribute: 'custom-theme',
  converter: (attrValue: string | undefined) =>
    attrValue ? JSON.parse(attrValue) : undefined,
  })
  customTheme?: CustomTheme;

  render() {
    return html`
      <style>
        ${themeToLitCss(this.customTheme)}
      </style>
        
      <p>Hello World</p>
    `;
  }
}

It works on a per instance basis indeed:

<hello-world custom-theme='{"primary": "orange"}'></hello-world>
<hello-world custom-theme='{"primary": "red"}'></hello-world>

Here is the link to it on Lit's playground.

I still do believe a more framework-native approach would make this a lot cleaner, but this will do for now.

Philippe Fanaro
  • 6,148
  • 6
  • 38
  • 76