How to use a CSS framework with LitElement
Asked Answered
H

3

7

I would like to use the CSS framework Bulma with LitElement. I know I can use an External Stylesheet However, they state it is bad practice to do it this way. I also have the problem that I had to import it into each element, which doesn't feel right.

So I copied the whole Bulma file content into a js module, but the styles are not applied after importing it.

import { css } from 'lit-element'

export default css`
@-webkit-keyframes spinAround {
  from {
    transform: rotate(0deg);
  }
...

Importing the style as link tag works but is as mentioned bad practice.

import { LitElement, html, css } from 'lit-element'
import './src/table.js'
import styles from './styles.js'

class LitApp extends LitElement {
  constructor() {
    super()
    this.tHeader = ['status', 'name', 'alias']
    this.data = [
      ['connect', 'john', 'jdoe'],
      ['disconnect', 'carol', 'carbon'],
      ['disconnect', 'mike', 'mkan'],
      ['disconnect', 'tina', 'tiba'],
    ]
  }
  static get styles() {
    return [
      styles, // does not work
      css`
      :host { 
        padding: 5vw;
        min-height: 100vh;
      }
      table.table {
        width: 100%;
      }`
    ] 
  }
  render() {
    return html`
      <link rel="stylesheet" href="./node_modules/bulma/css/bulma.min.css">
      <div class="columns">
        <div class="column is-8 is-offset-2">
          <div class="card">
            <div class="card-content">
              <agent-table .theader=${this.tHeader} .data=${this.data}></agent-table>
            </div>
          </div>
        </div>
      </div>`
  }
}

customElements.define('lit-app', LitApp)

Furthermore, the Table does not receive the styles and I had to import the file again, which I would like to avoid.

class AgentTable extends LitElement {
  constructor() {
    super()
    this.tHeader = []
    this.data = []
  }
  static get properties() {
    return { 
      theader: { type: Array },
      data: { type: Array },
    }
  }
  render() {
    return html`
        <table class="table is-narrow">
        <thead>
          <tr>${this.tHeader.map((header) => html`<td class="is-capitalized">${header}</td>`)}</tr>
        </thead>
        <tbody>
          ${this.data.map((row) => html`<tr>
            ${row.map((cell) => html`<td class="is-capitalized">${cell}</td>`)}
          </tr>`)}
        </tbody>`
  }
}

customElements.define('agent-table', AgentTable)

I generally struggle to find a good approach to apply a CSS framework. I read back and forth on this page https://lit-element.polymer-project.org/guide/styles but I don't get it to work.

Sorry, If this is a bit ambiguous or hard to understand I struggle to voice this question properly.

Hahn answered 6/1, 2020 at 21:6 Comment(4)
I don't know lit-element, but I assume your problem is that these elements use shadow DOM?Sinaloa
yes, those are web components. So they use shadow dom.Hahn
Web components don't have to use shadow DOM. They can, but it's not mandatory. Google "contructable stylesheets".Sinaloa
@Sinaloa if its not using a shadow dom, its a custom element not a web component. Web component is a custom element that uses a shadow dom. But yes adopted style sheet could be probably a solution.Hahn
B
2

There are a few problems with importing styles with the link tag. This is why it's a bad practice:

  • A new stylesheet will be created every time an instance of your element is created. LitElement uses constructable stylesheets (if supported) to share a single stylesheet instance across all elements.
  • The stylesheet is loaded after the element is rendered, so there can be a flash-of-unstyled-content
  • The ShadyCSS polyfill won't work
  • The href attribute is relative to the main document
  • Adding few thousands css rules to a custom element that only needs a few it's not the way it's meant to be. CSS frameworks should be split by custom-elements.

That said, you can do what you did there and copy the whole Bulma inside a js module. The problem it's that you have to escape the character "" of that CSS.

css`
.breadcrumb li + li::before {
  color: #b5b5b5;
  content: "\0002f";
}
`

This doesn't work as expected but doesn't fail because template literals allow you to read the raw strings as they were entered, without processing escape sequences. So there aren't any JS errors.

Escape those and it'll be fine:

css`
.breadcrumb li + li::before {
  color: #b5b5b5;
  content: "\\0002f";
}
`
Badenpowell answered 13/5, 2020 at 17:35 Comment(0)
H
0

Note, the browser support for the below is currently awful. As far as I understand, it only runs on chrome by default. Firefox has it implemented, but you need to start Firefox with an additional flag. In this post by the creator of svelte, the styling problem also comes up. There are some fascinating bits to take away from this whole discussion.

As hinted by some a while ago and already been under suspicion, I had another go at the problem.

So I am using adopted stylesheets now in this variant. As showcased here.

This seems like a legit way in some cases.

'use strict';

// define some web component
class WebComponent extends HTMLElement {
  constructor() {
    super()
    this.attachShadow({ mode: 'open' })
    this.text = ''
  }
  connectedCallback() {
    this.shadowRoot.innerHTML = `
    <!-- setting some custom css just to see if it stays -->
    <style> p { padding: 1rem; background-color: pink; } </style>

    <!-- this is expected to become large font size from tailwindcss -->
    <p class="title is-size-1">${this.text}</p>`
  }

}

customElements.define('web-component', WebComponent)

// create two instances of the component to see if adopted style
// will change for both, meaning they are both referencing the same 
// thing in memory
const webComponentA = document.createElement('web-component')
webComponentA.text = 'cowsay'
const webComponentB = document.createElement('web-component')
webComponentB.text= 'web'

// construct a blank style sheet
const sheet = new CSSStyleSheet();

(async () => {
  // get some css styles as text, maybe with fetch if purely in the browser
  // or via module resolver like rollup & webpack
  const url = 'https://cdn.jsdelivr.net/npm/[email protected]/css/bulma.min.css'
  const response = await fetch(url)
  await sheet.replace(await response.text());
  // set the styles on both components and add them to the dom
  webComponentA.shadowRoot.adoptedStyleSheets = [sheet];
  webComponentB.shadowRoot.adoptedStyleSheets = [sheet];
  document.body.append(webComponentA, webComponentB)
})().catch(console.warn);


// some time later, purge the style sheet
// and watch both components loosing the framework styles
setTimeout(() => sheet.replaceSync(``), 3000);

Unfortunately, one has to use a constructed style sheet like shown above. It doesn't work if you try to give the document root style sheet. I.E.

webComponent.shadowRoot.adoptedStyleSheets = [document.StyleSheet.item(0)]

DOMException: Failed to set the 'adoptedStyleSheets' property on 'ShadowRoot': Can't adopt non-constructed stylesheets.

Hahn answered 7/6, 2021 at 23:14 Comment(0)
S
0

I had this same question almost 4 years and a half later (integrating Bulma CSS with Lit); and in case it helps someone else, here is what I did.

I found the documentation on this page https://lit.dev/docs/components/shadow-dom

"Each Lit component has a render root—a DOM node that serves as a container for its internal DOM.

By default, LitElement creates an open shadowRoot and renders inside it, producing the following DOM structure:

<my-element>
  #shadow-root
    <p>child 1</p>
    <p>child 2</p>

There are two ways to customize the render root used by LitElement:

  1. Setting shadowRootOptions.
  2. Implementing the createRenderRoot method."

So the fastest option, just make sure all the components in the hierarchy (top -> down) overrides the createRenderRoot function and return this, to avoid the elements being wrapped (isolated) by a shadowRoot.

createRenderRoot() {
  return this;
}
Spindle answered 19/6 at 19:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.