agentsclimarketplace

React embeds

Skill jaballer/react-claude-skills/react-embeds

21 composable Claude Code skills for senior React + UX engineering — component design, a11y, performance, forms, routing, marketing & docs patterns, testing, and interview craft.

Install
npx -y skills add jaballer/react-claude-skills --skill react-embeds

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Provider-specific patterns for embedding third-party media in React — YouTube (incl. nocookie), SoundCloud (share URL vs. w.soundcloud.com player), Vimeo, Spotify, Bandcamp, Apple Podcasts, Twitter/X, Mastodon, Bluesky, CodePen, Figma, GitHub Gist. Each provider's iframable URL pattern, X-Frame-Options caveats, sandbox flags, aspect ratio, lazy loading, a11y (title attribute), responsive containers, and privacy-respecting variants (youtube-nocookie, dnt params). Use whenever rendering an iframe to a third-party platform — blog content, CMS embed URLs, social posts, media players. Triggers on "embed", "iframe", "YouTube", "SoundCloud", "Vimeo", "Spotify", "Bandcamp", "oEmbed", "X-Frame-Options", "player URL", "youtube-nocookie", "Twitter embed", "Mastodon", "CodePen", "Figma embed", "GitHub gist".

SKILL.md

13.4 KB, as published. Nobody here has run it

react-embeds

Every CMS-fed React app eventually has to render <iframe src={embed_url}>. Half the time it works; half the time the host blocks framing or strips features. This skill maps each major provider to the URL shape that actually embeds, plus the iframe attributes that matter.

The general pattern

function Embed({ src, title, aspect = "video" }: EmbedProps) {
  return (
    <div
      className={
        aspect === "video"
          ? "aspect-video w-full overflow-hidden rounded-lg border border-border bg-black"
          : "w-full overflow-hidden rounded-lg border border-border"
      }
    >
      <iframe
        src={src}
        title={title}                    // a11y: REQUIRED, describes the embed
        loading="lazy"                   // perf: don't load until in viewport
        allow="autoplay; clipboard-write; encrypted-media; picture-in-picture"
        allowFullScreen
        referrerPolicy="strict-origin-when-cross-origin"
        className="h-full w-full border-0"
      />
    </div>
  );
}

Always include a title. Screen readers read it; without one, the embed announces as "frame" with no context.

Always loading="lazy". Embeds are heavy and often below the fold.

allow= is provider-specific — see the table below.

Provider matrix — what URL actually iframes

ProviderWhat CMSes often storeWhat you must iframeAspect
YouTubehttps://youtube.com/watch?v=ID or https://youtu.be/IDhttps://www.youtube-nocookie.com/embed/{ID}video
Vimeohttps://vimeo.com/IDhttps://player.vimeo.com/video/{ID}video
SoundCloudhttps://soundcloud.com/user/track (share URL — blocked from iframes)https://w.soundcloud.com/player/?url={URL_ENCODED_SHARE}&color=...audio (160px)
Spotifyhttps://open.spotify.com/track/IDhttps://open.spotify.com/embed/track/{ID} (also /album/, /playlist/, /episode/)audio (152px) or square
BandcampAlbum/track page URLBandcamp's site provides the full <iframe> HTML; no derivable transform — store embed_htmlsquare
Apple Podcastshttps://podcasts.apple.com/.../id{ID}https://embed.podcasts.apple.com/.../id{ID}audio (175px)
Twitter/Xhttps://twitter.com/user/status/IDNo reliable cross-origin iframe — use react-tweet (static SSR)n/a
MastodonStatus URL{instance}/embed/{ID} works on most instancesdynamic
BlueskyPost URLhttps://embed.bsky.app/static/post.html?uri={AT_URI}dynamic
CodePenhttps://codepen.io/user/pen/IDhttps://codepen.io/{user}/embed/{ID}?default-tab=resultvideo-ish
FigmaFigma file URLhttps://www.figma.com/embed?embed_host=share&url={URL_ENCODED}video
GitHub Gisthttps://gist.github.com/user/IDUse a <script> tag, not iframe (Gist embed is JS-based)dynamic

Key rule: never iframe a share URL

