1. Getting Started
  2. Quickstart

Getting Started


This section will get you up and running with the library. You'll find specific instructions below depending on the type of installation method (NPM or CDN), library (HTML, React, etc.), and provider (Audio, Video, HLS, etc.) you opt to use.

Browser Support


We support at minimum ~92.74% of users tracked on caniuse.

Ensure the following browser support table is suitable for your application. We've built the library for the modern web; thus, we try to avoid bloated polyfills and outdated environments as much as possible. At the moment, we only support browsers that fully implement the Custom Elements V1.

We've tried to be conservative with these numbers; take this as a lower bound. We likely support a greater range of browsers and versions, but we won't note it here until we test it; if you're not sure, best try it yourself and let us know!

  • Edge 79+
  • Firefox 72+
  • Chrome 73+
  • Safari 13.1+
  • Opera 64+
  • iOS Safari 13.7+
  • Android Browser 81+
  • Opera Mobile 59+
  • Chrome for Android 88+

Player Installation

  1. Select Install Method

    Using a CDN like JSDelivr is the simplest and fastest way to start using the player library. We provide a CDN bundle that includes all package dependencies, and it's specially minified to get the bundle size as small as possible.

    Why use a CDN?

    So you can best decide what install method is best for you, we'll quickly look at some good reasons to use a CDN. Refer to the NPM option to find good counter reasons using the select menu above.

    • It's simple. There's no build step or anything to install. Add a few script tags, and you're ready to start creating a player, making this an ideal option for development, playground, and low-code environments (e.g., WordPress and Shopify).

    • If you aren't importing from the library or building any custom elements, there may be no point in checking it into Git. It's one less dependency to track, version control, and load in your Git repository. Refer to the NPM install option to find good reasons why you should still bundle it locally (if possible).

    • You'll get much faster load times because JSDelivr uses a multi-CDN architecture and has more than 750 points of presence (PoPs). All other users of our library who are also using the CDN will pull the code closer to all PoPs while keeping the cache warm every time they make a request.

    • It'll reduce the load and stress on your server. If you're already at your server limit computationally or financially, it may be best to delegate loading of some resources to an externally managed CDN.

  2. Select JS Library

    The HTML option refers to our Web Components library. Our custom elements can be used anywhere with the simple drop of an import or CDN link as they're natively supported by browsers. This option is best when writing plain HTML or using a JS library such as Angular. Native web components have excellent support in most libraries.

  3. Select Media Provider

    Embed video content into documents via the native video element. This provider also enables streaming video using the HTTP Live Streaming (HLS) protocol. HLS isn't widely supported yet, but we use the popular hls.js library to ensure it works anywhere Media Source Extensions (MSE) are supported, which accounts for ~96.42% of users tracked on caniuse.

  4. Register Elements

    Add the following script tags to register the custom media elements.

            <!DOCTYPE html>
      <!-- ... -->

    You can register specific elements like so:

  5. Add Player Markup

    Add the following player HTML boilerplate to get started.

      <vds-hls poster="https://media-files.vidstack.io/poster.png">
        <video src="https://media-files.vidstack.io/hls/index.m3u8" preload="none"></video>

Congratulations, you're done 🎉 You might not see anything yet and that's okay because you haven't designed a UI! You can quickly try showing the native controls to see if everything is working.

Media Controls

By default, we'll remove the controls on the underlying <audio> or <video> element as we expect a custom user interface (UI) to be presented. Set the controls property on the provider component if you'd like to display the native UI controls like so:

Media Loading

The following media loading strategies are available:

  • eager: Load media immediately - use when media needs to be interactive as soon as possible.
  • idle: Load media once the page has loaded and the requestIdleCallback is fired - use when media is lower priority and doesn't need to be interactive immediately.
  • visible (default): Load media once it has entered the visual viewport - use when media is below the fold and you prefer delaying loading until it's required.
  • custom: Load media when the startLoadingMedia() method is called or the vds-start-loading event is dispatched - use when you need fine control of when media should begin loading.

Here's another example using a custom loading strategy:

Media Autoplay

We manually handle autoplay so we can detect when it fails. Therefore, ensure you set autoplay on the provider component instead of the underlying media element like so:


The autoplay attribute on the <audio> or <video> element will take priority over the preload attribute. Never set this attribute because it will break the loading process.

Media Poster

You can declare a second poster in your markup like so:

The one on the Vidstack provider element is the poster that you expect your users to load and see. The other on the <video> element is shown temporarily as the primary poster loads, or if JavaScript is disabled by a client or crawler (i.e., search engine).

You can choose to load a low-res image that the user will see while the main poster is loading or a 124x70 (or greater) sized image explicitly designed to show in search results.


See the Structured Video Data documentation by Google to learn how to explicitly provide rich information about your videos.

Media Sizing

By default, the browser will use the intrinsic size of the loaded media to set the dimensions of the provider. As media loads over the network, the element will jump from the default size to the intrinsic media size, triggering a layout shift which is a poor user experience indicator for both your users and search engines (i.e., Google).

To avoid a layout shift, we recommend you fill 100% of your media container and use an aspect ratio container which holds a fixed ratio (e.g., 16/9) like so:


Ideally the ratio set should match the ratio of the media content itself (i.e., intrinsic aspect ratio) otherwise you'll end up with a letterbox template (empty black bars on the left/right of the media).

Avoiding FOUC

Custom elements contain internal styles in their component definition so they're not applied until the JS chunk has loaded and the component is registered. This can cause a "flash of unstyled content" (FOUC). You can avoid this by hiding the top-level media element until it's defined like so:

        /* In an SSR environment, use the code snippet below - not this one. */

/* Hide media element until it's defined. */
vds-media:not(:defined) {
  visibility: hidden;




This section only applies if you're using a JS integration such as React and you're rendering your application server-side (includes pre-rendering). Keep reading if you're using an application framework such as Next.js, Remix, SvelteKit, or Nuxt.

Our integrations handle rendering custom elements server-side as part of your application. Therefore, there is no FOUC in browsers that support Declarative Shadow DOM 🎉

However, FOUC issues still occur in browsers that don't support the proposal yet. Our hydrate.js script provides a polyfill but it requires JS to load, so changes won't be applied for a tick. The best way to avoid FOUC issues in this case is by hiding the media element's children until the template is attached like so:

        /* Hide media element until shadow root is attached. */
vds-media:not(:defined) > template[shadowroot] ~ * {
  visibility: hidden;


The rule is ignored in browsers that support Declarative Shadow DOM because the template shadowroot> child is removed during HTML parsing.