UNDERSTAND ABOUT SHADOW DOM IN HTML

Comprehensive Guide to Shadow DOM in HTML

Introduction

Shadow DOM is a web platform feature that provides encapsulation for component styling and markup, enabling developers to create self-contained web components that are isolated from the rest of the document. Shadow DOM creates a scoped DOM tree that is attached to an element but separated from the main document DOM tree, preventing styles, scripts, and markup from leaking between the component and the parent document.

Shadow DOM is one of the four main technologies that make up Web Components (along with Custom Elements, HTML Templates, and HTML Imports). It solves common problems in web development such as CSS style conflicts, DOM pollution, and component naming collisions by providing true encapsulation at the browser level.

Understanding Shadow DOM

What is Shadow DOM?

Shadow DOM is a DOM tree that is attached to an element (called the shadow host) but is separate from the main document DOM tree. This creates a boundary between the component's internal structure and the external document, providing:

  • Style encapsulation: CSS styles defined inside Shadow DOM don't affect the main document, and vice versa
  • DOM encapsulation: The internal DOM structure is hidden from external JavaScript queries
  • Event retargeting: Events fired inside Shadow DOM are retargeted to appear as if they came from the shadow host

Shadow DOM Architecture

Main Document DOM
├── Regular Element
├── Shadow Host (element with Shadow DOM)
│   └── Shadow Root (root of Shadow DOM tree)
│       ├── Shadow DOM Content
│       └── Slots (for content projection)
└── Another Regular Element

Shadow DOM Modes

Shadow DOM can be created in two modes:

  • Open mode: The Shadow DOM is accessible from outside via the shadowRoot property
  • Closed mode: The Shadow DOM is completely inaccessible from outside the component

Creating Shadow DOM

Basic Shadow DOM Creation

// Get the element that will be the shadow host
const hostElement = document.getElementById('my-element');
// Create Shadow DOM in open mode
const shadowRoot = hostElement.attachShadow({ mode: 'open' });
// Add content to Shadow DOM
shadowRoot.innerHTML = `
<style>
p {
color: blue;
font-weight: bold;
}
</style>
<p>This is inside Shadow DOM</p>
`;
// Create Shadow DOM in closed mode
const closedShadowRoot = hostElement.attachShadow({ mode: 'closed' });
// closedShadowRoot is not accessible from outside the component

Complete Web Component with Shadow DOM

class MyComponent extends HTMLElement {
constructor() {
super();
// Create Shadow DOM in open mode
this.shadowRoot = this.attachShadow({ mode: 'open' });
// Add content to Shadow DOM
this.shadowRoot.innerHTML = `
<style>
.container {
border: 2px solid #3498db;
padding: 16px;
margin: 16px;
border-radius: 8px;
background-color: #ecf0f1;
}
h3 {
color: #2c3e50;
margin-top: 0;
}
p {
color: #7f8c8d;
line-height: 1.6;
}
</style>
<div class="container">
<h3>Shadow DOM Component</h3>
<p>This content is encapsulated in Shadow DOM.</p>
</div>
`;
}
}
// Register the custom element
customElements.define('my-component', MyComponent);

Usage in HTML:

<my-component></my-component>

Shadow DOM Encapsulation

Style Encapsulation

Styles defined in Shadow DOM only apply to elements within the Shadow DOM:

<!DOCTYPE html>
<html>
<head>
<style>
/* This style only affects the main document */
p {
color: red;
font-size: 24px;
}
/* This cannot affect Shadow DOM content */
.shadow-paragraph {
color: green; /* Won't work */
}
</style>
</head>
<body>
<p>This is in the main document (red, 24px)</p>
<div id="shadow-host"></div>
<script>
const host = document.getElementById('shadow-host');
const shadow = host.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
/* This style only affects Shadow DOM */
p {
color: blue;
font-size: 16px;
}
</style>
<p class="shadow-paragraph">This is in Shadow DOM (blue, 16px)</p>
`;
</script>
</body>
</html>

DOM Encapsulation

Elements inside Shadow DOM are not accessible via standard DOM queries from the main document:

// Main document
const host = document.getElementById('my-host');
const shadow = host.attachShadow({ mode: 'open' });
shadow.innerHTML = '<div id="internal-div">Internal Content</div>';
// These queries will NOT find the internal div
console.log(document.getElementById('internal-div')); // null
console.log(document.querySelector('#internal-div')); // null
console.log(document.querySelectorAll('div').length); // doesn't include internal div
// To access Shadow DOM content, you need the shadow root
console.log(host.shadowRoot.getElementById('internal-div')); // found!

Slots and Content Projection

What are Slots?

Slots are placeholder elements inside Shadow DOM that allow content from the light DOM (main document) to be projected into the Shadow DOM. This enables component composition while maintaining encapsulation.

Basic Slot Usage

class CardComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
margin: 16px;
background: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.card-header {
font-size: 18px;
font-weight: bold;
color: #2c3e50;
margin-bottom: 12px;
}
.card-body {
color: #7f8c8d;
line-height: 1.6;
}
</style>
<div class="card">
<div class="card-header">
<slot name="header">Default Header</slot>
</div>
<div class="card-body">
<slot name="body">Default Body</slot>
</div>
</div>
`;
}
}
customElements.define('card-component', CardComponent);

