Content & Media

Free Video Popup Widget

Clickable thumbnail that opens a YouTube or Vimeo video in a modal.

Video Popup turns any thumbnail into a play button that opens your YouTube or Vimeo video in a clean modal overlay. It uses the privacy-friendly youtube-nocookie embed and only loads the video player after a visitor clicks, so your page stays fast. Great for product demos, testimonials, and landing pages on WordPress, Shopify, or any site that can host a script tag.

Live demo

Embed this widget

Paste either snippet into your page — a Custom HTML block on WordPress, Shopify, or Squarespace works too. Both snippets update live as you customize, so what you copy is exactly what you configured.

Hosted (recommended)html
<script src="https://hayneslab.dev/widgets/video-popup.js" data-hl-src="https://www.youtube.com/watch?v=aqz-KE-bpKQ" data-hl-title="Big Buck Bunny — Blender Open Movie" async></script>
<!-- Free Video Popup widget by haynes lab — https://hayneslab.dev/tools/widgets/video-popup -->
Inline (self-contained)html
<!-- Free Video Popup widget by haynes lab — https://hayneslab.dev/tools/widgets/video-popup -->
<script data-hl-src="https://www.youtube.com/watch?v=aqz-KE-bpKQ" data-hl-title="Big Buck Bunny — Blender Open Movie">
/*!
 * haynes lab — Video Popup widget (free)
 * Docs & embed code: https://hayneslab.dev/tools/widgets/video-popup
 */
