Creating Native Web Components
Native web components have been here for a while, but they still seem like the new kid on the block. Mainly due to the fact that they have a high barrier to entry, notably for beginners, and don’t have most of the features from the more established front-end frameworks. Nonetheless, they can be useful, especially for those who want to create framework-agnostic and reusable components. Today we will build some native web components with Minze, a simple JavaScript framework that makes it a breeze to create native web components.
Let’s get started!
Prior knowledge
This tutorial should be quite easy to follow for anybody familiar with the basics of HTML, CSS and JavaScript. Advanced knowledge in those fields is not required.
Outline
We will be building several web components for a fictive Smart Home dashboard application. You will learn some basic concepts of web components and how to create them with Minze. During this tutorial we will build 3 Components:
- An accordion – for showing and hiding content.
- A switch – that toggles between two states, exacltly like a light-switch.
- A card – a simple encapsulated component that can be fed with content externaly
Following along
You can follow along in any environment by including a CDN link of Minze or by using this Codepen with the preloaded Minze script.
<script src="https://unpkg.com/minze@1.0.3" defer></script>
Accordion
Our first component is an accordion. It can be used to toggle content visibility.
1. Creating and registering the component
First, we need to extend from the MinzeElement
class to create our component. Then we use the define
method of the said component to register it. The component will be automatically registered in dash-case derived from its class name: sh-accordion
. Web components should always consist of at least two words to prevent clashing with build-in html-components.
JavaScript
class ShAccordion extends MinzeElement {
// ...
}
ShAccordion.define()
HTML Element
<sh-accordion>
<div slot="title">
Stats
</div>
<div slot="content">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur, similique!
</div>
</sh-accordion>
2. Properties & Methods
Next, we define the properties and methods of our component. reactive
defines well you guessed it, reactive properties, when they change a template re-render will be triggered. The first argument of the nested array is the name of the property, the second is the initial value. toggleOpen
is a method that will toggle the open
property between true and false.
class ShAccordion extends MinzeElement {
reactive = [['open', false]]
toggleOpen = () => this.open = !this.open
}
ShAccordion.define()
3. HTML
Here we are defining the html
property as an arrow function for rendering the encapsulated HTML of the component. The template includes a slot
tag that can be filled in when the component is used.
class ShAccordion extends MinzeElement {
// previous code ...
html = () => `
<div class="title">
<slot name="title"></slot>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 20 20" fill="currentColor" class="arrow">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</div>
<slot name="content"></slot>
`
}
ShAccordion.define()
4. CSS
Styling can be added by using the css
property. We are using the :host
pseudo-class selector to style the component and the ::sloted
CSS selector to style the externally inserted content. Note the use of ternary operators to conditionally apply styling based on the state of the open
property.
class ShAccordion extends MinzeElement {
// previous code ...
css = () => `
:host {
background: rgb(228 228 231);
font-family: sans-serif;
border-radius: 2px;
cursor: pointer;
}
.title {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
user-select: none;
padding: 16px;
}
.arrow {
transition: transform 0.2s ease-in-out;
transform: ${this.open ? 'rotate(180deg)' : 'rotate(0)'};
}
::slotted([slot=content]) {
display: ${this.open ? 'block' : 'none'};
padding: 16px;
}
`
}
ShAccordion.define()
5. Event listeners
Finally, we are adding a click event listener. The eventListeners
property defines one or more event listeners inside the component. We are attaching a click handler to the title. The first argument of the nested array is a CSS selector, the second is the event type and the third is a callback function that runs when the title is clicked.
class ShAccordion extends MinzeElement {
// previous code ...
eventListeners = [['.title', 'click', this.toggleOpen]]
}
ShAccordion.define()
Result
Here is the full implementation of the accordion component.
See the Pen
ShAccordion by Sergej Samsonenko (@sergejcodes)
on CodePen.light
Switch
Next up we will build the switch component.
1. Creating and registering the component
First, we need to extend from the MinzeElement
class to create our component. Then we use the define
method of the said component to register it. The component will be automatically registered in dash-case derived from its class name: sh-switch
.
JavaScript
class ShSwitch extends MinzeElement {
// ...
}
ShSwitch.define()
HTML Element
<sh-switch></sh-switch>
2. Properties & Methods
Next, we need to define the properties and methods for our component. reactive
defines reactive properties, when they change a template re-render will be triggered. The first argument of the nested array is the name of the property, the second is the initial value. toggleActive
is a method that toggles the active
property between true and false.
class ShSwitch extends MinzeElement {
reactive = [['active', false]]
toggleActive = () => this.active = !this.active
}
ShSwitch.define()
3. HTML
Here we are defining the html
property as an arrow function that holds the template of the component.
class ShSwitch extends MinzeElement {
// previous code ...
html = () => `
<div class="indicator"></div>
`
}
ShSwitch.define()
4. CSS
Now we are adding styling. We are using the :host
pseudo-class selector to style the component. Note the use of ternary operators to conditionally apply styles based on the state of the active
property.
class ShSwitch extends MinzeElement {
// previous code ...
css = () => `
:host {
width: 48px;
height: 25px;
display: flex;
background: rgb(255 255 255);
border: 1px solid rgb(228 228 231);
border-radius: 9999px;
cursor: pointer;
transition: all 0.2s ease-in-out;
padding: 2px;
}
.indicator {
width: 20px;
height: 20px;
background: ${this.active ? 'rgb(161, 161, 170)' : 'rgb(228 228 231)'};
border-radius: 9999px;
position: relative;
transform: translateX(${this.active ? 'calc(100% + 2px)' : '0'});
transition: all 0.2s ease-in-out;
}
`
}
ShSwitch.define()
5. Event listeners
Finally, we are adding event listeners. The eventListeners
property can define multiple event listeners inside the component. We are attaching a click handler to the element. The first argument of the nested array is a CSS selector, the second is the event type and the third is a callback function.
class ShSwitch extends MinzeElement {
// previous code ...
eventListeners = [[this, 'click', this.toggleActive]]
}
ShSwitch.define()
Result
Below is the full implementation of the switch component.
See the Pen
ShToggle by Sergej Samsonenko (@sergejcodes)
on CodePen.light
Card
Our last element will be a card component.
1. Creating and registering the component
First, we need to extend from the MinzeElement
class to create our component. Then we use the define
method of the said component to register it. The component will be automatically registered in dash-case derived from its class name: sh-card
.
JavaScript
class ShCard extends MinzeElement {
// ...
}
ShCard.define()
HTML Element
<sh-card
top-line="Outside"
headline="Temperature"
value="12°c"
background="linear-gradient(220.64deg, #C8F5FF 0%, #B4B4FF 100%)"
></sh-card>
2. Attributes
Next, we need to define the attributes that can be added to the component.
class ShCard extends MinzeElement {
attrs = ['top-line', 'headline', 'value', 'background']
}
ShCard.define()
3. HTML
Here we are defining the html
property as an arrow function that holds the template of the component.
class ShCard extends MinzeElement {
// previous code ...
html = () => `
<div class="top-line">${this.topLine ?? ''}</div>
<div class="headline">${this.headline ?? ''}</div>
<slot>
<div class="value">${this.value ?? ''}</div>
</slot>
`
}
ShCard.define()
4. CSS
Adding styling for multiple nested elements inside the component. We are using the :host
pseudo-class selector to style the `sh-card` element.
class ShCard extends MinzeElement {
// previous code ...
css = () => `
:host {
width: 200px;
height: 180px;
display: flex;
flex-direction: column;
flex-grow: 1;
background: ${this.background ?? 'transparent'};
font-family: sans-serif;
border-radius: 2px;
padding: 24px 24px 16px;
}
.top-line {
font-size: 16px;
margin-bottom: 2px;
}
.headline {
font-size: 20px;
font-weight: bold;
}
.value {
font-size: 36px;
font-weight: bold;
margin-top: auto;
}
::slotted(*) {
margin-top: auto;
margin-bottom: 12px;
}
`
}
ShCard.define()
Result
Here is the full implementation of the card component.
See the Pen
ShCard by Sergej Samsonenko (@sergejcodes)
on CodePen.light
Conclusion
As you can probably tell by now creating web components isn’t really rocket science. To see even more examples view the demo accompanied to this tutorial: