Search code examples
typescriptbuttonweb-componentcustom-elementlit

Button Web Component using lit element


I am a beginner to webcomponents and I am using lit with TypeScript to build a custom button web component.

The specifications require me to render a <svg> and a <p> tag as children in the custom component. Eg:

<my-button>
   <svg slot="svg">...</svg>
   <p slot="label">sample text</p>
</my-button>

In my custom component, I am conditionally rendering the children.i:e; either svg or label or both will rendered depeding on a condition.

For styling the slotted elements, I tried using ::slotted(p){colour:white}:

I also want to change the fill of svg and these styles are to be handled inside the custom componenet style itself. But the styles are not being set.

I also want to set different colours to the <p> depending on wether the button is disabled or ot. How can i achieve this?


Solution

  • This question is a bit broad, but I'll try to address the separate pieces and explain how I accomplished them. Because the question asks about the implementation details of a button, there are many valid answers. I've focused on a minimal answer that addresses each point in your question. There are links to documentation for further reading.

    For a finished live demo in the Lit playground, please see: https://lit.dev/playground/#gist=b630a85e7f9ff2db5300b42f2f7a43f4

    In the live link, you'll find a list of custom-btn lit elements which take an svg and label. The display attribute conditionally renders either 'both', 'svg', or 'label'. And there is also a disabled attribute that disables the button and changes the styles.

    Let's break it down.

    Using reactive properties we can declare public attributes for what to display, and whether the button is disabled.

    In the context of the custom-btn class, declaring these properties looks like this:

    ...
      @property()
      display: 'svg' | 'label' | 'both' = 'both'
          
      @property({type: Boolean})
      disabled = false;
    

    The render method can use these to conditionally render the named slots, and also propagate whether the button should be disabled using ?button, a declarative boolean attribute expression:

    ...
      render() {
        return html`
            <button ?disabled=${this.disabled}>
              ${this.display === 'svg' || this.display === 'both'
                    ? html`<slot name="svg"></slot>`
                    : null}
              ${this.display === 'label' || this.display === 'both'
                    ? html`<slot name="label"></slot>`
                    : null}
            </button>
            `;
      }
    

    This addresses the (non-styling) functionality in your question.

    To add styles, the slotted contents can be selected like you mentioned in the question:

      static styles = css`
        ::slotted(*) {
          color: red;
        }
        :host([disabled]) ::slotted(*) {
          color: blue;
        }
      `
    

    This will color all slotted contents red when the button is enabled, and color all slotted contents blue if the host element has the disabled boolean attribute.

    Note: ::slotted(*) selects all slotted contents.

    And :host[disabled] selects for the case when the host, i.e., custom-btn has a disabled attribute on it. So the selector activates when <custom-btn disabled> is encountered.

    To make the SVG fill color inherit and use the color from CSS, it's important the SVG uses the special keyword: currentColor. Note in the live repro: fill="currentColor" is set on the svg elements.

    Hope this unblocks your button implementation!