javascript
264 lines · 1 tab
Alex Chang
Feb 2026
1 tab
// 1. Basic Custom Element
class MyButton extends HTMLElement {
constructor() {
super();
// Attach shadow DOM
const shadow = this.attachShadow({ mode: 'open' });
// Create elements
const button = document.createElement('button');
button.textContent = this.getAttribute('text') || 'Click me';
// Add styles
const style = document.createElement('style');
style.textContent = `
button {
padding: 10px 20px;
background: #4285f4;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: #357abd;
}
`;
shadow.appendChild(style);
shadow.appendChild(button);
button.addEventListener('click', () => {
this.dispatchEvent(new CustomEvent('myclick', {
bubbles: true,
composed: true,
detail: { message: 'Button clicked!' }
}));
});
}
}
// Register the custom element
customElements.define('my-button', MyButton);
// Usage: <my-button text="Submit"></my-button>
// 2. Component with Template and Slot
class UserCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
// Create template
const template = document.createElement('template');
template.innerHTML = `
<style>
:host {
display: block;
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
max-width: 300px;
}
.avatar {
width: 64px;
height: 64px;
border-radius: 50%;
object-fit: cover;
}
.name {
font-size: 1.25rem;
font-weight: bold;
margin: 0.5rem 0;
}
::slotted(p) {
color: #666;
}
</style>
<div class="card">
<img class="avatar" src="" alt="Avatar">
<h3 class="name"></h3>
<slot name="bio"></slot>
<slot></slot>
</div>
`;
shadow.appendChild(template.content.cloneNode(true));
}
// Lifecycle callbacks
connectedCallback() {
console.log('Component added to page');
this.render();
}
disconnectedCallback() {
console.log('Component removed from page');
}
adoptedCallback() {
console.log('Component moved to new page');
}
// Observed attributes
static get observedAttributes() {
return ['name', 'avatar'];
}
attributeChangedCallback(name, oldValue, newValue) {
console.log(`Attribute ${name} changed from ${oldValue} to ${newValue}`);
this.render();
}
render() {
const name = this.getAttribute('name') || 'Anonymous';
const avatar = this.getAttribute('avatar') || '/default-avatar.png';
const nameEl = this.shadowRoot.querySelector('.name');
const avatarEl = this.shadowRoot.querySelector('.avatar');
if (nameEl) nameEl.textContent = name;
if (avatarEl) avatarEl.src = avatar;
}
}
customElements.define('user-card', UserCard);
/* Usage:
<user-card name="Alex Chen" avatar="/alex.jpg">
<p slot="bio">Front-end developer</p>
<button>Follow</button>
</user-card>
*/
// 3. Advanced component with properties and methods
class TodoList extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this._todos = [];
}
connectedCallback() {
this.render();
this.attachEventListeners();
}
// Public API
addTodo(text) {
this._todos.push({ id: Date.now(), text, completed: false });
this.render();
}
removeTodo(id) {
this._todos = this._todos.filter(todo => todo.id !== id);
this.render();
}
toggleTodo(id) {
const todo = this._todos.find(t => t.id === id);
if (todo) {
todo.completed = !todo.completed;
this.render();
}
}
// Getters/setters
get todos() {
return this._todos;
}
set todos(value) {
this._todos = value;
this.render();
}
render() {
this.shadowRoot.innerHTML = `
<style>
ul { list-style: none; padding: 0; }
li {
padding: 0.5rem;
border-bottom: 1px solid #eee;
display: flex;
align-items: center;
gap: 0.5rem;
}
.completed { text-decoration: line-through; opacity: 0.6; }
button { padding: 0.25rem 0.5rem; }
</style>
<div>
<input type="text" id="new-todo" placeholder="Add todo">
<button id="add-btn">Add</button>
</div>
<ul>
${this._todos.map(todo => `
<li>
<input
type="checkbox"
${todo.completed ? 'checked' : ''}
data-id="${todo.id}"
>
<span class="${todo.completed ? 'completed' : ''}">
${todo.text}
</span>
<button data-id="${todo.id}" class="delete-btn">Delete</button>
</li>
`).join('')}
</ul>
`;
this.attachEventListeners();
}
attachEventListeners() {
const addBtn = this.shadowRoot.querySelector('#add-btn');
const input = this.shadowRoot.querySelector('#new-todo');
addBtn?.addEventListener('click', () => {
if (input.value.trim()) {
this.addTodo(input.value.trim());
input.value = '';
}
});
this.shadowRoot.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
checkbox.addEventListener('change', (e) => {
this.toggleTodo(Number(e.target.dataset.id));
});
});
this.shadowRoot.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
this.removeTodo(Number(e.target.dataset.id));
});
});
}
}
customElements.define('todo-list', TodoList);
// 4. Extending built-in elements
class FancyButton extends HTMLButtonElement {
connectedCallback() {
this.classList.add('fancy');
this.addEventListener('click', this.handleClick);
}
handleClick() {
this.classList.add('clicked');
setTimeout(() => this.classList.remove('clicked'), 300);
}
}
customElements.define('fancy-button', FancyButton, { extends: 'button' });
// Usage: <button is="fancy-button">Click me</button>
1 file · javascript
Explain with highlit
Web Components create reusable custom elements with encapsulated functionality. I use Custom Elements API to define new HTML tags with customElements.define(). Shadow DOM provides style and markup encapsulation preventing CSS leakage. HTML Templates with <template> and <slot> enable flexible component structures. The attachShadow() method creates shadow root for isolated DOM. Lifecycle callbacks like connectedCallback() and disconnectedCallback() manage component lifecycle. Attributes and properties enable component configuration. Web Components work with any framework or vanilla JavaScript. They provide true component reusability across projects and frameworks.
Related snips
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Semantic HTML Example</title>
Semantic HTML5 elements and accessibility best practices
html
html5
semantics
by Alex Chang
2 tabs
javascript
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
javascript
promises
async-await
by Alex Chang
1 tab
javascript
import { Controller } from "@hotwired/stimulus"
import Mousetrap from "mousetrap"
export default class extends Controller {
connect() {
// Global shortcuts
Keyboard shortcuts with Stimulus and Mousetrap
stimulus
javascript
ux
by Jordan Lee
2 tabs
javascript
// Get canvas and context
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
// 1. Basic shapes
// Rectangle (filled)
Canvas API for graphics and animations
canvas
javascript
graphics
by Alex Chang
1 tab
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Form Validation Example</title>
<style>
HTML forms with validation and accessibility
html
forms
validation
by Alex Chang
1 tab
erb
<div class="layout">
<ul class="product-list">
<% @products.each do |product| %>
<li class="product-row">
<%= link_to product.name,
product_path(product),
Turbo Frames: “details” panel that lazy-loads on click
rails
turbo
hotwire
by codesnips
4 tabs
Share this code
Here's the card — post it anywhere.