Add an animated demo to your site without hurting Core Web Vitals

Reserve the demo's space before it loads, show a plain poster image first, and load the player only when it nears the viewport. In a hero, keep that first image out of lazy loading, then test with and without the demo, because no general rule can tell you the cost on your page.

By the Programmable team · Updated · 9 min read

Everything here comes from Google's and the W3C's documentation, read on 31 October 2026. We haven't included benchmarks because a number from our pages wouldn't tell you about yours, and the last section shows how to get your own.

The three targets, and how a demo touches each

web.dev defines a good experience at the 75th percentile of page loads, split by mobile and desktop. A page passes only if it meets all three. The poor bands below come from PageSpeed Insights.

MetricGoodPoorHow a demo can hurt it
Largest Contentful Paint (LCP)2.5 s or lessOver 4 sA large demo in the hero loads late, or its first image waits on a script
Interaction to Next Paint (INP)200 ms or lessOver 500 msHeavy scripts keep the browser busy when someone taps
Cumulative Layout Shift (CLS)0.1 or lessOver 0.25The embed loads with no reserved space and pushes the page down

A demo below the fold mostly risks CLS and page weight. A demo in the hero risks all three.

Choose the format first

FormatGood forWatch out for
Static imageHeroes where speed comes firstNo motion
MP4 or WebM in a <video> tagA looping or click-to-play film you hostNeeds a poster and a pause control
Animated GIFPlaces that can't take video, such as emailLarge files
Iframe or embed scriptInteractive demos, analytics, updates without a redeployExtra requests and a third party

Avoid large GIFs on web pages. web.dev's example took a 3.7 MB GIF to a 551 KB MP4 or a 341 KB WebM. That article dates from 2018 and it's one file, so treat it as a direction, not a ratio. It recommends <video autoplay loop muted playsinline> for GIF-style clips.

Reserve the space

This is the cheapest fix and it solves most demo CLS. Google's advice is to "Always include width and height size attributes on your images and video elements", or to reserve the space with CSS aspect-ratio. Ads, embeds and iframes can all push content down, and the same article notes that embed platforms "don't always reserve space for their widgets", so don't assume yours does.

  • For a video: <video width="1280" height="720" poster="demo.jpg" ...> plus max-width:100%; height:auto in your CSS.
  • For an iframe: style="width:100%;aspect-ratio:16/9;border:0".
  • For a script embed: put it in a wrapper with its own aspect-ratio, so the slot exists before the script runs.

Then load the page on a slow connection and watch the content below the demo. It shouldn't move. Use transform for any motion on your own page: web.dev notes it lets you animate "without triggering layout shifts", while animating top and left can trigger them.

Give it a poster, and treat the hero differently

For a <video> element, LCP counts the poster image load time or the first frame presentation time, "whichever is earlier". So a poster helps in two ways: it fills the slot and it can be your LCP element.

If the demo is above the fold, its first image is probably your LCP element, and that changes what you should do:

  • Keep the poster in your HTML, not injected by a script. web.dev wants the LCP resource discoverable from the HTML source.
  • Don't lazy load it. "Never lazy-load your LCP image", says that article, and the video guide says the same of videos that are LCP elements.
  • Use fetchpriority="high" on an <img> that's likely to be the LCP element, on no more than one or two images.
  • Compress the poster, because a slow poster is a slow LCP.

The dependable hero pattern is a plain <img> that paints first, and a player that replaces it only after the page has loaded or when someone presses play.

Lazy load everything below the fold

For an iframe offscreen, add loading="lazy". web.dev explains that it defers offscreen iframes until the user scrolls near them. Be realistic: the Chromium research it quotes, from October 2019, found 2 to 3% median data savings across pages, and not every page has a heavy iframe. If yours does, the same article's embed figures show why it matters, such as about 500 KB for a YouTube embed on initial load. That figure isn't dated.

For a self-hosted video, preload="none" gives the best chance of avoiding the download, and poster holds the place. With loading="lazy", autoplay videos won't start until they're in or near the viewport, says the video guide.

A step further is the facade pattern: a static element that looks like the embed, which preconnects on hover and replaces itself with the real thing on click. Chrome's Lighthouse removed its facade audit in version 13, but the pattern is still valid. It's a good fit for heavier interactive demos that most visitors won't play.

Keep the page responsive

Loading fewer scripts helps INP most. Don't run more than one player on a page if you can avoid it, and pause anything that's offscreen, since an animation nobody can see still uses the CPU. A demo on the home page shouldn't load on your pricing or docs pages through a shared template.

Motion and accessibility

Performance isn't the only rule that applies to an autoplaying demo.

WCAG 2.2 success criterion 2.2.2, Pause, Stop, Hide, is Level A. Its Understanding page puts it this way: for moving content that "(1) starts automatically, (2) lasts more than five seconds, and (3) is presented in parallel with other content, there is a mechanism for the user to pause, stop, or hide it", unless the motion is essential. Animations count. A four-second clip on a loop keeps moving past five seconds, so we'd treat it as covered.

Your demoWhat you need
Autoplays and stops within five secondsNothing under 2.2.2
Autoplays longer, or loopsA visible pause control
Only plays when pressedAlready meets it; give it normal controls
Plays on hover or when scrolled into viewThe W3C page counts this as starting automatically, so treat it like autoplay

Next, respect the prefers-reduced-motion media feature. It tells you the visitor has asked for less non-essential motion. MDN notes that scaling or panning large objects can trigger vestibular problems. For a demo, the simple answer is to show the poster with a play button:

  • In CSS, hide the autoplaying version inside @media (prefers-reduced-motion: reduce).
  • In script, check matchMedia("(prefers-reduced-motion: reduce)").matches before calling play().

Two more rules from WCAG 2.2: nothing should flash more than three times in any one-second period (2.3.1, Level A), and prerecorded video with sound needs captions (1.2.2, Level A). Give the demo a text alternative too, so the point of it survives if it never plays.

Measure your own page

Compare the same page with and without the demo, because one score tells you nothing about the demo's cost.

  1. Get a baseline. Run the page in PageSpeed Insights. Note that it shows two things: field data from real Chrome users over the previous 28 days, and a lab run. Field data won't reflect a change for weeks, and a low-traffic page may not have any.
  2. Test locally. Open DevTools and the Performance panel. It shows local LCP and CLS straight away, and INP once you interact. Set the network to Fast 4G and slow the CPU, so your laptop acts more like a phone.
  3. Compare. Add a query flag or a preview branch that removes the demo. Load each version several times with the cache disabled, and record the middle value of each metric in a table.
  4. Find the cause. Check what the LCP element actually is. If it's the demo's poster, fix that first. Scroll the page and watch CLS: it should stay put when the embed loads.
  5. Watch real users. Add Google's web-vitals library with npm install web-vitals, and send onLCP, onINP and onCLS values to your analytics. After launch, Search Console's Core Web Vitals report shows groups of similar pages, by device, once it has enough data.

Two cautions. Lighthouse can't measure INP, so it uses Total Blocking Time as a proxy. And the web-vitals library says its APIs have "no visibility into" iframe content, so on pages with iframes its numbers can differ from Chrome's field data. If the two disagree on an embed, believe the field data and investigate the difference.

VersionLCPCLSINPPage weight
Without demo
With demo, your current setup
With demo, after fixes

If you use a Programmable embed

Here's what the code does today. We haven't published speed measurements for it, so use the steps above on your own page.

  • One-line script. It loads with async, and the file is about 17 KB before compression. It starts the iframe when the box comes within 300 px of the viewport, or when half of it is visible for website components. The iframe also has loading="lazy".
  • Reserved space. The script makes a 16:10 box, then switches to the film's true shape once its details arrive. If your film isn't 16:10, wrap the script in an element with the right aspect-ratio, or the page can shift.
  • Plain iframe. It sets aspect-ratio inline and uses loading="lazy", so the slot is reserved from the first paint. In a hero, remove the lazy attribute or show an image first.
  • Poster. Website components show a poster, which the script adds and lazy loads. That's fine below the fold, but in a hero it won't be discoverable in your HTML, so export a PNG still from Export and paint that first.
  • Motion. Embeds pause when offscreen and hold a still frame under reduced motion, and components show a play button instead of starting.
  • Pause control. A looping embed has no visible pause button. If your film runs over five seconds on a loop, use Click through steps from the Share panel, or add your own button that calls element.programmable.send("programmable:pause") on the embed's host element.

If you want no third party at all, export the MP4 and host it yourself with the <video> pattern above. You'd lose the embed's analytics and lead capture, but it's the simplest thing to measure and control. The site builder guides cover pasting an embed into Webflow, Framer, WordPress, Shopify and Squarespace, and the marketplace has animated components you can ship as code in your own bundle.

One SEO note: Google says a watch page's main purpose is to show a single video, and a page where the video is secondary won't qualify as one. A demo on your home page is there for visitors, so don't plan on video results from it. Our guide to choosing a demo video or an interactive demo can help you decide what to embed.

Common questions

Does autoplay hurt Core Web Vitals?

Autoplay isn't a metric. What matters is the file size, whether the slot is reserved, and when the first image paints. A muted, short, well-compressed video with a poster can be fine; a large one without a poster won't be.

Is a poster image or the video counted for LCP?

For a video element, LCP uses the poster load time or the first frame presentation time, whichever is earlier. That's why a light poster in your HTML matters for a hero.

Should I lazy load a demo in my hero?

Don't lazy load the image that paints first. You can delay the player behind that image, so the visitor sees something straight away and the heavier part loads later.

Do Core Web Vitals scores count content inside iframes?

Chrome's field data and JavaScript measurement can differ here. Google's web-vitals library says it can't see into iframes, and that this can cause differences from the Chrome User Experience Report. Compare both on pages with embeds.

Sources