Skip to content

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.