Show HN: Ghost-frame: 2click iframe embed webcomponent (e.g. YouTube, gmaps, x)

1 month ago 2

ghostframe

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).

See it in action

<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.

Read Entire Article