Working Custom Elements React: A Complete Guide

Master the integration of Web Components with React. Learn to leverage custom elements for reusable, framework-agnostic components within your React applications.

Understanding Custom Elements and Their Role in Modern Web Development

Custom elements are a cornerstone of the Web Components specification, enabling developers to create new HTML tags with fully encapsulated functionality. Unlike React components, which are bound to the React ecosystem, custom elements work natively in any framework--or no framework at all.

The Web Components standard comprises three complementary technologies:

  • Custom Elements for defining new DOM elements
  • Shadow DOM for encapsulation
  • HTML Templates for reusable markup structures

Together, these specifications provide a native browser foundation for component-based development that predates and influences modern framework component models.

Why Use Custom Elements Within React Applications

The decision to use custom elements within React applications typically stems from several compelling use cases:

Design System Distribution: Organizations building design systems that serve multiple applications benefit from custom elements as a distribution format. By building core components as custom elements, teams ensure that every consuming application receives identical behavior regardless of its technology stack. This approach aligns with modern web development best practices for maintaining consistent UI libraries across projects.

Third-Party Integration: Many services provide embedded widgets as custom elements, including payment processors, analytics platforms, and enterprise software connectors. Understanding how to integrate these cleanly within React enables effective use of these services.

Performance Profile: Custom elements use direct DOM manipulation rather than React's virtual DOM diffing, which can provide superior performance for stable, self-contained components such as navigation elements, footer content, or marketing widgets. This performance characteristic makes custom elements particularly valuable for AI-powered applications that require efficient rendering of stable UI components.

Core Challenges When Using Custom Elements with React

Working with custom elements in React involves navigating several architectural differences that can create friction if not properly understood.

Attribute and Property Mapping

One of the most common friction points involves the distinction between HTML attributes and DOM properties. HTML attributes are string values set in markup, while DOM properties are JavaScript values accessible through dot notation.

Custom elements often expect complex data through properties rather than attributes--objects, arrays, or functions that cannot be serialized to strings. Developers must explicitly set properties using ref-based access or wrapper components that handle the translation. This pattern is similar to how developers handle strongly typed polymorphic components in React where type safety and proper data binding are critical.

Event Handling Differences

Custom elements communicate state changes by dispatching standard DOM events that bubble normally. React's synthetic event system wraps native events in its own abstraction layer, which can create confusion when trying to listen to custom element events.

The most straightforward solution involves using the native addEventListener method within a useEffect hook, adding listeners when the custom element mounts and removing it when it unmounts.

Lifecycle Synchronization

Custom elements define their own lifecycle callbacks that operate independently of React's component lifecycle. The connectedCallback and disconnectedCallback map roughly to React's mount and unmount phases, but timing differences can cause issues.

Property initialization presents another synchronization challenge: developers might set properties on a custom element before its definition has loaded, particularly when using lazy loading.

Creating a Basic Custom Element with Shadow DOM
1class MyCustomElement extends HTMLElement {2 static get observedAttributes() {3 return ['value', 'disabled'];4 }5 6 constructor() {7 super();8 const shadow = this.attachShadow({ mode: 'open' });9 shadow.innerHTML = `10 <style>11 :host {12 display: block;13 font-family: system-ui, sans-serif;14 }15 :host([hidden]) {16 display: none;17 }18 .container {19 padding: 16px;20 border: 1px solid #e0e0e0;21 border-radius: 8px;22 }23 </style>24 <div class="container">25 <slot></slot>26 </div>27 `;28 }29 30 get value() {31 return this.getAttribute('value');32 }33 34 set value(val) {35 this.setAttribute('value', val);36 }37 38 attributeChangedCallback(name, oldValue, newValue) {39 if (oldValue !== newValue) {40 // Handle attribute changes41 }42 }43 44 connectedCallback() {45 // Element connected to DOM46 }47 48 disconnectedCallback() {49 // Element disconnected from DOM50 }51}52 53customElements.define('my-custom-element', MyCustomElement);
Custom Element Best Practices

Key principles for building high-quality custom elements that integrate well with React

Create Shadow Root in Constructor

Establish encapsulation before external code can access the element. This ensures exclusive access to implementation details.

Keep Attributes and Properties in Sync

Implement bidirectional reflection for primitive data types. Setting a property should update the attribute and vice versa.

Accept Rich Data as Properties

Objects and arrays should be accepted only through properties, never reflected to attributes. This preserves object references.

Dispatch Events for Internal Activity

Notify the host when internal state changes. Avoid dispatching events in response to host setting properties.

Support Hidden Attribute

Add `:host([hidden]) { display: none }` to respect the standard hidden attribute.

Don't Override Author Settings

Check if global attributes like tabindex or role are already set before applying defaults.

React Integration with Custom Elements
1import { useRef, useEffect, useState } from 'react';2 3function ConfigurableWidget({ initialConfig, widgetTitle }) {4 const elementRef = useRef(null);5 const [status, setStatus] = useState(null);6 7 useEffect(() => {8 const element = elementRef.current;9 if (!element) return;10 11 // Set properties directly (React 19 handles this better)12 if (initialConfig) {13 element.config = initialConfig;14 }15 16 // Handle events from custom element17 const handleStatusChange = (event) => {18 setStatus(event.detail.status);19 };20 element.addEventListener('status-change', handleStatusChange);21 22 // Cleanup23 return () => {24 element.removeEventListener('status-change', handleStatusChange);25 };26 }, [initialConfig]);27 28 return (29 <div className="widget-wrapper">30 <configurable-widget31 ref={elementRef}32 title={widgetTitle}33 className="custom-widget"34 />35 {status && (36 <div className="status-indicator">37 Status: {status}38 </div>39 )}40 </div>41 );42}43 44export default ConfigurableWidget;

Design System Distribution

Build components as custom elements to serve multiple applications across different frameworks. Ensure consistent behavior everywhere.

Third-Party Integration

Many services provide embedded widgets as custom elements. Integrate payment processors, analytics, and enterprise tools seamlessly.

Performance-Critical Components

Navigation headers, footer content, and marketing widgets benefit from direct DOM manipulation and smaller bundle sizes.

React Components vs Custom Elements Comparison
FeatureReact ComponentsCustom Elements
EcosystemReact-specificFramework-agnostic
Browser SupportRequires React runtimeNative browser support
EncapsulationReact-basedShadow DOM-based
ImplementationJavaScript/JSXStandard HTML/JavaScript
PerformanceVirtual DOM optimizationsDirect DOM manipulation
ReusabilityLimited to ReactWorks across frameworks
Bundle SizeIncludes React libraryNo added bundle size
Learning CurveReact-specific patternsStandard web APIs

Frequently Asked Questions

Can I use custom elements with React 18?

Yes, but React 18 requires more manual handling. You'll need to use refs to set properties and addEventListener for events. React 19 provides better native support for custom elements.

Should I build all components as custom elements?

Not necessarily. Custom elements excel for design systems, third-party integration, and performance-critical UI. For complex application state and data flows, React components often provide better developer experience.

How do custom elements affect bundle size?

Custom elements typically have smaller footprints because they don't include a framework runtime. However, this advantage requires discipline--avoiding large dependencies within individual elements.

Can custom elements access React context?

Custom elements don't have direct access to React context. If context is needed, pass it through properties or use a wrapper component that provides data via attributes and properties.

Do custom elements work with React Server Components?

Custom elements are client-side only. They cannot be rendered on the server because they depend on browser DOM APIs like customElements.define and attachShadow.

Ready to Build Modern Web Applications?

Our team specializes in building high-performance web applications using React, Web Components, and modern best practices.