Skip to content

Using in Frameworks

A custom element registered with customElements.define is a standard DOM node. Any framework that outputs HTML can use it. You place it in JSX, a Vue template, or an Angular template the same way you place a <div>.

Because frameworks ultimately produce real DOM nodes, they all interoperate with custom elements at the same level — the browser’s own element API. Properties, attributes, and events work the same way regardless of which framework is rendering the surrounding tree.

In React 18 and earlier, custom elements receive string attributes, not rich object props. Pass primitives (strings, numbers, booleans) as attributes. For complex data (arrays, objects) or for listening to custom events you must use a ref callback:

// React 18 — attach ref to wire up events
import { useRef, useEffect } from 'react';
function App() {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
el.addEventListener('my-event', (e) => console.log(e.detail));
return () => el.removeEventListener('my-event', () => {});
}, []);
return <my-counter ref={ref} label="Score"></my-counter>;
}

React 19 ships full custom element support. Properties and event handlers (prefixed with on) now bind directly without a ref workaround:

// React 19 — direct prop and event binding
function App() {
return (
<my-counter
label="Score"
count={0}
onmy-event={(e) => console.log(e.detail)}
/>
);
}

Vue treats custom elements as first-class citizens. Attributes and properties bind with :prop="value" (property) or attr="value" (attribute). Custom events are listened to with @event-name:

<!-- Vue template -->
<my-counter
:label="label"
:count="score"
@score-changed="handleChange"
/>

Vue automatically differentiates between a property (set on the DOM node) and an attribute (set via setAttribute) based on whether the key exists on the element’s prototype.

Angular supports custom elements via the CUSTOM_ELEMENTS_SCHEMA. Add it to your module’s schemas array to suppress unknown-element errors:

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@NgModule({
declarations: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}

In templates, use [property] for property binding and (event-name) for custom events:

<!-- Angular template -->
<my-counter [label]="title" [count]="score" (score-changed)="onScoreChanged($event)"></my-counter>

The demo below shows a vanilla web component (<demo-badge>) rendered in plain HTML — the same element would work identically when dropped into a React, Vue, or Angular tree because it is just a DOM node.

In React 18, what is the recommended way to listen to a custom event fired by a web component?
What does Vue use to decide whether to set a value as a DOM property or an HTML attribute on a custom element?
Which Angular module token allows you to use unregistered custom element tag names without compile errors?