embed-card

Supported platforms

Which URLs map to which built-in embed, how the default card renders them, and how to theme or replace behavior.

Supported platforms

The package ships a fixed ordered list of built-in providers (defaultProviders). For each pasted URL, the first provider whose match succeeds gets a chance to resolve it; if every provider returns null for that URL, the card falls back to a generic link preview. Invalid or unparseable input uses a small invalid URL state instead.

Use them the same everywhere: <EmbedCard url="…" />, <embed-card url="…">, or resolveEmbed(url) with the default registry.

PlatformTypical hostsCard behavior
YouTubeyoutube.com, youtu.be, m.youtube.comResponsive iframe embed (youtube.com/embed/…)
X / Twitterx.com, twitter.comIframe Tweet widget (platform.twitter.com/embed/Tweet.html)
Redditreddit.com, old.reddit.comClient preview (browser fetch to the public .json thread API, not an iframe)
Google Mapsgoogle.com/maps…Iframe map (output=embed added when resolving)
Vimeovimeo.com, player.vimeo.comIframe player
TikToktiktok.com, www, m, vm.tiktok.comIframe embed (tiktok.com/embed/v2/…); client oEmbed for vm.tiktok.com short links
Instagraminstagram.com, www, mIframe embed (instagram.com/…/embed/) for /p/, /reel/, /tv/ permalinks
Link previewAny other valid https:// URLIn-card link surface with title and hostname
Invalid inputNon-URL or empty stringInline message prompting for a full URL

Try live samples in the Playground.


YouTube

Match: youtube.com, www.youtube.com, m.youtube.com, youtu.be.

Resolve: Needs a recognizable video id — for example ?v= on watch URLs, /embed/, /shorts/, /live/, or the path segment on youtu.be/…. If the URL is on YouTube but no id is found, resolution fails and the next providers run; usually you end up on the link preview row.

Customize: Same as other iframe providers: theme (accent, radius, shadow, font, appearance) wraps the iframe surface. Provider accent defaults to YouTube red unless you override accentColor.

import { EmbedCard } from "embed-card"

<EmbedCard url="https://www.youtube.com/watch?v=dQw4w9WgXcQ" />

X and Twitter

Match: x.com, twitter.com, www.twitter.com.

Resolve: Path must include /status/<numeric id> (a single post). Profile or home URLs do not resolve as embeds.

Customize: The Tweet iframe theme (light / dark) follows the card’s resolved appearance when the URL is resolved (so it tracks theme.appearance and next-themes flows the same as the rest of the card). Other theme fields still apply to the surrounding surface.

<EmbedCard url="https://x.com/1chooo___/status/2028573993972969585" />

Reddit

Match: reddit.com, www.reddit.com, old.reddit.com.

Resolve: Path must contain /comments/ (a post thread). The card loads postUrl + ".json" in the visitor’s browser and renders a compact thread preview (no redditmedia iframe).

Customize: Card chrome uses theme like other providers. For fully custom markup while reusing the same data helpers, see Custom Rendering and Manual Rendering.

<EmbedCard url="https://www.reddit.com/r/github/comments/1j6jga7/i_rebuilt_my_personal_portfolio_using_nextjsits/" />

Google Maps

Match: google.com, www.google.com, or maps.google.com, with a pathname starting with /maps.

Resolve: The resolver copies your URL and sets output=embed for the iframe src. Use normal Maps share links; the card adapts them for embedding.

Customize: Default accent is Maps green; override with theme.accentColor as needed. Aspect ratio is 4 / 3 with a minimum height so small viewports still get a usable map.

<EmbedCard url="https://www.google.com/maps?q=Tokyo+Station&output=embed" />

Vimeo

Match: vimeo.com, www.vimeo.com, player.vimeo.com.

Resolve: Needs a numeric video id in the path (standard Vimeo URLs).

Customize: Same iframe + theme story as YouTube.

<EmbedCard url="https://vimeo.com/76979871" />

TikTok

Match: tiktok.com, www.tiktok.com, m.tiktok.com, and vm.tiktok.com (short links).

Resolve: Canonical URLs must include /@username/video/<numeric id> — the card uses TikTok’s https://www.tiktok.com/embed/v2/{id} iframe. vm.tiktok.com/{code} has no id in the path, so the card uses a tiktok_client renderer: the visitor’s browser calls TikTok’s public oEmbed endpoint (https://www.tiktok.com/oembed?url=…), reads the video id from the response, then loads the same embed iframe. Profile, tag, or home URLs on TikTok hosts do not resolve as embeds (you get the link preview fallback).

Layout: The built-in TikTok embed uses a 9:16 aspect ratio with max width and max height on IframeEmbedRenderer. Height is clamped with min(<px>, 90vmin, 65dvh) so short viewports cap the card before vmin alone. The browser keeps proportions when width or height hits a limit.

Chrome: TikTok resolves with embedChrome: "flush" on the iframe payload (and the same flush outer surface for tiktok_client): no package border, shadow, or gradient halo so TikTok’s own player is not double-framed.

Customize: For TikTok, the outer figure stays flush as above; theme (appearance, radius, font, etc.) still applies where relevant. TikTok’s inside-iframe UI is controlled by TikTok; there is no documented iframe flag comparable to X’s Tweet theme parameter.

import { EmbedCard } from "embed-card"

<EmbedCard url="https://www.tiktok.com/@scout2015/video/6718335390845095173" />

Instagram

Match: instagram.com, www.instagram.com, m.instagram.com.

Resolve: Public permalinks with /p/{shortcode}, /reel/{shortcode}, or /tv/{shortcode}. Profile, story, or home URLs do not resolve as embeds.

Customize: The iframe is centered with a max width so the card stays readable on wide layouts; use theme for accent, radius, and appearance like other iframe providers.

<EmbedCard url="https://www.instagram.com/p/DXKYttciGe7/" />

When: No built-in provider both matches and returns a resolved embed (including “known host but wrong path shape”).

Behavior: A link renderer: hostname as title line, short description, and a call-to-action. The built-in fallback descriptor sets a default CTA label on the resolved payload; you can still override the label shown in the UI with the ctaLabel prop on EmbedCard or the cta-label attribute on <embed-card>.

<EmbedCard url="https://example.com/article" ctaLabel="Read article" />

Invalid URL

When: The string is not a valid URL (and the small “add https://” heuristic still cannot parse it), or you called resolveEmbed with { includeFallback: false } and nothing matched.

Behavior: A dedicated invalid renderer with guidance text (not a network fetch).


How to use (all platforms)

  1. Install embed-card and render with a single url string (see Getting Started).
  2. Optional: pass theme for colors, radius, shadow, font, and light / dark / system appearance.
  3. Apps using next-themes: prefer ThemedEmbedCard from embed-card/next-themes so appearance matches the site (Getting Started → Match the site theme).

Web component equivalents: accent-color, radius, shadow, font-family, appearance, and cta-label — see Web Component.


How to customize

GoalWhere to go
Tweak colors, radius, shadow, fonts, light/darkGetting Started → Theme the card, Custom Rendering → Theming reference
Match site dark mode with next-themesGetting Started, Custom Rendering
Override link-preview button textctaLabel / cta-label (React, Web Component)
Add Spotify, internal docs, or other hostsProvider RegistrycreateEmbedProvider + providers={[…]}
Replace card UI entirely, reuse dataManual Rendering, Custom Rendering

Provider order is only changed when you pass your own providers array (prepend custom providers so their match runs first, then spread defaultProviders if you still want the built-ins).