Usage with named slots:

<card-component>
<span slot="header">Custom Header</span>
<p slot="body">Custom body content with <strong>HTML</strong> support.</p>
</card-component>

Default Slots

Slots without a name attribute are default slots that capture any content not assigned to named slots:

class SimpleCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
.card {
border: 1px solid #ddd;
padding: 16px;
margin: 16px;
border-radius: 8px;
}
</style>
<div class="card">
<slot>Default content</slot>
</div>
`;
}
}
customElements.define('simple-card', SimpleCard);

Usage:

<simple-card>
<h3>Custom Title</h3>
<p>Custom paragraph content.</p>
</simple-card>

Multiple Content Nodes in Slots

Multiple elements can be assigned to the same slot:

<card-component>
<h3 slot="header">Main Header</h3>
<h4 slot="header">Sub Header</h4>
<p slot="body">First paragraph</p>
<p slot="body">Second paragraph</p>
</card-component>

CSS in Shadow DOM

Internal Styling

CSS defined inside Shadow DOM only affects elements within that Shadow DOM:

class StyledComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
/* These styles only apply inside Shadow DOM */
:host {
display: block;
margin: 16px 0;
}
.container {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 8px;
}
h3 {
margin-top: 0;
}
</style>
<div class="container">
<h3>Styled Component</h3>
<p>Content with gradient background</p>
</div>
`;
}
}
customElements.define('styled-component', StyledComponent);

The :host Pseudo-class

The :host pseudo-class allows you to style the shadow host element from within Shadow DOM:

/* Style the host element itself */
:host {
display: block;
margin: 16px 0;
}
/* Style the host when it has certain attributes */
:host([disabled]) {
opacity: 0.5;
pointer-events: none;
}
/* Style the host when it has certain classes */
:host(.large) {
font-size: 18px;
}
/* Style the host in different contexts */
:host-context(.dark-theme) {
background-color: #333;
color: white;
}

CSS Custom Properties (Variables)

CSS custom properties provide a way to theme Shadow DOM components from the outside:

class ThemedButton extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
button {
/* Use CSS custom properties with fallbacks */
background-color: var(--button-bg, #3498db);
color: var(--button-color, white);
border: var(--button-border, none);
padding: var(--button-padding, 10px 20px);
border-radius: var(--button-radius, 4px);
font-size: var(--button-font-size, 16px);
cursor: pointer;
transition: background-color 0.2s;
}
button:hover {
background-color: var(--button-hover-bg, #2980b9);
}
</style>
<button>
<slot>Click Me</slot>
</button>
`;
}
}
customElements.define('themed-button', ThemedButton);

Usage with custom properties:

<style>
themed-button {
--button-bg: #e74c3c;
--button-hover-bg: #c0392b;
--button-color: white;
--button-padding: 12px 24px;
}
.dark-theme themed-button {
--button-bg: #9b59b6;
--button-hover-bg: #8e44ad;
}
</style>
<themed-button>Custom Themed Button</themed-button>
<div class="dark-theme">
<themed-button>Dark Theme Button</themed-button>
</div>

CSS Mixins (Legacy Approach)

Note: CSS mixins were part of an older Shadow DOM specification and are not widely supported. Use CSS custom properties instead.

JavaScript and Shadow DOM

Accessing Shadow DOM

In open mode, Shadow DOM can be accessed via the shadowRoot property:

const host = document.querySelector('my-component');
const shadow = host.shadowRoot;
// Access elements inside Shadow DOM
const button = shadow.querySelector('button');
const container = shadow.getElementById('container');
// Add event listeners
button.addEventListener('click', () => {
console.log('Button clicked inside Shadow DOM');
});

Event Handling in Shadow DOM

Events fired inside Shadow DOM are retargeted to appear as if they came from the shadow host:

class EventComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<button id="internal-btn">Click Me</button>
`;
}
connectedCallback() {
this.shadowRoot.getElementById('internal-btn').addEventListener('click', (event) => {
// This event will appear to come from the shadow host
console.log('Event target:', event.target); // button
console.log('Event currentTarget:', event.currentTarget); // button
console.log('Event composedPath:', event.composedPath()); // [button, shadow-root, host, body, html, document, Window]
});
}
}
customElements.define('event-component', EventComponent);

