DEV Community

Cover image for Giving Super-Powers to your Web Components
Rajasegar Chandran
Rajasegar Chandran

Posted on

Giving Super-Powers to your Web Components

In this post, we are going to take a look at a new JavaScript library called styled-web-components which enhances your plain and simple web components by giving them super-powers via additional custom style properties.

Styled-web-components is light weight with no external dependencies. It has got convenient and shorthand property names like m,p,mx,px and so on. It doesn't require a build or compilation step. You can directly use them in your browser since it supports ES module format.

Inspiration

The real inspiration for this library came from styled-system. Styled System is a collection of utility functions that add style props to your React components and allows you to control styles based on a global theme object with typographic scales, colors, and layout properties.

If we give provision to add style props to our web components automatically, then they become flexible enough to build any complex UI layouts in the browser. That's what styled-web-components is all about.

Let's see this with an example. Let's assume you want to create a Flexbox layout with web components and you are planning to build a separate layout or container component something like my-flex. And you use it to compose your flex items to get a flex layout something like this:

<my-flex>
<div></div>
<div></div>
</my-flex>
Enter fullscreen mode Exit fullscreen mode

And you can style your my-flex component with something like this:

: host {
display: flex;
}
Enter fullscreen mode Exit fullscreen mode

Let's say you want to make some changes to your flex layout like adding some background-color or border-radius to your flex container. One way of doing that is to add the respective CSS properties to your my-flex component style declarations.

: host {
display: flex;
background-color: yellow;
border-radius: 20px;
}
Enter fullscreen mode Exit fullscreen mode

This is a serious mistake and an anti-pattern. Because you are basically violating the Re-usability and Single-Responsibility Principle (SRP) of the my-flex component. The job of my-flex component is only to provide a flexbox container for it's children, nothing more, nothing less. It has only one thing to do and it has to do it well.

Alt Text

And also this approach might not scale well if we want to have more styling for our layout components and what about if we want to build a more generic layout component, something like my-box with a block display value and can be used for a lot of use-cases with a lot of style customization just like how the typical example of a React styled-system component looks like:

<Box
  fontSize={4}
  fontWeight='bold'
  p={3}
  mb={[ 4, 5 ]}
  color='white'
  bg='primary'>
  Hello
</Box>
Enter fullscreen mode Exit fullscreen mode

Composition is the key

So how does this library achieve this. How can you add more custom style properties to your web components? Since all our web components aka Custom Elements are just plain JavaScript classes extending from the common base class HTMLElement, if we can make our components extends from multiple classes with different style props, then all the style props of the parent classes will become part of our component.

Alt Text

But we have one problem, multiple inheritance is not possible in JavaScript, which means this code won't work:

class Example extends ClassOne, ClassTwo {
    constructor() {
    }
}
Enter fullscreen mode Exit fullscreen mode

Then I came across this Stack Overflow post about this issue of extending multiple classes in JavaScript.

ES6 Class Multiple inheritance

166

I've done most of my research on this on BabelJS and on MDN (which has no information at all), but please feel free to tell me if I have not been careful enough in looking around for more information about the ES6 Spec.

I'm wondering whether or not ES6 supports…

The recommended solution to multiple inheritance is basically class composition by using class factories just like below:

// base class
class A {  
  foo() {
   }
}

// B mixin, will need a wrapper over it to be used
const B = (B) => class extends B {
  foo() {
    if (super.foo) super.foo(); // mixins don't know who is super, guard against not having the method
   }
};

// C mixin, will need a wrapper over it to be used
const C = (C) => class extends C {
  foo() {
    if (super.foo) super.foo(); // mixins don't know who is super, guard against not having the method
  }
};

// D class, extends A, B and C, preserving composition and super method
class D extends C(B(A)) {  
  foo() {
    super.foo();
  }
}
Enter fullscreen mode Exit fullscreen mode

Installation

You can install the library via npm using the below command.

npm install @rajasegar/styled-web-components
Enter fullscreen mode Exit fullscreen mode

via CDN:
You can also directly embed the library from a CDN via the script tag.

