Web components (+lit dependency)
I could not use frameworks (like React or Vue) on every project, so I started creating these components. This way it is more flexible and convenient for me, because I don't have an opportunity to always use Node.js and to build projects in module bundlers (like Webpack). There are a lot of small buisinesses that use php
or other server programming languages. And there is less entry threshold in using native web components, than in frontend frameworks.
To make web-components more comfortable, I used the lit library. It adds some syntactic sugar to skip the boilerplate and other useful features on top of standard web-components. You don't need to build or compile code. It is progressive enhancement and already ready to use.
There is no minify version. I use these components for my work and I want keep it in a readable state. You can minify files in your project if you want.
There are two ways to install and use these components:
In html document declare script tag with the importmap
type. And after that paste the script with components.
In html file
<script type="importmap">
{
"imports": {
"lit": "https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js"
}
}
</script>
<script defer type='modal' src='web-component-path'></script>
In js file
import {LitElement, html, css} from 'lit';
Just declare the dependency right in the js file. In html file
<script defer type='modal' src='web-component-path'></script>
In js file
import {LitElement, html, css} from 'https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js';
// here component code...
Generally web component consists of three elements:
- component tag (
<my-modal></my-modal>
) - attributes (to control modal:
<my-modal opened></my-modal>
) - entries (pasted into
<slot>...elements</slot>
element);
There is an example:
<my-modal opened>
<div slot='modal-header'>
<!-- elements -->
</div>
<div>
<!-- elements -->
</div>
</my-modal>
In this example I use the slot
attribute to place an element to a certain place in the web component
In the web component it looks like this:
<slot name='modal-header'></slot> <!-- there will be "<div slot='modal-header'>" -->
<slot></slot> <!-- there will be other no named elements -->
For more examples you can explore the code of web components.
Also some web components can trigger custom events. For example, a modal component triggers opened
and closed
events. You can set a listener to the web component and get data from event.detail
.
document.querySelector('p-modal').addEventListener('opened', (event) => {
console.log(event.detail)
})
In the web components you can give an access to contol styles. And there are two main ways to do it.
- use global css custom properties inside the web component
- declare in a web component
part
attribute and style it from css using::part()
function
Examples:
Global styles
:root {
--button-bg-color: #fff;
}
Web component style
.button {
background-color: var(--button-bg-color, #000);
}
Web component html
<button part='button'>Click me!</button>
Global styles
::part(button) {
background-color: #fff;
}
Important! When page is loading and js file with web component code have not executed yet, you will face to FOAC (flash of unstyled content). And for a moment you will see usual html inside web-component. Here is an example:
<p-select>
<select>
<option value="first">
first option
</option>
<option value="second">
second option
</option>
<option value="third">
third option
</option>
</select>
</p-select>
In this example a browser starts rendering usual select. And when js code executes, select will be replaced by stylized web-component. Try it and you will see. To avoid it, you should add these lines to css:
:not(:defined) {
opacity: 0;
}
Instead the opacity
property you can use another way to hide an element. It will select all undefined web components on the page and hide them until web components initialize.
For more comfortable using web components, vs code has two plugins: lit-plugin and lit-html
Also you can download basic-template files and make from them snippets for your IDE.
<p-select>
<select> <!-- ::part(custom-select__list) -->
<option value="first"> <!-- ::part(custom-select__trigger) -->
first option
</option>
<option value="second">
second option
</option>
<option value="third">
third option
</option>
</select>
</p-select>
Attributes
attribute | description | default value |
---|---|---|
needsearch | add search field for filtering options | false |
Events
event | description | return |
---|---|---|
change | triggering when a custom option is clicked and chosen | clicked element with data- attributes |
Styles
css-property | description | default value |
---|---|---|
--border-color | border color of list, options and search | #eee |
--hover-color | background-color when option is hovered | #eee |
--arrow-color | background-color when option is hovered | #000 |
--element-padding | padding of a trigger, a search field and options | 1rem |
--font-size | font size of all elements | 1rem |
--top-list-offset | top list offset from top of a trigger element | 3rem |
Attributes
attribute | description | default value |
---|---|---|
nostyle | remove default styles and add an opportunity to style tabs by own | false |
delimiter | character to concatenate multiple values | ',' |
<p-multiple-select delimiter="|">
<select name="select" slot="select">
<option selected value="1">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Perferendis sapiente quos sequi?
</option>
<option selected value="2">
Lorem ipsum dolor sit amet.
</option>
<option disabled value="3">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis repellat asperiores vel aliquam quis, exercitationem itaque! Earum nulla a eius!
</option>
<option value="4">
four
</option>
<option value="5">
five
</option>
</select>
</p-multiple-select>
There is a focus trap in this component for more a11y.
The modal adds to the root html element class p-block
after opening. You can add blocking styles to your css code if you want. Like html.p-block { overflow: hidden }
.
You can place this modal element to any DOM node and don't think about positions of parent nodes, their z-indexes. Script will teleport a modal content to the end of the body
.
Events
event | description | return |
---|---|---|
opened | triggering when a modal is opened | slotted element named 'modalContent' |
closed | triggering when a modal is closed | slotted element named 'modalContent' |
Parts
part | description |
---|---|
::part(modal-close) | to style a modal close button |
<p-modal>
<!-- your own button to open a modal -->
<button slot="openModal">open</button>
<!-- place for the modal content -->
<div slot="modalContent" class="slotted">
<form>
<input type="text" name="name">
<input type="text" name="lastname">
<input type="submit" value="submit">
</form>
</div>
</p-modal>
Attributes
attribute | description | default value |
---|---|---|
opened | you can use this attribute to control state of the loader component | false |
custom | you can paste your loader html with styles (see the below example) | false |
Styles
css-property | description | default value |
---|---|---|
--loader-overlay-color | background color of the overlay | #0000006b |
--spiner-color | color of spiner elements | #fff |
<p-loader custom opened>
<div name='custom'>
<!-- your loader -->
</div>
</p-loader>
Attributes
attribute | description | default value |
---|---|---|
nocollapse | prevent closing all accordion elements, when one element is chosen | false |
nostyle | remove default styles and add an opportunity to style tabs by own | false |
Parts
part | description |
---|---|
::part(p-trigger) | to style trigger buttons |
::part(p-content) | to style content parts |
<p-accordion nocollapse>
<div slot="list">
<button p-trigger>
Lorem, ipsum dolor.
</button>
<div p-content>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Non delectus, quidem dolores veniam eveniet rerum a dolorum vero explicabo? Rerum eligendi asperiores ducimus iure velit! Ratione quas dolor amet facilis?
</div>
<button p-trigger>
Lorem, ipsum dolor.
</button>
<div p-content>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Non delectus, quidem dolores veniam eveniet rerum a dolorum vero explicabo? Rerum eligendi asperiores ducimus iure velit! Ratione quas dolor amet facilis?
</div>
<button p-trigger>
Lorem, ipsum dolor.
</button>
<div p-content>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Non delectus, quidem dolores veniam eveniet rerum a dolorum vero explicabo? Rerum eligendi asperiores ducimus iure velit! Ratione quas dolor amet facilis?
</div>
</div>
</p-accordion>
Attributes (main tag)
attribute | description | default value |
---|---|---|
nostyle | remove default styles and add an opportunity to style tabs by own | false |
accordiononmobile | change tabs to accordion on mobile screen size (mobilemedia attribute) | false |
mobilemedia | set mobile media size for transforming tabs to accordion | 480 |
Parts
part | description |
---|---|
::part(p-trigger) | to style trigger buttons |
::part(p-content) | to style content parts |
Styles
css-property | description | default value |
---|---|---|
--custom-main-color | background color of triggers and content | #eee |
--custom-padding | padding of triggers and content | 1rem |
<p-tabs>
<div slot="list">
<button p-trigger>
<!-- tab title -->
</button>
<div p-content>
<!-- tab content -->
</div>
<button p-trigger>
<!-- tab title -->
</button>
<div p-content>
<!-- tab content -->
</div>
<button p-trigger>
<!-- tab title -->
</button>
<div p-content>
<!-- tab content -->
</div>
</div>
</p-tabs>
This component without any styling. It just a wrapper for a form. Needs to create a simple validation.
Attributes (main tag)
attribute | description | default value |
---|---|---|
successtext (required) | text that will appear when the form will be successfully validated and send data to the server | '' |
failtext (required) | text that will appear when the form will not send data to the server | '' |
backendhandler | path to form handler on the server look note 1 | '' |
Attributes (field tag) - all required
attribute | description |
---|---|
data-validate | it needs if you want to validate a field |
data-type | types to validate a field value in different way |
data-errormessage | the message that set to a parent label tag property data-error (see examples link above how to stylize it) |
Events
event | description | return |
---|---|---|
afterSuccessValidation example | triggered when form validation is succeeded | object reference look note 2 |
text
(check if a field value is empty)tel
(check if a phone value is numbers and consists of 11 numbers)email
(check if a email value fits the pattern)checkbox
(check if a checkbox is checked)
Backend handler have to return json object with answer
property (success|fail
):
{
"answer": "success",
// other your props
}
If backend handler didn't specified you can handle `afterSuccessValidation` event and make your custom form handler.
Object reference with fields:
{
form: this.form, // reference to the current form
}
Event catching example:
<p-form>
<form class='myForm'>
<!-- fields -->
</form>
</p-form>
<script>
const form = document.querySelector('.myForm')
form.addEventListener('afterSuccessValidation', (event) => {
// if backend handler is empty, there will be your form handler logic
})
</script>
IMPORTANT!
- any input field must be wrapped by label
tag to validate fields properly.
<p-form
successtext="Everything is fine"
failtext="We have a dangerous situation here"
beforesendcallback="validationCallback"
backendhandler="/path/to/handler.php"
>
<form>
<label>
<span>Name</span>
<input
name="firstname"
data-validate
data-type="text"
data-errormessage="field should not be emtpy"
type="text"
>
</label>
<label>
<span>Phone</span>
<input
name="phone"
data-validate
data-type="tel"
data-errormessage="phone is incorrect"
type="text"
>
</label>
<label>
<span>Email</span>
<input
name="email"
data-validate
data-type="email"
data-errormessage="email is incorrect"
type="email"
>
</label>
<label>
<input
name="agree"
data-validate
data-type="checkbox"
data-errormessage="email is incorrect"
type="checkbox"
>
<span>Please, agree with our policy</span>
</label>
<input type="submit" value="confirm">
</form>
</p-form>
Attributes
attribute | description | default value |
---|---|---|
acceleration | moving marquee speed | 1 |
gap | distance between elements | 20 |
direction | move direction `toRight | toLeft` |
fps | framerate | 60 |
IMPORTANT! If you past an image to marquee component, set width and height to this image. To prevent layout shift.
<p-infinite-marquee fps="30" acceleration="3" direction="toRight">
<p slot="content">Lorem ipsum dolor sit amet.</p>
</p-infinite-marquee>