Composed Events

To allow events to cross the Shadow DOM boundary, set composed: true:

// Inside Shadow DOM
this.dispatchEvent(new CustomEvent('custom-event', {
bubbles: true,
composed: true, // This allows the event to cross Shadow DOM boundary
detail: { message: 'Hello from Shadow DOM' }
}));
// In main document
document.querySelector('my-component').addEventListener('custom-event', (event) => {
console.log(event.detail.message); // "Hello from Shadow DOM"
});

Focus Management

Elements inside Shadow DOM can receive focus, but require special handling:

class FocusableComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
:host {
display: inline-block;
}
input:focus {
outline: 2px solid #3498db;
}
</style>
<input type="text" placeholder="Focusable input">
`;
}
// Make the host focusable and delegate to internal element
focus() {
this.shadowRoot.querySelector('input').focus();
}
}
customElements.define('focusable-component', FocusableComponent);

Advanced Shadow DOM Techniques

Dynamic Content Updates

Update Shadow DOM content dynamically based on attributes or properties:

class DynamicComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
static get observedAttributes() {
return ['title', 'content'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.render();
}
}
connectedCallback() {
this.render();
}
render() {
this.shadowRoot.innerHTML = `
<style>
.container {
padding: 16px;
border: 1px solid #ddd;
border-radius: 4px;
}
h3 { margin-top: 0; }
</style>
<div class="container">
<h3>${this.getAttribute('title') || 'Default Title'}</h3>
<p>${this.getAttribute('content') || 'Default content'}</p>
</div>
`;
}
}
customElements.define('dynamic-component', DynamicComponent);

Template-based Shadow DOM

Use HTML templates for better performance and organization:

<template id="my-component-template">
<style>
.container {
padding: 16px;
background: #f8f9fa;
border-radius: 8px;
}
</style>
<div class="container">
<h3><slot name="title">Default Title</slot></h3>
<p><slot name="content">Default content</slot></p>
</div>
</template>
<script>
class TemplateComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// Clone template content
const template = document.getElementById('my-component-template');
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
}
customElements.define('template-component', TemplateComponent);
</script>

Shadow DOM with External Stylesheets

Import external stylesheets into Shadow DOM:

class ExternalStyleComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
// Create link element for external stylesheet
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'component-styles.css';
// Add link and content to Shadow DOM
this.shadowRoot.appendChild(link);
this.shadowRoot.innerHTML += `
<div class="styled-content">
<slot></slot>
</div>
`;
}
}
customElements.define('external-style-component', ExternalStyleComponent);

Browser Support and Polyfills

Current Browser Support

  • Chrome: Full support (v53+)
  • Firefox: Full support (v63+)
  • Safari: Full support (v10+)
  • Edge: Full support (v79+)
  • Internet Explorer: No support

Feature Detection

Check for Shadow DOM support before using it:

if ('attachShadow' in Element.prototype) {
// Shadow DOM is supported
createShadowDOMComponent();
} else {
// Fallback for older browsers
createRegularComponent();
}

Polyfills

For older browsers, use the Web Components polyfill:

<!-- Load Web Components polyfill -->
<script src="https://unpkg.com/@webcomponents/[email protected]/webcomponents-loader.js"></script>
<script>
// Your Shadow DOM code
if ('attachShadow' in Element.prototype) {
// Create Shadow DOM components
}
</script>

Best Practices

1. Choose the Right Mode

  • Use open mode for debugging and when you need external access
  • Use closed mode for maximum encapsulation and security
// Open mode (recommended for most cases)
this.attachShadow({ mode: 'open' });
// Closed mode (for maximum encapsulation)
this.attachShadow({ mode: 'closed' });

2. Use CSS Custom Properties for Theming

Provide theming capabilities through CSS custom properties:

/* Inside Shadow DOM */
:host {
--primary-color: #3498db;
--text-color: #2c3e50;
}
.button {
background-color: var(--primary-color);
color: var(--text-color);
}

3. Minimize DOM Queries

Cache references to Shadow DOM elements to avoid repeated queries:

class OptimizedComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = '<button id="btn">Click</button>';
// Cache element reference
this.button = this.shadowRoot.getElementById('btn');
}
connectedCallback() {
// Use cached reference
this.button.addEventListener('click', this.handleClick);
}
}

4. Handle Attribute Changes Properly

Observe and respond to attribute changes for dynamic updates:

static get observedAttributes() {
return ['disabled', 'variant', 'size'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.updateComponent();
}
}

5. Use Slots for Content Composition

Leverage slots for flexible content projection:

<!-- Good: Uses slots for composition -->
<my-card>
<h3 slot="header">Custom Header</h3>
<p slot="body">Custom content</p>
</my-card>
<!-- Avoid: Hard-coded content -->
<my-card></my-card> <!-- No way to customize content -->

6. Provide Proper Accessibility

Ensure Shadow DOM components are accessible:

class AccessibleComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
button {
/* Ensure sufficient contrast */
background-color: #3498db;
color: white;
}
</style>
<button aria-label="Custom action">
<slot>Action</slot>
</button>
`;
}
}

