Embed

Third-party service embed component. Supports booking (Calendly, Cal.com), payments (Stripe, Gumroad, Ko-fi), forms (Typeform, Tally, Jotform), newsletters (Mailchimp, ConvertKit, Beehiiv), audio (Spotify, SoundCloud, Apple Podcasts), social (Instagram, Twitter/X, TikTok), chat widgets (Crisp, Intercom, Tidio), Google Calendar, or custom HTML/iframe/script code.

Required Props: id

Structure

All styling goes in props.className as a single Tailwind utility string. Non-class props (text, src, alt, etc.) stay on their own keys. root.animation for scroll effects.

Properties

PropertyTypeDescriptionExamples
servicestring
custom | calendly | cal | stripe | gumroad | kofi | typeform | tally | jotform | mailchimp | convertkit | beehiiv | spotify | soundcloud | podcast | instagram | twitter | tiktok | crisp | intercom | tidio | google-calendar
The embed service type. Determines how 'url' is processed into embed code.
Default: "custom"
-
urlstring
The service URL, link, or ID. For URL-based services (Calendly, Cal.com, Spotify, etc.), paste the full URL. For chat widgets (Crisp, Intercom, Tidio), paste the widget/app ID. For code-paste services (Stripe, Mailchimp, ConvertKit, Beehiiv), paste the raw embed code here.
-
codestring
Raw HTML/JavaScript embed code. Used when service is 'custom', or as an override for service-generated code. Replaces the deprecated 'videoId' prop.
-
videoIdstring
DEPRECATED — use 'code' instead. Kept for backward compatibility with existing sites.
-
titlestring
Accessibility title for the embedded content (used as aria-label)
-
headCodestring
Raw HTML rendered into <head> during SSR. Parsed into real React elements so <script> tags execute at HTML-parse time (unlike `code` which uses innerHTML and silently drops scripts). Use for widget init snippets (Cal.com popup, Intercom boot, Crisp, etc.). Identical snippets across multiple Embeds on the same page are deduped by content hash. Self-contained blocks: drop a hidden Embed with headCode + any buttons that call the widget's API via props.handlers.
-
footCodestring
Raw HTML rendered inline at the Embed's position in the body. Scripts here execute at HTML-parse time in the body (good for widgets that require the surrounding DOM to exist before init). For true end-of-body placement use ROOT.props.footer instead.
-
runInEditorboolean
When true, headCode/footCode also emit in editor mode. Default false — prevents popups, analytics, and chat widgets from firing while designing.
Default: false
-
canDeleteboolean
-
Default: true
-
canEditNameboolean
-
Default: true
-
customobject
-
-
classNamestring
Tailwind utility classes string. Mobile-first: unprefixed = base, md: = 768px+, sm: = 640px+, lg: = 1024px+. Includes layout, spacing, surface design, and typography.
flex flex-col gap-space-sm py-space-lg px-container-x md:flex-rowbg-primary text-primary-content rounded-box py-3.5 px-6 font-semibold
urlTargetstring
Rarely used; embeds are usually inline
-
rolestring
-
-
aria-labelstring
-
-
aria-hiddenstring
-
-
aria-describedbystring
-
-
aria-livestring
polite | assertive | off
-
-
rootobject
-
-

Examples

{
  "service": "calendly",
  "url": "https://calendly.com/your-name/30min",
  "title": "Book a meeting",
  "className": "w-full"
}
{
  "service": "spotify",
  "url": "https://open.spotify.com/track/4cOdK2wGLETKBW3PvgPWqT",
  "title": "Now playing",
  "className": "w-full"
}
{
  "service": "stripe",
  "url": "<stripe-buy-button buy-button-id=\"buy_btn_xxx\" publishable-key=\"pk_live_xxx\"></stripe-buy-button><script async src=\"https://js.stripe.com/v3/buy-button.js\"></script>",
  "title": "Purchase product",
  "className": "w-full"
}
{
  "service": "custom",
  "code": "<iframe src=\"https://example.com/widget\" width=\"100%\" height=\"400\" frameborder=\"0\"></iframe>",
  "title": "Custom widget",
  "className": "w-full"
}
{
  "service": "custom",
  "code": "",
  "className": "hidden",
  "title": "Cal.com popup loader",
  "headCode": "<script type=\"text/javascript\">(function (C, A, L) { let p = function (a, ar) { a.q.push(ar); }; let d = C.document; C.Cal = C.Cal || function () { let cal = C.Cal; let ar = arguments; if (!cal.loaded) { cal.ns = {}; cal.q = cal.q || []; d.head.appendChild(d.createElement(\"script\")).src = A; cal.loaded = true; } if (ar[0] === \"init\") { const api = function () { p(api, arguments); }; api.q = api.q || []; const namespace = ar[1]; typeof namespace === \"string\" ? (cal.ns[namespace] = cal.ns[namespace] || api) && p(cal.ns[namespace], ar) && p(cal, [\"initNamespace\", namespace]) : p(cal, ar); return; } p(cal, ar); }; })(window, \"https://app.cal.com/embed/embed.js\", \"init\");Cal(\"init\", \"my-namespace\", {origin:\"https://cal.com\"});Cal.ns[\"my-namespace\"](\"ui\", {\"hideEventTypeDetails\":false,\"layout\":\"month_view\"});</script>"
}