The most common bug, hit on KrateCMS: a CMS stores the user-friendly share URL (e.g. https://soundcloud.com/user/track) in embed_url. That URL serves an HTML page with X-Frame-Options: sameorigin, which blocks iframe embedding. The fix is always provider-specific URL transformation.

YouTube

function youtubeIdFromUrl(url: string): string | null {
  try {
    const u = new URL(url);
    if (u.hostname.includes("youtu.be")) return u.pathname.slice(1) || null;
    if (u.searchParams.get("v")) return u.searchParams.get("v");
    const match = u.pathname.match(/\/embed\/([^/?#]+)/);
    return match ? match[1] : null;
  } catch { return null; }
}

function YouTubeEmbed({ url, title }: { url: string; title: string }) {
  const id = youtubeIdFromUrl(url);
  if (!id) return null;
  return (
    <div className="aspect-video overflow-hidden rounded-lg bg-black">
      <iframe
        src={`https://www.youtube-nocookie.com/embed/${id}`}
        title={title}
        loading="lazy"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
        allowFullScreen
        referrerPolicy="strict-origin-when-cross-origin"
        className="h-full w-full border-0"
      />
    </div>
  );
}

Why youtube-nocookie.com: same player, but doesn't set tracking cookies until the user interacts. Default for any privacy-respecting site. Drop-in replacement for youtube.com/embed/.

Useful query params on the embed URL:

  • ?rel=0 — don't show related videos at end
  • ?modestbranding=1 — minimize YouTube branding (limited effect)
  • ?start={seconds} — start at a timestamp
  • ?cc_load_policy=1 — captions on by default

SoundCloud

The biggest gotcha. SoundCloud's public share URL is not iframable. Use the player URL:

function soundcloudPlayerUrl(shareUrl: string): string {
  const params = new URLSearchParams({
    url: shareUrl,                      // the original share URL goes here, encoded
    color: "#aa3bff",                   // accent color (matches your brand)
    auto_play: "false",
    hide_related: "true",
    show_comments: "false",
    show_user: "true",
    show_reposts: "false",
    show_teaser: "false",
    visual: "false",                    // false = compact waveform; true = full art
  });
  return `https://w.soundcloud.com/player/?${params.toString()}`;
}

Iframe height matters:

  • visual: "false" (waveform): 160px height
  • visual: "true" (full art): 300–450px height (square-ish)
<iframe
  src={soundcloudPlayerUrl(shareUrl)}
  title={`SoundCloud player for ${trackTitle}`}
  loading="lazy"
  allow="autoplay"
  className="block h-40 w-full border-0"
/>

Detection — when the CMS stores embed_provider separately:

const isSoundCloud =
  post.embed_provider?.toLowerCase() === "soundcloud" ||
  post.embed_url?.includes("soundcloud.com");

Vimeo

function vimeoIdFromUrl(url: string): string | null {
  const match = url.match(/vimeo\.com\/(?:video\/)?(\d+)/);
  return match ? match[1] : null;
}
<iframe
  src={`https://player.vimeo.com/video/${id}?dnt=1`}  // dnt=1 disables tracking
  title={title}
  loading="lazy"
  allow="autoplay; fullscreen; picture-in-picture; clipboard-write"
  allowFullScreen
  className="h-full w-full border-0"
/>

?dnt=1 (do-not-track) is Vimeo's privacy flag — disables analytics. Pair with no-cookie YouTube for a consistent privacy stance.

Spotify

Spotify has multiple embed types (track / album / playlist / episode / show). The pattern is the same — insert /embed after the host:

function spotifyEmbedUrl(url: string): string | null {
  // https://open.spotify.com/track/ID  ->  https://open.spotify.com/embed/track/ID
  try {
    const u = new URL(url);
    if (u.hostname !== "open.spotify.com") return null;
    u.pathname = u.pathname.replace(/^\/(track|album|playlist|episode|show|artist)\//, "/embed/$1/");
    return u.toString();
  } catch { return null; }
}

Heights:

  • Track: 80px (compact) or 152px (full)
  • Album/playlist: 352px (compact) or 380–680px depending on content
  • Podcast episode: 232px
<iframe
  src={spotifyEmbedUrl(url)!}
  title={title}
  loading="lazy"
  allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
  className="h-[152px] w-full rounded-lg border-0"
/>

Twitter / X — don't iframe, use react-tweet

Twitter's platform.twitter.com/widgets.js script-based embeds are heavy, slow, and require third-party JS. The modern alternative is react-tweet (by Vercel) — SSR a static-rendered tweet that looks identical:

npm install react-tweet
import { Tweet } from "react-tweet";

<Tweet id="1234567890" />

Zero third-party JS at runtime. Works in RSC. Pulls tweet data via API at render time. For Mastodon/Bluesky, the official embed iframes work fine (see matrix above).

Unknown providers — fallback with a link

For providers you don't have a transform for, try the iframe but always provide an "Open in new tab" fallback in case the host blocks framing:

<figure>
  <div className="overflow-hidden rounded-lg border border-border">
    <iframe
      src={embed_url}
      title={`${provider} content for ${title}`}
      loading="lazy"
      allow="autoplay; clipboard-write; encrypted-media"
      className="h-40 w-full border-0"
    />
  </div>
  <figcaption className="mt-2 text-xs text-fg">
    Can't see the embed?{" "}
    <a href={embed_url} target="_blank" rel="noopener noreferrer" className="underline">
      Open on {provider}
    </a>
    .
  </figcaption>
</figure>

Responsive aspect ratios

Modern Tailwind has aspect-video (16:9) and aspect-square. For audio embeds (fixed pixel height), use a wrapper that lets the iframe overflow on small screens:

// Video — 16:9 responsive
<div className="aspect-video w-full">
  <iframe className="h-full w-full" ... />
</div>

// Audio — fixed pixel height, full width
<iframe className="h-40 w-full" ... />

// Custom ratio — Tailwind v4 supports arbitrary aspect values
<div className="aspect-[4/5]"> ... </div>

Sandbox flag — when to add it

sandbox is a security tightening for iframes from less-trusted sources:

<iframe
  sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
  ...
/>

Use it for:

  • Unknown / dynamic embed URLs from user-submitted content
  • CodePen-style code execution embeds

Don't use it for major media providers (YouTube, Vimeo, Spotify) — the player needs many permissions you'd have to enumerate anyway, and the providers' players are well-vetted.

Performance — defer below-the-fold embeds

loading="lazy" is good. Even better for heavy embeds (multiple per page) — render a placeholder until the user scrolls or clicks:

function YouTubeFacade({ id, title }: { id: string; title: string }) {
  const [load, setLoad] = useState(false);

  if (load) return <YouTubeEmbed url={`https://youtu.be/${id}`} title={title} />;

  return (
    <button
      type="button"
      onClick={() => setLoad(true)}
      className="relative aspect-video w-full overflow-hidden rounded-lg"
      aria-label={`Play ${title}`}
    >
      <img
        src={`https://i.ytimg.com/vi/${id}/hqdefault.jpg`}
        alt=""
        loading="lazy"
        className="h-full w-full object-cover"
      />
      <span aria-hidden="true" className="absolute inset-0 flex items-center justify-center">
        <span className="rounded-full bg-black/70 px-4 py-3 text-white">▶</span>
      </span>
    </button>
  );
}

This pattern (the "facade") cuts initial bundle weight by ~500KB per YouTube embed. The trick: render an <img> with the thumbnail, swap in the real iframe on click. Lighthouse score loves it.

@lite-youtube/lite-youtube is the same idea as a web component.

A11y checklist

  • title attribute is present and describes the content
  • Aspect ratio container prevents CLS
  • If the embed is decorative, surround with <figure> + <figcaption> describing what it is
  • If autoplay is enabled, audio is muted by default (provider param: YouTube mute=1, Vimeo muted=1)
  • If the embed fails to load (X-Frame-Options blocked), a fallback link is shown
  • Embed loads after critical content (lazy attribute, or facade pattern)

What NOT to do

Don'tWhy
<iframe src={post.embed_url}> with no transformationHalf the providers' share URLs aren't iframable.
Render embeds eagerly above the foldEmbeds are heavy. Lazy-load or use a facade.
Skip title because "the embed has its own UI"Screen readers can't see the embed UI.
Set sandbox on major providersTheir players need more permissions than you want to enumerate.
Inject Twitter's widgets.jsUse react-tweet instead.
Iframe Bandcamp by deriving the URLBandcamp's embed code is unique per release. Store the full <iframe> HTML (or embed_html) in the CMS.
Forget referrerPolicy="strict-origin-when-cross-origin"Default leaks the full referring URL to the embed host.

Authoritative references

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.