Polymer Introduction — Web Components and Modern Frontend Development
In this tutorial, you will learn about Polymer Introduction. We cover key concepts, practical examples, and best practices to help you master this topic.
Polymer is a library for building web components using the Web Components standard — Custom Elements, Shadow Dom, and HTML Templates — creating reusable, encapsulated components that work across any framework.
What You'll Learn
- Web Components specification overview
- Polymer library and LitElement
- Custom Elements API
- Shadow DOM Encapsulation
- HTML Templates
Why It Matters
Web Components are a browser-native standard — no framework required. Components built with Polymer work in any HTML page, any framework (React, Angular, Vue), and are future-proof because they use native browser APIs.
Real-World Use
A design system of reusable UI components (buttons, cards, dialogs, inputs) shared across multiple projects using different frameworks — all built with Polymer and used as standard HTML elements.
Polymer Architecture
flowchart TD
A[Polymer/Web Components] --> B[Custom Elements]
A --> C[Shadow DOM]
A --> D[HTML Templates]
A --> E[LitElement]
B --> F[Lifecycle Callbacks]
C --> G[Style Encapsulation]
C --> H[DOM Encapsulation]
D --> I[Declarative Templates]
E --> J[Reactive Properties]
E --> K[Render Function]
style A fill:#e6f3ff,stroke:#4a90d9,stroke-width:2px
Web Components Standards
// Custom Elements - define your own HTML elements
class MyElement extends HTMLElement {
constructor() {
super();
this.innerHTML = '<p>Hello from Custom Element</p>';
}
}
customElements.define('my-element', MyElement);
<!-- Usage -->
<my-element></my-element>
Expected output: The custom element renders "Hello from Custom Element" wherever <my-element> is used. Custom Elements are part of the browser standard.
Setting Up Polymer / LitElement
# Install LitElement (modern Polymer base class)
npm install lit
// Basic LitElement component
import { LitElement, html, css } from 'lit';
class MyComponent extends LitElement {
static styles = css`
:host {
display: block;
padding: 16px;
border: 1px solid #ccc;
border-radius: 8px;
}
h2 {
color: #1a237e;
}
`;
render() {
return html`
<h2>My Component</h2>
<p>This is a LitElement web component.</p>
`;
}
}
customElements.define('my-component', MyComponent);
<!-- Usage -->
<my-component></my-component>
Expected output: A styled card with a heading and paragraph. The Shadow DOM encapsulates the styles — they do not leak out or get affected by global CSS.
Shadow DOM
class ShadowDemo extends HTMLElement {
constructor() {
super();
// Attach shadow root in closed or open mode
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
p { color: blue; font-weight: bold; }
</style>
<p>This text is in Shadow DOM</p>
`;
}
}
customElements.define('shadow-demo', ShadowDemo);
<style>
/* This style does NOT affect the shadow DOM */
p { color: red; }
</style>
<shadow-demo></shadow-demo>
<p>This text uses global styles (red)</p>
Expected output: The shadow DOM paragraph is blue (encapsulated). The global paragraph is red. The Shadow DOM provides style and DOM encapsulation.
HTML Templates
<!-- Template definition -->
<template id="my-template">
<style>
.card {
border: 1px solid #ddd;
padding: 16px;
border-radius: 8px;
}
.title { font-size: 18px; font-weight: bold; }
</style>
<div class="card">
<div class="title"><slot name="title">Default Title</slot></div>
<div class="content"><slot></slot></div>
</div>
</template>
<script>
class CardComponent extends HTMLElement {
constructor() {
super();
const template = document.getElementById('my-template');
const content = template.content.cloneNode(true);
this.attachShadow({ mode: 'open' }).appendChild(content);
}
}
customElements.define('card-component', CardComponent);
</script>
<!-- Usage with slots -->
<card-component>
<span slot="title">My Card</span>
<p>This is the card content passed via slots.</p>
</card-component>
Expected output: The card component renders with a title (from slot="title") and content (default slot). Templates are parsed once and cloned for each instance.
LitElement Properties
import { LitElement, html, css } from 'lit';
class GreetingComponent extends LitElement {
static properties = {
name: { type: String },
count: { type: Number },
active: { type: Boolean }
};
constructor() {
super();
this.name = 'World';
this.count = 0;
this.active = true;
}
render() {
return html`
<h2>Hello, ${this.name}!</h2>
<p>Count: ${this.count}</p>
<p>Status: ${this.active ? 'Active' : 'Inactive'}</p>
<button @click=${this._increment}>Increment</button>
`;
}
_increment() {
this.count++;
}
}
customElements.define('greeting-component', GreetingComponent);
<greeting-component name="Polymer" count="5"></greeting-component>
Expected output: The component renders with the provided attributes. The count increments when the button is clicked. Properties are reactive — changes trigger re-render.
Component Lifecycle
class LifecycleDemo extends LitElement {
static properties = {
data: { type: Object }
};
constructor() {
super();
console.log('1. Constructor');
this.data = { loaded: false };
}
connectedCallback() {
super.connectedCallback();
console.log('2. Connected to DOM');
this._loadData();
}
async _loadData() {
console.log('3. Loading data...');
this.data = await fetch('/api/data').then(r => r.json());
console.log('4. Data loaded');
}
willUpdate(changedProperties) {
console.log('5. Will update:', changedProperties);
}
render() {
console.log('6. Render');
return html`<p>Data loaded: ${this.data.loaded}</p>`;
}
firstUpdated(changedProperties) {
console.log('7. First render complete');
}
updated(changedProperties) {
console.log('8. Updated:', changedProperties);
}
disconnectedCallback() {
super.disconnectedCallback();
console.log('9. Disconnected from DOM');
}
}
customElements.define('lifecycle-demo', LifecycleDemo);
Expected output: The lifecycle methods fire in order: constructor, connectedCallback, willUpdate, render, firstUpdated, updated. On removal: disconnectedCallback.
Common Mistakes
Not calling super() in constructor - LitElement requires super() in the constructor. Failing to call it breaks the element's lifecycle.
Using closed shadow mode unnecessarily - Closed mode (mode: 'closed') prevents external access. Use open mode for testability unless you have a specific reason.
Modifying DOM directly instead of using render - LitElement's render function is the only way to update the DOM. Direct DOM manipulation is overwritten on next render.
Forgetting to define customElements.define - Without registration, the component does not work. Always call customElements.define with a hyphenated name.
Not cleaning up in disconnectedCallback - Remove event listeners and timers in disconnectedCallback to prevent memory leaks.
Practice Questions
- What are the three Web Components standards?
- What is the difference between open and closed Shadow DOM?
- How do you define reactive properties in LitElement?
- What is the purpose of connectedCallback?
- How do you pass content into a component using slots?
Challenge: Build a reusable profile card component with: name, title, avatar URL, and bio as properties, a slot for action buttons, Shadow DOM for style encapsulation, reactive updates when properties change, and a connectedCallback that loads default data.
FAQ
Mini Project
Build a dashboard widget system with: a reusable card component with title, content slot, and action slot, a gauge component showing a percentage value with arc visualization, a data-table component accepting column definitions and row data via properties, and a dashboard container using all three components together.
What's Next
Web Components basics are covered. Learn how Polymer Custom Elements dives deeper into the Custom Elements API with advanced patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro