Ui Components
Master the foundations of component architecture without frameworks. Learn Custom Elements, Shadow DOM, slots, state management, and patterns used by React, Vue, and Svelte.
Learn Python, JavaScript, Java and more with free interactive lessons, real projects and AI-powered help. Beginner-friendly.
Part of the free JavaScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Custom Elements and Shadow DOM work in all modern browsers. To test these components:
What You'll Learn
What Are Reusable UI Components?
A reusable component is a self-contained piece of UI with its own markup, styles, logic, and state. It can be instantiated multiple times, accepts inputs (props), emits events, and manages its own lifecycle.
💡 Why Pure JS? Zero dependencies, faster than framework components, works anywhere, teaches real DOM mastery, and you can port logic to React/Vue easily.
Factory Function Pattern
The simplest pattern for creating reusable components. A function returns an object with the DOM element and a public API for external control.
Template Clone Pattern
For HTML-driven components, use the <template> element. Templates are not rendered but can be cloned efficiently, making this pattern very fast.
Component Communication with Custom Events
Components emit custom events to communicate with parent code. This keeps components loosely coupled and reusable across different contexts.
Shadow DOM — Full Encapsulation
Shadow DOM creates a private DOM tree inside your element. Styles are completely isolated — no leakage to or from global CSS. This is how browsers implement <video>, <input>, and <select>.
✅ Shadow DOM Benefits: Style encapsulation, no naming conflicts, framework-level component scoping, and markup isolation.
Custom Elements with Lifecycle
Custom Elements are the browser's native component system. They have lifecycle callbacks similar to React, can observe attribute changes, and are fully reusable.
Named Slots for Flexible Composition
Slots let users inject content into specific areas of your component — similar to React's children prop but with named targets.
Component Factory Pattern
Generate multiple components from a single factory function. This pattern lets you create consistent UI libraries with minimal code duplication.
Global State Management
For shared state across components (theme, user session, notifications), implement a simple store with subscribers — the same pattern used by Redux and Vuex.
Event Bus for Cross-Component Communication
An event bus allows any component to emit or listen to events without direct references. This enables loose coupling between unrelated components.
Async Components
Components that load data before rendering are essential for real applications. Handle loading states, errors, and dynamic attribute changes.
Accessibility Patterns
Professional components must be accessible. Implement ARIA roles, keyboard navigation, focus management, and screen reader support.
💡 Accessibility Checklist: ARIA roles, keyboard navigation (Arrow keys, Home, End), focus states, aria-selected/aria-hidden, proper tabindex, and screen reader labels.
High-Performance Patterns
For large-scale applications, optimize with template caching, batched updates, object pooling, lazy initialization, and event delegation.
Key Takeaways
Practice quiz
What is a reusable UI component?
- A single global variable
- A CSS file
- A self-contained piece of UI with its own markup, styles, logic, and state
- A server endpoint
Answer: A self-contained piece of UI with its own markup, styles, logic, and state. A reusable component is self-contained — it has its own markup, styles, logic, and state, and can be instantiated multiple times.
In the factory function pattern, what does the function return?
- An object exposing the DOM element and a public API for external control
- A string of HTML only
- Nothing
- A CSS class
Answer: An object exposing the DOM element and a public API for external control. The factory returns an object with the DOM element plus a public API (methods like setValue, getValue, destroy).
Why is the <template> element useful for components?
- It renders immediately
- It runs JavaScript
- It styles the page globally
- It is not rendered but can be cloned efficiently
Answer: It is not rendered but can be cloned efficiently. Template elements are not rendered, but their content can be cloned efficiently, making the pattern fast.
How does a component notify parent code of something happening?
- By mutating a global variable
- By dispatching a CustomEvent that parents can listen to
- By calling document.write
- By reloading the page
Answer: By dispatching a CustomEvent that parents can listen to. Components emit CustomEvents (via dispatchEvent) so parent code can listen and stay loosely coupled.
What does Shadow DOM provide?
- A private DOM tree with completely isolated styles
- Faster network requests
- Automatic routing
- Server-side rendering
Answer: A private DOM tree with completely isolated styles. Shadow DOM creates a private DOM tree inside the element with fully isolated styles — no leakage to or from global CSS.
Which lifecycle callback runs when a custom element is added to the DOM?
- constructor()
- disconnectedCallback()
- connectedCallback()
- attributeChangedCallback()
Answer: connectedCallback(). connectedCallback() is called when the element is inserted into the DOM.
What must a custom element declare to be notified of attribute changes?
- A CSS variable
- A static observedAttributes getter listing the attributes
- A render() method
- A shadow root only
Answer: A static observedAttributes getter listing the attributes. static get observedAttributes() returns the attribute names to watch; attributeChangedCallback then fires for them.
What are slots used for?
- Network caching
- Encrypting markup
- Sorting lists
- Letting users inject content into specific areas of a component
Answer: Letting users inject content into specific areas of a component. Slots let users inject content into named areas of a component, similar to React's children but with named targets.
In the global store (Redux/Vuex pattern), how do components react to state changes?
- They poll the DOM
- They subscribe with a callback that runs when notify() is called
- They reload the page
- They use setInterval
Answer: They subscribe with a callback that runs when notify() is called. Components call store.subscribe(callback); on set(), notify() runs every subscriber with the new state.
What does an event bus enable?
- Direct parent-only communication
- Faster CSS
- Any component to emit or listen to events without direct references (loose coupling)
- Automatic persistence
Answer: Any component to emit or listen to events without direct references (loose coupling). An event bus lets unrelated components emit and listen to events without holding direct references to each other.