| 1 | Use hyphenated element names | Required for valid custom elements. | customElements.define("user-card", UserCard); |
| 2 | Use clear component names | Makes components easier to understand. | <app-button></app-button> |
| 3 | Use PascalCase for element classes | Improves class readability. | class UserCard extends HTMLElement {} |
| 4 | Extend HTMLElement for autonomous elements | Creates reusable custom HTML tags. | class AppCard extends HTMLElement {} |
| 5 | Call super first in constructor | Initializes the parent element class. | constructor() { super(); } |
| 6 | Avoid heavy DOM work in constructor | Attributes and children may not be ready. | connectedCallback() { this.render(); } |
| 7 | Use connectedCallback for setup | Runs when the element is added to the page. | connectedCallback() { this.render(); } |
| 8 | Use disconnectedCallback for cleanup | Prevents memory leaks. | disconnectedCallback() { this.cleanup(); } |
| 9 | Use observedAttributes carefully | Tracks only attributes that need updates. | static get observedAttributes() { return ["open"]; } |
| 10 | Check values before re-rendering | Prevents unnecessary work. | if (oldValue === newValue) return; |
| 11 | Prevent duplicate definitions | Avoids runtime errors. | if (!customElements.get("app-card")) { customElements.define("app-card", AppCard); } |
| 12 | Use customElements.whenDefined | Waits until a component is registered. | customElements.whenDefined("app-card").then(init); |
| 13 | Use Shadow DOM when encapsulation is needed | Keeps internal markup and styles isolated. | this.attachShadow({ mode: "open" }); |
| 14 | Prefer open Shadow DOM for reusable components | Easier to inspect, test, and debug. | this.attachShadow({ mode: "open" }); |
| 15 | Avoid closed Shadow DOM unless necessary | Closed mode makes debugging harder. | // Prefer open mode for libraries |
| 16 | Use slots for external content | Allows flexible content projection. | <slot>Default content</slot> |
| 17 | Use named slots for structured content | Improves component layout control. | <slot name="title"></slot> |
| 18 | Provide slot fallback content | Improves empty-state behavior. | <slot>Default text</slot> |
| 19 | Use CSS Custom Properties for theming | Allows styling from outside Shadow DOM. | color: var(--card-color, #111827); |
| 20 | Provide CSS variable fallbacks | Keeps components safe without custom themes. | background: var(--button-bg, #0d6efd); |
| 21 | Use CSS Parts for element-level styling | Exposes safe styling hooks. | <button part="button">Save</button> |
| 22 | Treat part names as public API | Changing part names can break consumers. | app-button::part(button) |
| 23 | Use ::slotted only for direct slot content | Avoids styling confusion. | ::slotted(h2) { color: #0d6efd; } |
| 24 | Dispatch custom events for communication | Keeps internal implementation private. | this.dispatchEvent(new CustomEvent("item-selected")); |
| 25 | Use kebab-case event names | Matches HTML and component naming style. | "item-selected" |
| 26 | Use detail for event data | Passes useful payloads to consumers. | detail: { id: 101 } |
| 27 | Use bubbles for parent listeners | Allows event delegation outside the component. | bubbles: true |
| 28 | Use composed for Shadow DOM events | Allows events to cross Shadow DOM boundaries. | composed: true |
| 29 | Clean event listeners | Prevents memory leaks. | removeEventListener("click", this.handleClick); |
| 30 | Clean observers and timers | Prevents stale background work. | observer.disconnect(); clearInterval(timerId); |
| 31 | Use semantic HTML inside components | Improves accessibility. | <button type="button">Save</button> |
| 32 | Support keyboard interaction | Makes components usable without a mouse. | element.addEventListener("keydown", handleKey); |
| 33 | Manage focus carefully | Improves modal, menu, and form usability. | this.shadowRoot.querySelector("button").focus(); |
| 34 | Reflect important state with attributes | Makes state visible to CSS and HTML. | this.toggleAttribute("open", isOpen); |
| 35 | Sync attributes and properties safely | Keeps HTML and JavaScript APIs consistent. | set value(val) { this.setAttribute("value", val); } |
| 36 | Avoid unsafe innerHTML with user input | Reduces XSS risks. | message.textContent = userInput; |
| 37 | Use templates for repeated markup | Improves readability and reuse. | const template = document.createElement("template"); |
| 38 | Cache DOM references when useful | Reduces repeated queries. | this.button = this.shadowRoot.querySelector("button"); |
| 39 | Batch DOM updates | Improves rendering performance. | requestAnimationFrame(() => this.render()); |
| 40 | Avoid unnecessary re-renders | Improves performance and user experience. | if (this.value === nextValue) return; |
| 41 | Use Declarative Shadow DOM for SSR | Supports HTML-first rendering. | <template shadowrootmode="open"></template> |
| 42 | Hydrate only when interactivity is needed | Reduces JavaScript cost. | customElements.define("app-card", AppCard); |
| 43 | Design stable public APIs | Protects consumers from breaking changes. | // Document attributes, properties, events, slots, and parts |
| 44 | Document component usage | Improves adoption across teams. | // Example: <app-alert type="success"> |
| 45 | Version components carefully | Makes upgrades predictable. | // Use semantic versioning |
| 46 | Test accessibility | Prevents usability issues. | // Test keyboard, screen reader, and contrast behavior |
| 47 | Write component tests | Prevents regressions. | expect(element.shadowRoot).toBeTruthy(); |
| 48 | Test in real browsers | Web Components are browser APIs. | // Use Playwright or Web Test Runner |
| 49 | Keep dependencies minimal | Improves portability and bundle size. | // Prefer platform APIs when possible |
| 50 | Build for framework interoperability | Allows use in React, Angular, Vue, and vanilla apps. | <app-button>Save</app-button> |