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

    Locally installing the package via NPM is best when you're integrating the library with build tools such as Parcel, Rollup, Vite, or Webpack. We ship both an un-optimized development bundle that includes logs, and a production bundle that is specially minified to get the bundle size as small as possible. Thanks to Node package exports your bundler will automatically load the correct type based on the Node process environment (NODE_ENV).

    Bundle vs. CDN

    So you can best decide what install method is best for you, we'll quickly look at some good reasons to locally bundle instead of using a CDN.

    • It provides the greatest control over the library. If you're looking to build your own player elements or modify certain behaviour, then this is the path of least resistance.

    • It provides the optimal development experience working with the library because your IDE can provide you with type/value validation and documentation. We also ship a helpful integration for VSCode so you can get autocomplete suggestions for our custom elements when writing HTML.

    • It leads to less duplication of code as dependencies of this library (e.g, Lit) will be bundled only once. If you're using a CDN, any dependencies that are used both in your application and the player library will be loaded twice.

    • Reduces the final bundle size as your bundler can perform tree-shaking through static analysis to eliminate dead-code (i.e., unused imports). We've marked side-effect files in the library to improve this process further.

    • Reduces the number of HTTP requests and round-trips required to load the player. Ultimately, this speeds up the time it takes for the player to load because your bundler can optimize the loading and evaluating time of JavaScript by grouping code into chunks.

    • You can easily take advantage of dynamic imports to determine when the browser loads the library. You don't want the loading of player-related code to block your users from interacting with your application.

  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. Install NPM Package

    Install @vidstack/player and dependencies via NPM.

            npm i lit hls.js @vidstack/player@next
  5. Register Elements

    Register the custom media elements. The .js extension is required for the package export map to work.

            import '@vidstack/player/define/vds-media.js';
    import '@vidstack/player/define/vds-hls.js';
  6. 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.