Common Pitfalls to Avoid

1. Trying to Style Shadow DOM from Outside

/* Wrong: This won't work */
my-component .internal-element {
color: red;
}
/* Right: Use CSS custom properties */
my-component {
--internal-color: red;
}

2. Forgetting Event Composition

// Wrong: Event won't bubble out of Shadow DOM
this.dispatchEvent(new CustomEvent('my-event', { bubbles: true }));
// Right: Set composed to true
this.dispatchEvent(new CustomEvent('my-event', { 
bubbles: true, 
composed: true 
}));

3. Memory Leaks from Event Listeners

// Wrong: Event listeners not removed
connectedCallback() {
this.button.addEventListener('click', this.handleClick);
}
// Right: Remove event listeners
connectedCallback() {
this.button.addEventListener('click', this.handleClick);
}
disconnectedCallback() {
this.button.removeEventListener('click', this.handleClick);
}

4. Overusing Shadow DOM

// Consider if Shadow DOM is really needed
// For simple components without style conflicts, regular DOM might suffice
// Shadow DOM adds complexity and performance overhead

Complete Example: Reusable Modal Component

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shadow DOM Modal Example</title>
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
background-color: #f5f5f5;
}
/* Global styles don't affect Shadow DOM */
button {
background-color: green;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<h1>Shadow DOM Modal Example</h1>
<button id="open-modal">Open Modal</button>
<modal-dialog id="my-modal" title="Shadow DOM Modal">
<p>This modal is built with Shadow DOM!</p>
<p>Notice how the global button styles don't affect the modal buttons.</p>
</modal-dialog>
<script>
class ModalDialog extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.shadowRoot.innerHTML = `
<style>
.backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s, visibility 0.3s;
}
.backdrop.open {
opacity: 1;
visibility: visible;
}
.modal {
background: white;
border-radius: 8px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow: auto;
}
.modal-header {
padding: 16px 24px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-title {
margin: 0;
color: #2c3e50;
font-size: 18px;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #7f8c8d;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.close-btn:hover {
background-color: #f8f9fa;
color: #2c3e50;
}
.modal-content {
padding: 24px;
color: #34495e;
line-height: 1.6;
}
.modal-footer {
padding: 16px 24px;
border-top: 1px solid #eee;
text-align: right;
}
/* Modal-specific button styles */
.modal-footer button {
background-color: #3498db;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
margin-left: 8px;
}
.modal-footer button:hover {
background-color: #2980b9;
}
.modal-footer button.secondary {
background-color: #95a5a6;
}
.modal-footer button.secondary:hover {
background-color: #7f8c8d;
}
</style>
<div class="backdrop">
<div class="modal">
<div class="modal-header">
<h2 class="modal-title">
<slot name="title">Modal Title</slot>
</h2>
<button class="close-btn" aria-label="Close">&times;</button>
</div>
<div class="modal-content">
<slot>Modal content</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button class="secondary">Cancel</button>
<button class="primary">OK</button>
</slot>
</div>
</div>
</div>
`;
}
connectedCallback() {
this.backdrop = this.shadowRoot.querySelector('.backdrop');
this.closeBtn = this.shadowRoot.querySelector('.close-btn');
// Set title from attribute
const title = this.getAttribute('title');
if (title) {
this.shadowRoot.querySelector('.modal-title').textContent = title;
}
// Close button event
this.closeBtn.addEventListener('click', () => this.close());
// Click outside to close
this.backdrop.addEventListener('click', (e) => {
if (e.target === this.backdrop) {
this.close();
}
});
// Escape key to close
this.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.close();
}
});
// Emit custom event when opened
this.addEventListener('open', () => {
console.log('Modal opened');
});
}
open() {
this.backdrop.classList.add('open');
this.setAttribute('open', '');
this.focus();
this.dispatchEvent(new CustomEvent('open', { bubbles: true }));
}
close() {
this.backdrop.classList.remove('open');
this.removeAttribute('open');
this.dispatchEvent(new CustomEvent('close', { 
bubbles: true, 
composed: true 
}));
}
// Make component focusable
get tabIndex() {
return this.hasAttribute('open') ? 0 : -1;
}
}
customElements.define('modal-dialog', ModalDialog);
// Initialize modal
document.addEventListener('DOMContentLoaded', () => {
const modal = document.getElementById('my-modal');
const openBtn = document.getElementById('open-modal');
openBtn.addEventListener('click', () => {
modal.open();
});
// Handle modal close event
modal.addEventListener('close', () => {
console.log('Modal closed');
});
});
</script>
</body>
</html>

