Framework Integration
This lesson explains how to use Web Components inside modern frontend frameworks, including property binding, event handling, wrappers, and the patterns that keep shared UI reliable across apps.
Why Framework Integration Matters
Web Components are browser-native, so they can be used in plain
HTML and inside React, Vue, Angular, Astro, and Svelte. Framework
integration is about making that usage predictable: passing data
in, listening for events out, and avoiding common binding
mismatches.
Most production setups use Web Components for shared design system
primitives while frameworks handle routing, state, and page-level
composition.
| Integration Topic | What to Handle |
| Attributes | String values set in markup |
| Properties | JavaScript values set on the element instance |
| Events | Custom events bubbling from shadow trees |
| Children / slots | Projected content from framework templates |
| Registration | Import component definitions before use |
| Wrappers | Framework components that adapt the API |
Basic Framework Usage
Once a custom element is defined, frameworks can render it like
any other HTML tag.
<user-badge status="active" label="Alex"></user-badge>
import "./user-badge.js";
// React, Vue, Angular, Astro, or Svelte can now render:
// <user-badge status="active" label="Alex"></user-badge>
The key requirement is importing the component definition before
the framework renders the tag. Without registration, the browser
treats it as an unknown element.
Attributes vs Properties in Frameworks
Frameworks often set attributes in JSX or templates, but many
component APIs expose JavaScript properties for non-string data.
class DataTable extends HTMLElement {
set rows(value) {
this._rows = value;
this.render();
}
get rows() {
return this._rows;
}
}
| Type | Good For | Framework Note |
| Attributes | Strings, booleans, enums | Usually mapped automatically in templates |
| Properties | Objects, arrays, functions | Often need refs or wrapper components |
Design your public API with this split in mind. Strings belong in
attributes; complex values belong in properties.
Using Web Components in React
React can render custom elements directly, but property binding and
custom events sometimes need refs or thin wrapper components.
import { useEffect, useRef } from "react";
import "./status-badge.js";
function StatusBadge({ status, children }) {
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
ref.current.status = status;
}
}, [status]);
return (
<status-badge ref={ref}>
{children}
</status-badge>
);
}
Use attributes for simple string props. Use a ref when you need to
assign objects, arrays, or non-string property values.
Listening for Custom Events in React
function SearchField() {
const ref = useRef(null);
useEffect(() => {
const element = ref.current;
if (!element) return;
const handleSearch = (event) => {
console.log(event.detail.query);
};
element.addEventListener("search", handleSearch);
return () =>
element.removeEventListener("search", handleSearch);
}, []);
return <app-search ref={ref}></app-search>;
}
Custom events from Web Components should use
composed: true when they originate inside Shadow DOM
so parent frameworks can hear them.
Using Web Components in Vue
<script setup>
import "./rating-stars.js";
</script>
<template>
<rating-stars
:value="rating"
@change="rating = $event.detail.value"
></rating-stars>
</template>
Vue generally works well with custom elements. Bind attributes
with v-bind and listen for native-style custom
events with @event-name.
Using Web Components in Angular
import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}
<app-alert
type="success"
(dismissed)="onDismiss()"
>
Profile updated.
</app-alert>
Angular requires CUSTOM_ELEMENTS_SCHEMA (or
NO_ERRORS_SCHEMA in limited cases) so templates can
use unknown custom tags. Import the component script in your app
bootstrap or feature module.
Using Web Components in Astro
---
import "../components/app-banner.js";
---
<app-banner variant="info">
Welcome to the docs.
</app-banner>
Astro is a strong fit for Web Components because it renders real
HTML on the server and hydrates only what is needed. Import the
component definition in the page frontmatter, then use the tag in
the template.
Using Web Components in Svelte
<script>
import "./toast-message.js";
let message = "Saved";
</script>
<toast-message message={message}></toast-message>
Svelte can render custom elements like normal HTML. For complex
property APIs, bind through an action or a small wrapper
component.
Direct Usage vs Wrapper Components
| Approach | When to Use |
| Direct custom element | Simple string attributes and slots |
| Framework wrapper | Complex props, typed APIs, SSR glue |
| Codegen wrappers | Large design systems with many components |
Start with direct usage. Add wrappers only when a framework needs
help synchronizing properties, events, or TypeScript types.
Designing a Framework-Friendly Event API
this.dispatchEvent(
new CustomEvent("change", {
detail: { value: this.value },
bubbles: true,
composed: true
})
);
Framework-friendly components emit DOM events with clear
detail payloads. Use predictable event names such as
change, select, or
close instead of framework-specific naming.
Slots and Framework Children
<app-card>
<h2 slot="title">Billing</h2>
<p>Manage your subscription.</p>
</app-card>
Frameworks can pass children into slotted regions the same way
plain HTML does. Named slots work with the slot
attribute on child elements.
Registering Components in Apps
// design-system/index.js
import "./button.js";
import "./dialog.js";
import "./text-field.js";
export {
defineAll() {
// side-effect imports above already register tags
}
}
Centralize registration in one entry file. Apps import that entry
once during bootstrap so every page and framework route has
access to the same custom elements.
TypeScript and JSX Support
declare global {
namespace JSX {
interface IntrinsicElements {
"status-badge": React.DetailedHTMLProps
<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
status?: "idle" | "active" | "error";
};
}
}
}
TypeScript projects often extend JSX intrinsic element types so
custom tags get autocomplete and compile-time checks in React
apps.
When Framework Integration Pays Off
- You share a design system across React, Vue, and Angular apps.
- You want encapsulated UI without rewriting it per framework.
- You embed the same widgets in CMS pages and SPA shells.
- You build micro frontends with different framework choices.
- You need long-lived primitives that outlive one app rewrite.
- You publish a component library consumed by external teams.
Framework Integration Best Practices
- Keep the Web Component API framework-neutral.
- Use attributes for strings and properties for complex values.
- Emit bubbling, composed custom events with clear detail objects.
- Register components once in a shared entry module.
- Document attributes, properties, slots, and events for each tag.
- Add framework wrappers only when direct usage is awkward.
- Test components in plain HTML before testing in every framework.
Common Integration Mistakes
- Passing objects through attributes instead of properties.
- Forgetting to import the component definition in the app.
- Using non-composed events that never reach framework handlers.
- Assuming React attribute binding sets JavaScript properties.
- Skipping
CUSTOM_ELEMENTS_SCHEMA in Angular modules. - Creating a separate wrapper for every tiny prop mismatch.
- Mixing framework state logic inside the Web Component itself.
Key Takeaways
- Web Components work in all major frontend frameworks.
- Attributes and properties solve different integration needs.
- Custom events are the main output channel for frameworks.
- Direct usage is often enough for simple shared primitives.
- Wrappers help with complex binding and TypeScript ergonomics.
- A neutral component API keeps reuse high across teams.
Pro Tip
Design Web Components like a small public API: string attributes
in, DOM events out, slots for content. Frameworks become consumers
of that API instead of owners of the implementation.