Framework usage

<o-embed> is a standard Web Component and works with any framework. This page covers framework-specific setup, TypeScript configuration, and SSR considerations.

For reactive URL binding patterns (dynamic updates, preview-as-you-type), see Reactive URL binding.

TypeScript

Importing @social-embed/wc automatically augments global types:

// These declarations ship with the package (OEmbedElement.ts):
declare global {
interface HTMLElementTagNameMap {
"o-embed": OEmbedElement;
}
namespace JSX {
interface IntrinsicElements {
"o-embed": Partial<OEmbedElement>;
}
}
}

This means document.querySelector("o-embed") returns OEmbedElement and JSX/TSX usage is type-safe out of the box.

React

React 19+

React 19 natively supports custom element properties. Direct attribute binding works:

import "@social-embed/wc";
function MediaEmbed({ url }) {
return <o-embed url={url}></o-embed>;
}

React 18

React 18 does not pass unknown props to custom elements as properties. Use a ref to set properties imperatively:

import { useEffect, useRef } from "react";
import "@social-embed/wc";
function MediaEmbed({ url }) {
const ref = useRef(null);
useEffect(() => {
if (ref.current) {
ref.current.url = url;
}
}, [url]);
return <o-embed ref={ref}></o-embed>;
}

React TypeScript augmentation

The package ships a generic JSX.IntrinsicElements declaration using Partial<OEmbedElement>. If your project uses @types/react and you see type errors, add a React-specific augmentation:

// react-app-env.d.ts or global.d.ts
import type { OEmbedElement } from "@social-embed/wc";
declare global {
namespace JSX {
interface IntrinsicElements {
"o-embed": React.DetailedHTMLProps<
React.HTMLAttributes<OEmbedElement> & { url?: string },
OEmbedElement
>;
}
}
}

Vue 3

Vue handles custom elements natively with property binding:

<template>
<o-embed :url="mediaUrl"></o-embed>
</template>

To suppress the "Failed to resolve component: o-embed" warning, configure the Vue compiler to recognize the custom element:

vite.config.ts
import vue from "@vitejs/plugin-vue";
export default {
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag === "o-embed",
},
},
}),
],
};
main.ts
const app = createApp(App);
app.config.compilerOptions.isCustomElement = (tag) => tag === "o-embed";

Svelte 5

Svelte passes attributes directly to custom elements:

<script>
import "@social-embed/wc";
let { url } = $props();
</script>
<o-embed url={url}></o-embed>

Angular

Add CUSTOM_ELEMENTS_SCHEMA to your module or standalone component:

import { CUSTOM_ELEMENTS_SCHEMA, Component } from "@angular/core";
import "@social-embed/wc";
@Component({
selector: "app-embed",
template: `<o-embed [attr.url]="mediaUrl"></o-embed>`,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class EmbedComponent {
mediaUrl = "https://youtu.be/Bd8_vO5zrjo";
}

SSR (Next.js, Nuxt, SvelteKit, Astro)

<o-embed> is a Web Component that registers itself via customElements.define(). This must happen on the client side — the element will not render during server-side rendering.

The HTML tag is safe to include in SSR output. The browser treats <o-embed> as an unknown element until the JavaScript loads and upgrades it. Use slot content as a fallback:

<o-embed url="https://youtu.be/Bd8_vO5zrjo">
<a href="https://youtu.be/Bd8_vO5zrjo">Watch on YouTube</a>
</o-embed>

Dynamic import patterns

Import @social-embed/wc on the client side only:

"use client";
import { useEffect } from "react";
export function MediaEmbed({ url }: { url: string }) {
useEffect(() => {
import("@social-embed/wc");
}, []);
return <o-embed url={url}></o-embed>;
}
<script setup>
onMounted(() => {
import("@social-embed/wc");
});
</script>
<template>
<o-embed :url="mediaUrl"></o-embed>
</template>
<script>
import { onMount } from "svelte";
let { url } = $props();
onMount(() => {
import("@social-embed/wc");
});
</script>
<o-embed {url}></o-embed>
<!-- No dynamic import needed — use a script tag -->
<script>
import "@social-embed/wc";
</script>
<o-embed url="https://youtu.be/Bd8_vO5zrjo">
<a href="https://youtu.be/Bd8_vO5zrjo">Watch on YouTube</a>
</o-embed>