Conclusion

Shadow DOM is a powerful web platform feature that provides true encapsulation for web components, solving common problems like CSS style conflicts and DOM pollution. By creating a scoped DOM tree that is separate from the main document, Shadow DOM enables developers to build self-contained, reusable components that maintain their integrity across different contexts and applications.

The key benefits of Shadow DOM include:

  • Style encapsulation: CSS styles don't leak between components and the main document
  • DOM encapsulation: Internal component structure is hidden from external queries
  • Event retargeting: Events are properly handled while maintaining encapsulation
  • Composition with slots: Flexible content projection while maintaining boundaries
  • Theming with CSS custom properties: External customization without breaking encapsulation

When used properly, Shadow DOM forms the foundation of robust, maintainable, and interoperable web components that can be shared across different frameworks and applications. By following best practices and avoiding common pitfalls, developers can leverage Shadow DOM to create professional-grade web applications that are both powerful and maintainable.

Mastering Shadow DOM represents an advanced skill in modern web development that demonstrates understanding of web platform APIs, component architecture, and encapsulation principles. Proper Shadow DOM implementation enables the creation of truly modular, reusable, and future-proof web components that serve as building blocks for complex web applications.

HTML Basics – Elements, Attributes, Headings, Paragraphs, Links, Images, Tables & Forms (Related to HTML)


HTML Elements:
HTML elements are the basic building blocks of a webpage. They define how content like text, images, and sections are structured and displayed in the browser using tags.

Read more: https://macronepal.com/blog/understand-about-html-element-in-detail/


HTML Attributes:
HTML attributes provide extra information about elements and control their behavior or properties. They are written inside the opening tag in name-value form.

Read more: https://macronepal.com/blog/understand-about-html-attribute-in-detail/


HTML Headings:
HTML headings are used to define titles and organize content in a hierarchy from <h1> to <h6>, helping structure the page clearly.

Read more: https://macronepal.com/blog/understand-about-html-heading-in-detail/


HTML Paragraphs:
HTML paragraphs are used to display blocks of text using the <p> tag, helping to separate and organize written content.

Read more: https://macronepal.com/blog/understand-about-html-paragraph-in-detail/


HTML Links:
HTML links connect webpages using the <a> tag and the href attribute, allowing navigation between pages or websites.

Read more: https://macronepal.com/blog/understand-about-html-links-in-details/


HTML Images:
HTML images are added using the <img> tag to display pictures on a webpage, with src for the image source and alt for alternative text.

Read more: https://macronepal.com/blog/understand-about-html-image-in-details/


HTML Tables:
HTML tables organize data into rows and columns using <table>, <tr>, <th>, and <td> tags for structured data display.

Read more: https://macronepal.com/blog/understand-about-html-tables-in-details/


HTML Forms:
HTML forms are used to collect user input like text and passwords using elements such as <form>, <input>, and <button>.

Read more: https://macronepal.com/blog/understand-about-html-forms-in-details/

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper