# 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](https://browsersl.ist) 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*

| Browser | Versions |
| --- | --- |
| Chrome | 150 and 151 |
| Edge | 150 and 151 |
| Firefox | 153 and 154 |
| Safari | 26.5 and 26.6 |
| Safari on iOS | 26.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](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements), [`@scope`](https://developer.mozilla.org/en-US/docs/Web/CSS/@scope), [container queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_containment/Container_queries), and [anchor positioning](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_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](https://github.com/browserslist/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`](https://developer.mozilla.org/en-US/docs/Web/CSS/@supports); anchor positioning falls back to positions computed in JavaScript.

The table reads the first fully supporting version of each feature from [caniuse-lite](https://github.com/browserslist/caniuse-lite) when the docs build, and feature names link to [caniuse.com](https://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.*

| Feature | Chrome and Edge | Firefox | Safari and iOS | Global support |
| --- | --- | --- | --- | --- |
| [`@scope`](https://caniuse.com/css-cascade-scope) Required. No component styling. | 118 | 146 | 17.4 | 90.0% |
| [`CSS nesting`](https://caniuse.com/css-nesting) Required. No component styling. | 120 | 117 | 17.2 | 90.7% |
| [`@layer`](https://caniuse.com/css-cascade-layers) Required. No component styling. | 99 | 97 | 15.4 | 95.3% |
| [`:has()`](https://caniuse.com/css-has) Required. Menu and slider focus states are lost. | 105 | 121 | 15.4 | 94.1% |
| [`@container`](https://caniuse.com/css-container-queries) Required. Controls do not adapt to player width. | 106 | 110 | 16 | 94.0% |
| [`Media query range syntax`](https://caniuse.com/css-media-range-syntax) Required. Large-screen sizing is lost. | 104 | 63 | 16.4 | 94.0% |
| [`:dir()`](https://caniuse.com/css-dir-pseudo) Required. Right-to-left layout is lost. | 120 | 17 | 16.4 | 91.4% |
| [`Relative color syntax`](https://caniuse.com/css-relative-colors) Degrades. Adaptive shadows are lost. | 131 | 133 | 18 | 85.7% |
| [`scrollbar-color and scrollbar-width`](https://caniuse.com/css-scrollbar) Degrades. Menus show default scrollbars. | 121 | 64 | 26.2 | 85.6% |
| [`backdrop-filter`](https://caniuse.com/css-backdrop-filter) Degrades. Surfaces lose their blur. | 76 / 17 | 103 | 9 | 95.7% |
| [`Anchor positioning`](https://caniuse.com/css-anchor-positioning) Has a fallback. Popups are positioned by JavaScript. | 125 | 147 | 26 | 84.1% |
| Effective floor | 120 | 146 | 17.4 | 89.2% |

A few features have no caniuse entry. [`oklch()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) is required but older than the floor (Chrome 111, Firefox 113, Safari 15.4). [`light-dark()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/light-dark), [relative colors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_colors/Relative_colors), [`@property`](https://developer.mozilla.org/en-US/docs/Web/CSS/@property), and [`contrast-color()`](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/contrast-color) each degrade on their own; set `--media-accent-text-color` from [Customize skins](./customize-skins.md) if you support browsers without `contrast-color()`. [Firefox ESR](https://www.mozilla.org/en-US/firefox/enterprise/) 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.

Packaged HTML skins inline their CSS into JavaScript, so [add the skin source to your project](./customize-skins.md#style-skin-source) first. The added `skin.css` can pass through your CSS pipeline. CDN bundles inline their CSS too; to recompile it, move to a [self-hosted build](./self-hosting.md) and use the skin source instead of the prebuilt CDN skin.

[lightningcss](https://lightningcss.dev/transpilation.html) and [postcss-preset-env](https://preset-env.cssdb.org/) 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)`](https://developer.mozilla.org/en-US/docs/Web/CSS/:where), which adds no specificity. The PostCSS version, picked up by Vite, Next.js, and most bundlers from `postcss.config.mjs`:

```bash
npm install --save-dev postcss postcss-preset-env postcss-selector-parser
```

**postcss.config.mjs**

```js
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](https://tailwindcss.com/docs/compatibility#browser-support), 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](https://developer.apple.com/documentation/webkit/wkwebview)) and Android ([Android WebView](https://developer.android.com/reference/android/webkit/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.

## Related pages

### Guides

- [Customize skins](./customize-skins.md): Style a packaged Video.js skin or add its source to change controls, layout, styles, and interactions
- [Installation](./installation.md): Install Video.js packages and build an accessible, customizable video player with composable controls
- [Bundlers](./bundlers.md): Bundler requirements for Video.js package exports, CSS, and wrapper libraries