Skip to content
SlashClone

Embed YouTube videos without tracking cookies

@slashclone/privacy-friendly-youtube-embed

Embed YouTube videos on your site without loading anything from Google until the visitor presses Play or allows videos. A click-to-play placeholder with a local poster swaps in the youtube-nocookie.com player, with a keyboard-friendly button, no layout shift, CSP entries, and a Playwright test that proves no YouTube request happens first. React and HTML templates. Not legal advice.

SkillDevOpsv0.1.0
Activity
New: be the first to try it
  1. 1<!doctype html>
  2. 2<html lang="en">
  3. 3 <head>
  4. 4 <meta charset="utf-8" />
  5. 5 <meta name="viewport" content="width=device-width, initial-scale=1" />
  6. 6 <title>Video example</title>
  7. 7 <style>
  8. 8 /* The box reserves 16:9 space before anything loads, so nothing shifts. */
  9. 9 .yt-embed {
  10. 10 position: relative;
  11. 11 width: 100%;
  12. 12 aspect-ratio: 16 / 9;
  13. 13 overflow: hidden;
  14. 14 background: #111;
  15. 15 color: #fff;
  16. 16 }
  17. 17 .yt-embed__fill {
  18. 18 position: absolute;
  19. 19 inset: 0;
  20. 20 width: 100%;
  21. 21 height: 100%;
  22. 22 border: 0;
  23. 23 }
  24. 24 .yt-embed__poster {
  25. 25 object-fit: cover;
  26. 26 opacity: 0.6;
  27. 27 }
  28. 28 .yt-embed__layer {
  29. 29 display: flex;
  30. 30 flex-direction: column;
  31. 31 align-items: center;
  32. 32 justify-content: center;
  33. 33 gap: 0.75rem;
  34. 34 padding: 1rem;
  35. 35 box-sizing: border-box;
  36. 36 text-align: center;
  37. 37 }
  38. 38 .yt-embed__title {
  39. 39 margin: 0;
  40. 40 font-weight: 600;
  41. 41 }
  42. 42 .yt-embed__title span {
  43. 43 font-weight: 400;
  44. 44 }
  45. 45 .yt-embed__play {
  46. 46 font: inherit;
  47. 47 padding: 0.6rem 1.2rem;
  48. 48 border-radius: 999px;
  49. 49 cursor: pointer;
  50. 50 }
  51. 51 .yt-embed__play:focus-visible,
  52. 52 .yt-embed__link:focus-visible,
  53. 53 .yt-embed a:focus-visible {
  54. 54 outline: 3px solid #ffd54a;
  55. 55 outline-offset: 3px;
  56. 56 }
  57. 57 .yt-embed__note {
  58. 58 margin: 0;
  59. 59 max-width: 32rem;
  60. 60 font-size: 0.8125rem;
  61. 61 }
  62. 62 .yt-embed a,
  63. 63 .yt-embed__link {
  64. 64 color: inherit;
  65. 65 }
  66. 66 .yt-embed__link {
  67. 67 font: inherit;
  68. 68 background: none;
  69. 69 border: 0;
  70. 70 padding: 0;
  71. 71 text-decoration: underline;
  72. 72 cursor: pointer;
  73. 73 }
  74. 74 </style>
  75. 75 </head>
  76. 76 <body>
  77. 77 <main>
  78. 78 <h1>Video example</h1>
  79. 80 <!-- data-poster must point at your own origin. Never a YouTube thumbnail URL. -->
  80. 81 <div
  81. 82 class="yt-embed"
  82. 83 data-video="VIDEO_ID_HERE"
  83. 84 data-title="Getting started in five minutes"
  84. 85 data-duration="5:02"
  85. 86 data-poster="/posters/getting-started.jpg"
  86. 87 data-captions="true"
  87. 88 ></div>
  88. 89 <p><a href="/transcripts/getting-started">Read the transcript</a></p>
  89. 90 </main>
  90. 92 <script src="/js/youtube-embed.js"></script>
  91. 93 <script>
  92. 94 // Wire this to your consent layer. Example: grant the embedded-media
  93. 95 // category from the placeholder, then listen for changes.
  94. 96 // YouTubeEmbeds.onAllowAll = function () { myConsent.allow("media"); };
  95. 97 // myConsent.onChange(function (c) { YouTubeEmbeds.setAllowed(c.media); });
  96. 98 YouTubeEmbeds.init();
  97. 99 </script>
  98. 100 </body>
  99. 101</html>

Cookies that keep you signed in and remember these choices are always on. Everything else is off until you turn it on.