(function () {
  'use strict';

  var PREFIX = 'hlw-video-popup';
  var STYLE_ID = 'hlw-video-popup-styles';

  function findScript() {
    return document.currentScript ||
      document.querySelector('script[src*="widgets/video-popup.js"]');
  }

  function injectStyles() {
    if (document.getElementById(STYLE_ID)) return;
    var css =
      '.' + PREFIX + ', .' + PREFIX + ' * { box-sizing: border-box; }' +
      '.' + PREFIX + ' { position: relative; max-width: 640px; margin: 0 auto;' +
      ' font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }' +
      '.' + PREFIX + '__trigger { position: relative; display: block; width: 100%; padding: 0;' +
      ' border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; background: #111827; cursor: pointer; }' +
      '.' + PREFIX + '__thumb { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }' +
      '.' + PREFIX + '__placeholder { display: block; width: 100%; aspect-ratio: 16 / 9;' +
      ' background: linear-gradient(135deg, #1f2937 0%, #111827 100%); }' +
      '.' + PREFIX + '__play { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);' +
      ' width: 64px; height: 64px; border-radius: 50%; background: var(--hlw-accent); color: #ffffff;' +
      ' display: flex; align-items: center; justify-content: center; box-shadow: 0 6px 20px rgba(0,0,0,0.35);' +
      ' transition: transform 0.15s ease; }' +
      '.' + PREFIX + '__trigger:hover .' + PREFIX + '__play { transform: translate(-50%, -50%) scale(1.08); }' +
      '.' + PREFIX + '__trigger:focus-visible { outline: 2px solid var(--hlw-accent); outline-offset: 2px; }' +
      '.' + PREFIX + '__play svg { display: block; margin-left: 4px; }' +
      '.' + PREFIX + '__label { position: absolute; left: 0; right: 0; bottom: 0; padding: 24px 14px 10px;' +
      ' font-size: 14px; font-weight: 600; color: #ffffff; text-align: left;' +
      ' background: linear-gradient(transparent, rgba(0,0,0,0.65)); }' +
      '.' + PREFIX + '__credit { display: inline-flex; align-items: center; gap: 4px; margin-top: 8px;' +
      ' font-size: 11px; color: #9ca3af; text-decoration: none; }' +
      '.' + PREFIX + '__credit svg { display: block; }' +
      '.' + PREFIX + '__credit:hover { color: #6b7280; text-decoration: underline; }' +
      '.' + PREFIX + '__overlay { position: fixed; inset: 0; z-index: 99999; background: rgba(17,24,39,0.85);' +
      ' display: flex; align-items: center; justify-content: center; padding: 20px; }' +
      '.' + PREFIX + '__dialog { position: relative; width: 100%; max-width: 900px; }' +
      '.' + PREFIX + '__frame-wrap { position: relative; width: 100%; aspect-ratio: 16 / 9;' +
      ' background: #000000; border-radius: 12px; overflow: hidden; box-shadow: 0 20px 60px rgba(0,0,0,0.5); }' +
      '.' + PREFIX + '__frame { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }' +
      '.' + PREFIX + '__close { position: absolute; top: -12px; right: -12px; width: 36px; height: 36px;' +
      ' border: 0; border-radius: 50%; background: #ffffff; color: #374151; font-size: 18px; line-height: 1;' +
      ' cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0;' +
      ' box-shadow: 0 4px 12px rgba(0,0,0,0.35); }' +
      '.' + PREFIX + '__close:hover { color: #111827; }' +
      '.' + PREFIX + '__close:focus-visible { outline: 2px solid var(--hlw-accent); outline-offset: 2px; }' +
      '.' + PREFIX + '__caption { margin-top: 10px; font-size: 14px; color: #e5e7eb; text-align: center; }' +
      '@media (max-width: 560px) { .' + PREFIX + '__overlay { padding: 12px; }' +
      ' .' + PREFIX + '__close { top: -40px; right: 0; } }';
    var style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = css;
    document.head.appendChild(style);
  }

  function el(tag, className, text) {
    var node = document.createElement(tag);
    if (className) node.className = className;
    if (text != null) node.textContent = text;
    return node;
  }

  function parseVideo(src) {
    var m;
    // YouTube: watch?v=, youtu.be/, /embed/, /shorts/, /live/
    m = src.match(/(?:youtube(?:-nocookie)?\.com\/(?:watch\?[^#]*v=|embed\/|shorts\/|live\/)|youtu\.be\/)([\w-]{6,20})/);
    if (m) {
      return {
        type: 'youtube',
        id: m[1],
        embed: 'https://www.youtube-nocookie.com/embed/' + m[1] + '?autoplay=1&rel=0',
        thumb: 'https://i.ytimg.com/vi/' + m[1] + '/hqdefault.jpg'
      };
    }
    // Vimeo: vimeo.com/12345 or player.vimeo.com/video/12345
    m = src.match(/(?:player\.)?vimeo\.com\/(?:video\/)?(\d+)/);
    if (m) {
      return {
        type: 'vimeo',
        id: m[1],
        embed: 'https://player.vimeo.com/video/' + m[1] + '?autoplay=1',
        thumb: null
      };
    }
    return null;
  }

  function playSvg() {
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('viewBox', '0 0 24 24');
    svg.setAttribute('width', '26');
    svg.setAttribute('height', '26');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    var path = document.createElementNS(svgNS, 'path');
    path.setAttribute('d', 'M8 5.14v13.72c0 .8.87 1.3 1.56.88l10.5-6.86a1.04 1.04 0 0 0 0-1.76L9.56 4.26A1.03 1.03 0 0 0 8 5.14z');
    path.setAttribute('fill', 'currentColor');
    svg.appendChild(path);
    return svg;
  }

  function logoSvg() {
    var svgNS = 'http://www.w3.org/2000/svg';
    var svg = document.createElementNS(svgNS, 'svg');
    svg.setAttribute('viewBox', '0 0 32 32');
    svg.setAttribute('width', '13');
    svg.setAttribute('height', '13');
    svg.setAttribute('aria-hidden', 'true');
    svg.setAttribute('focusable', 'false');
    function circle(cx, cy, r, attrs) {
      var c = document.createElementNS(svgNS, 'circle');
      c.setAttribute('cx', cx);
      c.setAttribute('cy', cy);
      c.setAttribute('r', r);
      for (var key in attrs) c.setAttribute(key, attrs[key]);
      svg.appendChild(c);
    }
    circle(16, 16, 16, { fill: '#0f0a0a' });
    circle(16, 16, 12, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.3 });
    circle(16, 16, 9, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1, opacity: 0.5 });
    circle(16, 16, 6, { fill: 'none', stroke: '#fff5f5', 'stroke-width': 1.2, opacity: 0.9 });
    circle(16, 16, 3, { fill: '#06b6d4' });
    circle(24.5, 7.5, 2, { fill: '#f97316' });
    circle(7, 16, 1.8, { fill: '#f97316' });
    circle(20, 20, 1.5, { fill: '#f97316' });
    return svg;
  }

  function build(script) {
    var src = script.getAttribute('data-hl-src');
    if (!src) {
      console.warn('[haynes lab video-popup] Missing required data-hl-src attribute (YouTube or Vimeo URL). Nothing rendered.');
      return;
    }
    var video = parseVideo(src);
    if (!video) {
      console.warn('[haynes lab video-popup] data-hl-src is not a recognized YouTube or Vimeo URL: ' + src);
      return;
    }

    var thumbnail = script.getAttribute('data-hl-thumbnail') || video.thumb;
    var title = script.getAttribute('data-hl-title') || 'Play video';
    var accent = script.getAttribute('data-hl-accent') || '#f97316';
    var inStage = !!script.closest('.widget-demo__stage');

    injectStyles();

    var root = el('div', PREFIX);
    root.style.setProperty('--hlw-accent', accent);

    var trigger = el('button', PREFIX + '__trigger');
    trigger.type = 'button';
    trigger.setAttribute('aria-haspopup', 'dialog');
    trigger.setAttribute('aria-label', title);

    if (thumbnail) {
      var img = el('img', PREFIX + '__thumb');
      img.src = thumbnail;
      img.alt = '';
      img.loading = 'lazy';
      trigger.appendChild(img);
    } else {
      trigger.appendChild(el('span', PREFIX + '__placeholder'));
    }

    var play = el('span', PREFIX + '__play');
    play.appendChild(playSvg());
    trigger.appendChild(play);

    if (script.getAttribute('data-hl-title')) {
      trigger.appendChild(el('span', PREFIX + '__label', title));
    }
    root.appendChild(trigger);

    var overlay = null;

    function close() {
      if (!overlay) return;
      overlay.remove();
      overlay = null;
      document.removeEventListener('keydown', onKeydown);
      trigger.focus();
    }

    function onKeydown(e) {
      if (e.key === 'Escape') close();
    }

    function open() {
      if (overlay) return;
      overlay = el('div', PREFIX + '__overlay');
      if (inStage) overlay.style.position = 'absolute';

      var dialog = el('div', PREFIX + '__dialog');
      dialog.setAttribute('role', 'dialog');
      dialog.setAttribute('aria-modal', 'true');
      dialog.setAttribute('aria-label', title);

      var wrap = el('div', PREFIX + '__frame-wrap');
      var frame = el('iframe', PREFIX + '__frame');
      frame.src = video.embed;
      frame.title = title;
      frame.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share');
      frame.setAttribute('allowfullscreen', '');
      wrap.appendChild(frame);
      dialog.appendChild(wrap);

      var closeBtn = el('button', PREFIX + '__close', '×');
      closeBtn.type = 'button';
      closeBtn.setAttribute('aria-label', 'Close video');
      closeBtn.addEventListener('click', close);
      dialog.appendChild(closeBtn);

      if (script.getAttribute('data-hl-title')) {
        dialog.appendChild(el('div', PREFIX + '__caption', title));
      }

      overlay.appendChild(dialog);
      overlay.addEventListener('click', function (e) {
        if (e.target === overlay) close();
      });
      document.addEventListener('keydown', onKeydown);

      if (inStage && script.closest('.widget-demo__stage')) {
        script.closest('.widget-demo__stage').appendChild(overlay);
      } else {
        document.body.appendChild(overlay);
      }
      closeBtn.focus();
    }

    trigger.addEventListener('click', open);

    var credit = el('a', PREFIX + '__credit');
    credit.appendChild(logoSvg());
    credit.appendChild(document.createTextNode('Powered by haynes lab'));
    credit.href = 'https://hayneslab.dev/tools/widgets/video-popup';
    credit.target = '_blank';
    credit.rel = 'noopener';
    root.appendChild(credit);

    if (script.parentNode && script.parentNode.nodeName !== 'HEAD') {
      script.insertAdjacentElement('afterend', root);
    } else {
      document.body.appendChild(root);
    }
  }

  function init() {
    var script = findScript();
    if (!script) {
      console.warn('[haynes lab video-popup] Could not locate its own script tag. Nothing rendered.');
      return;
    }
    build(script);
  }

  if (document.body) {
    init();
  } else {
    document.addEventListener('DOMContentLoaded', init);
  }
})();

</script>

Configuration

Every option is an attribute on the script tag — the same attributes the customizer above exposes. All attributes are optional unless marked required — defaults apply when omitted.

AttributeDefaultDescription
data-hl-src(required)The YouTube or Vimeo video URL; YouTube plays through the privacy-friendly youtube-nocookie domain.
data-hl-thumbnail(none)Custom thumbnail image; if omitted, the YouTube video thumbnail is used automatically.
data-hl-title"Play video"Title shown on the thumbnail and in the modal, also used for accessibility labels.
data-hl-accent"#f97316"Accent color for the play button and focus outlines.

Frequently asked questions

How do I install the Video Popup?

Paste the script tag with a data-hl-src attribute pointing at your YouTube or Vimeo video where you want the thumbnail to appear. The clickable thumbnail renders right there, and the video opens in a modal overlay when a visitor clicks it.

Can I change the thumbnail, title, and colors?

Yes. Use data-hl-thumbnail for a custom thumbnail image (otherwise the YouTube thumbnail is fetched automatically), data-hl-title for the caption and accessibility label, and data-hl-accent for the play button color.

Does it work on WordPress, Shopify, or Squarespace?

Yes — it is a single vanilla JS file with no dependencies. Paste the script tag into any HTML block, embed element, or code injection area and it works.

Is the embed privacy-friendly and fast?

Yes. YouTube videos play through youtube-nocookie.com, and the video iframe is only created after a visitor clicks the thumbnail, so no player code or cookies load with your page.

More free widgets

Need a custom widget?

We build custom widgets, integrations, and whole products. Tell us what your site needs and we'll scope it.

Get in Touch