Video Embed
<cw-video-embed> | CwVideoEmbed
A lazy-loading facade for embedding third-party video (YouTube, Vimeo, etc.) without paying its cost until someone actually wants to watch.
Most third-party video embeds (YouTube, Vimeo, and similar) load a meaningful amount of JavaScript and start
tracking scripts the moment their <iframe> is added to the page — even if nobody ever
presses play. <cw-video-embed> avoids that: it shows a static thumbnail and a play
button, and only activates the real embed once someone actually clicks.
Supply the real embed markup — typically an <iframe> — inside an inert
<template> element. Its content is never rendered until activation, so nothing inside it
loads early.
<cw-video-embed thumbnail="https://images.unsplash.com/photo-1611162617213-7d7a39e9b1d7?w=800&h=450&fit=crop" label="Play: Example video"> <template> <iframe src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" title="Example video" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen ></iframe> </template> </cw-video-embed>
import CwVideoEmbed from '@cordwainer/cw-elements/dist/react/video-embed'; const App = () => ( <CwVideoEmbed thumbnail="https://images.unsplash.com/photo-1611162617213-7d7a39e9b1d7?w=800&h=450&fit=crop" label="Play: Example video"> <template> <iframe src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" title="Example video" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen /> </template> </CwVideoEmbed> );
Always provide a label describing the specific video — a generic facade component has no way
to know what’s actually embedded, and screen reader users need more than “Play video” to decide whether to
activate it.
Examples
Aspect Ratio
<cw-video-embed> reserves layout space up front using the
aspect-ratio attribute, so activating the embed never causes content to shift. It defaults to
16 / 9; set it to match your video, or to an empty string to let the embed size itself.
<cw-video-embed thumbnail="https://images.unsplash.com/photo-1611162617213-7d7a39e9b1d7?w=800&h=450&fit=crop" aspect-ratio="4 / 3" label="Play: Example video" style="max-width: 400px;" > <template> <iframe src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" title="Example video" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen ></iframe> </template> </cw-video-embed>
Captions
Use the caption slot to add a caption below the embed.
<cw-video-embed thumbnail="https://images.unsplash.com/photo-1611162617213-7d7a39e9b1d7?w=800&h=450&fit=crop" label="Play: Example video"> <template> <iframe src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" title="Example video" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen ></iframe> </template> <span slot="caption">A short behind-the-scenes look at how this example was made.</span> </cw-video-embed>
Activating Programmatically
Call activate() to trigger the embed without a user click — useful for “play on scroll into
view” or similar patterns. Listen for cw-activate to know when it happens.
<cw-video-embed id="programmatic-example" thumbnail="https://images.unsplash.com/photo-1611162617213-7d7a39e9b1d7?w=800&h=450&fit=crop" label="Play: Example video" > <template> <iframe src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ" title="Example video" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen ></iframe> </template> </cw-video-embed> <br /> <cw-button onclick="document.getElementById('programmatic-example').activate()">Activate</cw-button> <script> const embed = document.getElementById('programmatic-example'); embed.addEventListener('cw-activate', () => console.log('Activated!')); </script>
Embedding Non-Video Content
Despite the name, <cw-video-embed> works generically with any iframe-embeddable content —
it doesn’t parse or validate what’s inside the <template>. A map embed behind a
lazy-loading facade is just as valid a use case.
<cw-video-embed thumbnail="https://images.unsplash.com/photo-1524661135-423995f22d0b?w=800&h=450&fit=crop" label="Load map" > <template> <iframe src="https://www.openstreetmap.org/export/embed.html?bbox=-0.1%2C51.5%2C0%2C51.51&layer=mapnik" ></iframe> </template> </cw-video-embed>
Importing
If you’re using the autoloader or the traditional loader, you can ignore this section. Otherwise, feel free to use any of the following snippets to cherry pick this component.
To import this component from the CDN using a script tag:
<script type="module" src="https://cdn.jsdelivr.net/npm/@cordwainer/cw-elements@1.6.0/cdn/components/video-embed/video-embed.js"></script>
To import this component from the CDN using a JavaScript import:
import 'https://cdn.jsdelivr.net/npm/@cordwainer/cw-elements@1.6.0/cdn/components/video-embed/video-embed.js';
To import this component using a bundler:
import '@cordwainer/cw-elements/dist/components/video-embed/video-embed.js';
To import this component as a React component:
import CwVideoEmbed from '@cordwainer/cw-elements/dist/react/video-embed';
Slots
| Name | Description |
|---|---|
| (default) |
An inert <template> containing the real embed markup (typically an
<iframe>). Its content is cloned into the light DOM the first time the embed is
activated, never before — this is what keeps a third-party player’s own JS and tracking scripts from
loading until someone actually clicks.
|
caption
|
Optional caption content shown below the embed. |
Learn more about using slots.
Properties
| Name | Description | Reflects | Type | Default |
|---|---|---|---|---|
thumbnail
|
The URL of the poster image shown before the embed is activated. |
string
|
''
|
|
label
|
An accessible label for the play control, e.g. "Play: Introducing Cordwainer Elements".
A generic facade component has no way to know what the embedded video actually is, so provide this
whenever the surrounding context doesn’t already make it obvious — falls back to a generic “Play
video” otherwise.
|
string
|
''
|
|
aspectRatio
aspect-ratio
|
The aspect ratio to reserve for the embed before it activates, as a valid CSS
aspect-ratio value (e.g. "16 / 9", "4 / 3"). Reserves layout
space up front so activating the embed can’t cause content to shift. Set to an empty string to let
the embed size itself instead.
|
string
|
'16 / 9'
|
|
loading
|
Whether the thumbnail image should load eagerly or lazily. |
'eager' | 'lazy'
|
'lazy'
|
|
activated
|
Indicates whether the real embed has been activated. Reflects, so activation state is stylable and inspectable. |
|
boolean
|
false
|
updateComplete |
A read-only promise that resolves when the component has finished updating. |
Learn more about attributes and properties.
Events
| Name | React Event | Description | Event Detail |
|---|---|---|---|
cw-activate |
onCwActivate |
Emitted once, the first time the embed is activated (by a user click or a call to
activate()).
|
- |
Learn more about events.
Methods
| Name | Description | Arguments |
|---|---|---|
activate() |
Activates the embed: clones the slotted <template>’s content into the light DOM
and emits cw-activate. Does nothing if already activated, or if no
<template> is present.
|
- |
Learn more about methods.
Parts
| Name | Description |
|---|---|
base |
The component’s base wrapper. |
play-button |
The clickable button shown before activation, wrapping the thumbnail and play icon. |
thumbnail |
The poster image shown before activation. |
play-icon |
The play icon overlaid on the thumbnail. |
embed |
The container the real embed is cloned into after activation. |
caption |
The container wrapping the caption slot. |
Learn more about customizing CSS parts.
Dependencies
This component automatically imports the following dependencies.
<cw-icon>