To make a React component usable as a browser-native tag such as <profile-card>, keep the React component and wrap it in a custom element. The wrapper registers a hyphenated tag, creates a React root when connected, passes data through a defined interface, and unmounts the root when disconnected. This is an integration boundary—not a conversion of JSX into a different component implementation.
What the wrapper does
A Web Component is built from browser features, including custom elements and, optionally, Shadow DOM. For a React-backed custom element, the browser owns the outer tag while React renders the existing component into a child mount node. MDN describes an autonomous custom element as a class extending HTMLElement and registered with customElements.define() (MDN: Using custom elements).
The wrapper is responsible for translating between the browser-facing interface and the React component’s props and callbacks. Define that interface deliberately: markup attributes are strings, whereas JavaScript properties can carry objects, arrays, and other values.
Build a basic React-backed custom element
- Choose an autonomous custom-element name. Use a lowercase name containing a hyphen, such as
profile-card. - Extend
HTMLElementand register the element. CallcustomElements.define('profile-card', ProfileCardElement)once the class is defined. - Create a mount node and React root on connection. In
connectedCallback(), add a child node, callcreateRoot(mount), then render the existing component. - Pass inputs and expose events. Map supported attributes or properties to React props, and dispatch documented
CustomEvents for events host pages need to handle. - Unmount on disconnection. Call
root.unmount()indisconnectedCallback()to let React detach from the container.
This illustrative JSX sketch shows the core pattern, not a production-ready implementation:
#1 Best Overall
import React from 'react';
import { createRoot } from 'react-dom/client';
import ProfileCard from './ProfileCard.js';
class ProfileCardElement extends HTMLElement {
connectedCallback() {
if (this._root) return;
const mount = document.createElement('div');
this.append(mount);
this._root = createRoot(mount);
this._render();
}
disconnectedCallback() {
this._root?.unmount();
this._root = undefined;
}
set profile(value) {
this._profile = value;
this._render();
}
get profile() {
return this._profile;
}
_render() {
if (!this._root) return;
this._root.render(
<ProfileCard
profile={this._profile}
onSelect={(event) => {
this.dispatchEvent(new CustomEvent('profile-select', {
detail: event,
bubbles: true,
composed: true,
}));
}}
/>
);
}
}
customElements.define('profile-card', ProfileCardElement);
React’s createRoot reference explains that a root manages the DOM inside its container and that root.unmount() detaches React from it. The sketch renders only after connection; it does not cover every case a real wrapper must handle, including attributes, inputs assigned before connection, and repeated connection after disconnection.
Choose attributes or properties for inputs
Use attributes for string configuration
Attributes suit values that should be visible in HTML, such as an identifier, label, or display mode. They are strings, so parse and validate them where appropriate. To respond to changes, declare the observed attribute names with static get observedAttributes() and implement attributeChangedCallback(); custom-element lifecycle callbacks are described in MDN’s custom-element guide.
Rank #2
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
Use properties for rich JavaScript values
Assign objects and arrays as properties on the element rather than expecting them to work as HTML attributes. For example, a consumer can set element.profile = profileData. A robust wrapper should account for a property being set before connectedCallback() runs, then render the latest value when the React root is created.
React version matters when another React application renders the custom element. The React documentation for custom elements describes JSX values passed as attributes by default, and the React 19 release notes describe improved handling: on the client, React assigns a prop as a property when the custom-element instance has a matching property, and otherwise uses an attribute. Server rendering follows separate rules: primitive values such as strings, numbers, and true render as attributes, while non-primitives and false are omitted. Declare the properties the element supports and verify behavior against the React version and rendering path used by the host.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rank #3
Expose events across the boundary
Use CustomEvent when a non-React consumer needs to respond to something the React component does. Specify the event name, the payload in event.detail, and whether it bubbles or crosses a shadow boundary. In the example, profile-select is a lowercase, hyphenated event with both bubbles and composed enabled; choose those options to match the intended consumers rather than copying them automatically.
React hosts can listen to custom-element events using an on-prefixed JSX prop. Event names are case-sensitive, so preserve the exact capitalization and hyphens defined by the element. See React’s custom-element documentation for the host-side behavior.
Rank #4
- Brand: Wiley
- Set of 2 Volumes
- A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers
Handle lifecycle, reconnection, and server rendering
connectedCallback() is the point to initialize rendering, and disconnectedCallback() is the point to unmount the React root. If an element can be removed and later reinserted, ensure that reconnection creates a fresh root and renders its current inputs without creating duplicate mount nodes. Also decide whether the wrapper owns only a dedicated child container or other DOM within the custom element; React takes control of the root container it renders into.
createRoot() is for client rendering. If the integration receives server-rendered React markup that should be reused, React provides hydrateRoot() for hydration instead of replacing that markup. The correct approach depends on how the custom element is delivered and whether its markup is server-rendered; confirm the deployment’s SSR requirements before choosing a root API. See the createRoot reference and React’s hydrateRoot reference.
Recommended Free Tools
Best Value
Decide whether to use Shadow DOM
Shadow DOM is optional. Rendering into a normal child node keeps the content in the light DOM; using a shadow root can provide DOM and style encapsulation. Choose it only if encapsulation is part of the component’s contract, since it changes how styles and consumers interact with the rendered content. MDN’s Web Components overview describes Shadow DOM as one of the technologies that can be combined with custom elements.
For broad browser compatibility, prefer an autonomous element extending HTMLElement. Customized built-in elements extend an existing native element, but MDN notes that Safari does not plan to support them (MDN: Using custom elements).
Quick Recap
Production readiness checklist
- Define which inputs are HTML attributes and which are JavaScript properties.
- Handle property assignments made before connection, supported attribute changes, and removal followed by reconnection.
- Document each emitted event’s exact name, payload shape, and bubbling behavior.
- Choose light DOM or Shadow DOM based on the required style and DOM boundaries.
- Use
createRoot()for client rendering orhydrateRoot()when reusing server-rendered React markup. - Verify the host React version, browsers, bundler setup, and SSR behavior for the deployment.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




