Skip to content

GuideGetting Started

Browser support

Browsers and rendering environments supported by Video.js 10, and what the stylesheets need from them

Video.js 10 supports the latest two stable versions of Chrome, Edge, Firefox, Safari, and Safari on iOS. This page shows the versions that means today, what the packaged skins need from a browser, and how to reach older ones.

Browser baseline

The build reads the policy from a browserslist query in the repository, and this page resolves the same query when the docs are built:

Supported browsers and the versions the support policy resolves to today
BrowserVersions
Chrome150 and 151
Edge150 and 151
Firefox153 and 154
Safari26.5 and 26.6
Safari on iOS26.5 and 26.6

These are the versions we test in and fix bugs for. We build directly on the web platform, with custom elements, @scope, container queries, and anchor positioning, and we do not hold a feature back to keep older versions working. Older versions often work anyway:

In practice the packaged skins render in Chrome 120, Edge 120, Firefox 146, Safari 17.4, and Safari on iOS 17.4 and later. Those versions account for 89.2% of global web usage according to caniuse-lite 1.0.30001810, and this figure updates when we update that data.

CSS requirements

The packaged skins are generated CSS, and a handful of newer features hold a skin together. Required features have no fallback: without them the player shows no component styling, and @scope alone sets the floor. Degrades features lose one visual detail. Has a fallback features sit inside @supports; anchor positioning falls back to positions computed in JavaScript.

The table reads the first fully supporting version of each feature from caniuse-lite when the docs build, and feature names link to caniuse.com. The effective floor is the newest required version in each column.

First browser version that fully supports each CSS feature the packaged skins use, with the share of global web usage on supporting browsers. Feature names link to their caniuse.com support data. "Not supported" means the browser has no full support. Paired browsers share a column and show two values only when they differ. The effective floor row is the newest required version in each column.
FeatureChrome and EdgeFirefoxSafari and iOSGlobal support
@scope
Required. No component styling.
11814617.490.0%
CSS nesting
Required. No component styling.
12011717.290.7%
@layer
Required. No component styling.
999715.495.3%
:has()
Required. Menu and slider focus states are lost.
10512115.494.1%
@container
Required. Controls do not adapt to player width.
1061101694.0%
Media query range syntax
Required. Large-screen sizing is lost.
1046316.494.0%
:dir()
Required. Right-to-left layout is lost.
1201716.491.4%
Relative color syntax
Degrades. Adaptive shadows are lost.
1311331885.7%
scrollbar-color and scrollbar-width
Degrades. Menus show default scrollbars.
1216426.285.6%
backdrop-filter
Degrades. Surfaces lose their blur.
76 / 17103995.7%
Anchor positioning
Has a fallback. Popups are positioned by JavaScript.
1251472684.1%
Effective floor 12014617.489.2%

A few features have no caniuse entry. oklch() is required but older than the floor (Chrome 111, Firefox 113, Safari 15.4). light-dark(), relative colors, @property, and contrast-color() each degrade on their own; set --media-accent-text-color from Customize skins if you support browsers without contrast-color(). Firefox ESR releases older than 146 lack @scope, so the skins render unstyled there.

The JavaScript has a lower floor: ES2022 syntax, with newer APIs feature-detected, so it runs in Chrome 102, Firefox 112, and Safari 16.4 or later. The stylesheet decides whether an older browser shows a working player.

Support older browsers

To reach browsers below the floor, recompile the skin stylesheet with fallbacks. Only a stylesheet your build processes can be recompiled.

Skins are stylesheets you import, such as @videojs/react/video/skin.css, so your bundler’s CSS pipeline already sees them and nothing else changes.

lightningcss and postcss-preset-env both read a browserslist query and add what those browsers lack: they flatten nesting and rewrite :dir(), range media queries, light-dark(), and oklch() in ordinary declarations, and add vendor prefixes. Neither rewrites @scope or :scope, so a small plugin flattens each block into descendant selectors prefixed with :where(root), which adds no specificity. The PostCSS version, picked up by Vite, Next.js, and most bundlers from postcss.config.mjs:

npm install --save-dev postcss postcss-preset-env postcss-selector-parser
import presetEnv from 'postcss-preset-env';
import selectorParser from 'postcss-selector-parser';

/** Rewrite `@scope (<root>) { … }` into descendant selectors that older browsers understand. */
function flattenScope() {
  const rewrite = (selector, root) =>
    selectorParser((selectors) => {
      selectors.each((sel) => {
        let referencesRoot = false;

        sel.walk((node) => {
          if (node.type === 'nesting' || (node.type === 'pseudo' && node.value === ':scope')) {
            node.replaceWith(selectorParser.string({ value: root }));
            referencesRoot = true;
          }
        });

        if (!referencesRoot) {
          sel.prepend(selectorParser.combinator({ value: ' ' }));
          sel.prepend(selectorParser.string({ value: `:where(${root})` }));
        }
      });
    }).processSync(selector);

  return {
    postcssPlugin: 'flatten-scope',
    OnceExit(css) {
      const scopes = [];
      css.walkAtRules('scope', (atRule) => scopes.push(atRule));

      // Innermost first, so a nested scope is flattened before its parent prefixes it.
      for (const atRule of scopes.reverse()) {
        const match = /^\(\s*(.+?)\s*\)\s*$/.exec(atRule.params);
        if (!match || /\)\s*to\s*\(/.test(atRule.params)) continue;

        atRule.walkRules((rule) => {
          rule.selectors = rule.selectors.map((selector) => rewrite(selector.trim(), match[1]));
        });
        atRule.replaceWith(atRule.nodes);
      }
    },
  };
}
flattenScope.postcss = true;

export default {
  plugins: [flattenScope(), presetEnv({ browsers: 'baseline widely available', stage: 2 })],
};

The result renders in Chrome 111, Firefox 121, and Safari 16.4 or later, the floor Tailwind CSS 4 documents, because :has(), @container, @layer, and color-mix() have no fallback. Flattening @scope trades scope proximity for source order; the packaged skins never nest a component inside itself, so the output matches. Run npx browserslist "baseline widely available" to see what the query resolves to today, or pick another query for your audience.

Rendering contexts

WebViews

A WebView is a browser engine embedded inside a native app. WebViews on iOS (WKWebView) and Android (Android WebView) can behave differently from the full browser; autoplay policies, fullscreen APIs, and hardware acceleration can vary.

Video.js 10 targets standard browser environments. If you embed a player inside a native app, test it on each platform and WebView version you support.

Progressive Web Apps

Installed Progressive Web Apps (PWAs) use the browser engine, but their standalone display mode and platform policies can affect fullscreen, media sessions, and other browser integration. Test playback both in a browser tab and in the installed app.

Smart TVs and set-top boxes

TV platforms run embedded browsers with limited standards support. Video.js 10’s core is not tied to any specific platform, which makes future TV adapters possible, but TV is not a supported target today.

Server-side rendering

Video.js components render valid markup on the server. Interactivity — state management, media playback, and event handling — requires the browser and kicks in after hydration.

Guides