<script src="https://unpkg.com/@rajasegar/styled-web-components@2.0.2/dist/styled-web-components.min.js"></script>
Enter fullscreen mode Exit fullscreen mode

Usage

Create your own Custom element with composing multiple style props like Spacing, Color and Typography styles.

import { SpaceProps, ColorProps, TypographyProps } from 'styled-web-components'
class SWBox extends HTMLElement {
  constructor() {
    super()
    this.attachShadow({ mode: 'open' })
    const template = document.createElement('template')
    // This is very important, your template should have a style tag with :host selector
    template.innerHTML = `<style>
    :host { display: block; }
    <style>
    <slot></slot>`
    this.shadowRoot.appendChild(template.content.cloneNode(true))
  }
}
customElements.define('sw-box', TypographyProps(ColorProps(SpaceProps(SWBox))))
Enter fullscreen mode Exit fullscreen mode

Use your newly defined custom element in your HTML

  <sw-box py="2em" color="red" bg="yellow" font-family="sans-serif">
  <h1>Hello world</h1>
  </sw-box>
Enter fullscreen mode Exit fullscreen mode

Flex box custom component

Let's take a look at an example of creating our Flexbox layout component.

import { FlexboxProps } from 'styled-web-components'
class SWFlex extends HTMLElement {
  constructor() {
    super()
    this.attachShadow({ mode: 'open' })
    const template = document.createElement('template')
    template.innerHTML = `<style>
    :host { display: flex; }
    </style>
    <slot></slot>`
    this.shadowRoot.appendChild(template.content.cloneNode(true))
  }
}
customElements.define('sw-flex', FlexboxProps(SWFlex))
Enter fullscreen mode Exit fullscreen mode

Usage

And this is how we can make use of our newly created sw-flex component


<sw-flex justify-content="center" flex-direction="row-reverse">
  <sw-box m="1em" width="500px" py="2em" color="red" bg="yellow" font-family="sans-serif" text-align="center">
    <h3>Section 1</h3>
  </sw-box>
  <sw-box m="1em"  width="500px" py="2em" color="red" bg="yellow" font-family="sans-serif" text-align="center">
    <h3>Section 2</h3>
  </sw-box>
</sw-flex>

Enter fullscreen mode Exit fullscreen mode

Grid custom component

This is an example for a Grid layout component

import { GridProps } from 'styled-web-components'
class SWGrid extends HTMLElement {
  constructor() {
    super()
    this.attachShadow({ mode: 'open' })
    const template = document.createElement('template')
    template.innerHTML = `<style>
    :host { display: grid; }
    </style>
    <slot></slot>`
    this.shadowRoot.appendChild(template.content.cloneNode(true))
  }
}
customElements.define('sw-grid', GridProps(SWGrid))
Enter fullscreen mode Exit fullscreen mode

Usage

We can use the sw-grid component by passing the style properties like grid-template-columns and grid-gap

  <h2>Grid demo</h2>
  <sw-box m="2em">
  <sw-grid grid-template-columns='100px 100px 100px' grid-gap="10px">
    <sw-box bg="#444" color="#fff" border-radius="5px" p="20px" font-size="150%">A</sw-box>
    <sw-box bg="#444" color="#fff" border-radius="5px" p="20px" font-size="150%">B</sw-box>
    <sw-box bg="#444" color="#fff" border-radius="5px" p="20px" font-size="150%">C</sw-box>
    <sw-box bg="#444" color="#fff" border-radius="5px" p="20px" font-size="150%">D</sw-box>
    <sw-box bg="#444" color="#fff" border-radius="5px" p="20px" font-size="150%">E</sw-box>
    <sw-box bg="#444" color="#fff" border-radius="5px" p="20px" font-size="150%">F</sw-box>
  </sw-grid>
  </sw-box>
Enter fullscreen mode Exit fullscreen mode

Let's see our components in action in this Codepen demo:

That's all about the styled-web-components. Give it a try, and let us know for any issues, feedback or improvements in the comments.

The source code is in Github

Discussion (0)