Privacy-first 2-click embeds for full <iframe> code — with a global consent manager.
- 🛡️ No preload before consent (the real iframe sits inert inside a <template>).
- 🧠 Per-host consent stored in localStorage with TTL (180 days).
- 🧩 Works with full third-party embeds (YouTube, LinkedIn, Maps, …).
- 🧲 Global manager to view/revoke consents; revoking unloads embeds instantly.
- 🎛️ Styling hooks via CSS variables and shadow parts.
- ♿ Accessible; no layout shift (overlay scrolls if content is taller than the frame).
<iframe
width="560" height="315"
src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
title="YouTube video"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe>
</template>
<span slot="consent-text">
This YouTube content is blocked to protect your privacy. Click to enable loading from <strong>youtube-nocookie.com</strong>.
</span>
</ghost-frame>
<ghost-frame-manager></ghost-frame-manager>"><script type="module">
import 'ghost-frame'; // registers both <ghost-frame> and <ghost-frame-manager>
</script>
<ghost-frame>
<template slot="embed">
<!-- Paste your full iframe embed here -->
<iframe
width="560" height="315"
src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
title="YouTube video"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen>
</iframe>
</template>
<span slot="consent-text">
This YouTube content is blocked to protect your privacy. Click to enable loading from <strong>youtube-nocookie.com</strong>.
</span>
</ghost-frame>
<!-- Optional global manager (place anywhere) -->
<ghost-frame-manager></ghost-frame-manager>
- You place the original <iframe> inside <template slot="embed">. This keeps it inert so the third-party content never loads before consent.
- When the user chooses:
- Enable once: we construct a fresh iframe with safe defaults and only then set its src.
- Always allow: we persist a boolean flag per host (e.g. gf:youtube-nocookie.com) to localStorage (TTL 180 days) and then load.
- A small “Revoke” bar appears below the frame while enabled.
- The global manager lists all saved hosts; revoking an entry fires ghostframe:consent-changed and all matching embeds unload immediately.
Override tokens on the element:
ghost-frame {
--gf-aspect-fallback: 16/9;
--gf-radius: 14px;
--gf-border-color: rgba(0,0,0,.14);
--gf-overlay-bg: rgba(0,0,0,.04);
--gf-panel-bg: rgba(255,255,255,.98);
--gf-button-bg: rgba(0,0,0,.06);
--gf-button-bg-hover: rgba(0,0,0,.12);
}
/* Shadow parts for more control */
ghost-frame::part(panel) { backdrop-filter: blur(6px); }
ghost-frame::part(button-primary) { font-weight: 700; }
ghost-frame::part(belowbar) button { font-weight: 600; }
- Slots
- embed (required) — a <template> containing your full <iframe>.
- consent-text (optional) — custom consent text (HTML allowed).
- Methods
- revoke() — revokes consent for the host, unloads the iframe, and restores the overlay.
- Behavior
- Aspect ratio is derived from the embedded iframe’s width/height (or inline px style). Fallback is --gf-aspect-fallback.
- After consent, iframe is created and loaded only when in view (IntersectionObserver; safe fallback).
- A minimal UI to view stored consents and revoke (per host or all).
- Parts: open-button, dialog, header, title, close, clear-all, body, list, footer, table, empty.
window.addEventListener('ghostframe:consent-changed', (e) => {
console.log(e.detail.host, e.detail.allowed);
});
Fired when consent is saved or revoked (by embeds or the manager).
- referrerPolicy="strict-origin-when-cross-origin"
- sandbox="allow-scripts allow-same-origin" unless the original iframe included a sandbox attribute.
- We copy allow and allowfullscreen if you specified them.
- The frame container uses role="region" and aria-live="polite".
- Keyboard focus states and visible outlines.
- Overlay and panel are scrollable (no layout shift on small heights).
- Modern evergreen browsers (Chromium, Firefox, Safari).
- If <dialog> is unsupported, we fall back to the open attribute.
- IntersectionObserver is used for lazy load after consent; if missing, we load immediately after consent.
This component is consent-aware and prevents preloads, but you are responsible for your legal compliance, consent wording, and vendor choices. Use youtube-nocookie.com where possible.
npm install
npm run build
# serve /dist or use your bundler to import from "ghost-frame"
MIT — see LICENSE.
.png)


