webcomponents/polyfills: Custom Elements and Shadow DOM for browsers that lack them
Web Components Polyfills
At a glance
- What is it?
- The Web Components polyfills are a suite of JavaScript libraries that implement Custom Elements, Shadow DOM and the template element for browsers without native support, delivered as a loader that downloads only the polyfills a given browser needs. The older the browser you are targeting, the more the project asks of your build, and the test matrix says exactly which browsers it still cares about.
- Who is it for?
- Adopt these polyfills if you are shipping a Web Components application to browsers that lack native support, and you control the build pipeline enough to compile to ES5. Do not adopt them for a modern-browser-only application, where the polyfill loader is dead weight and a source of edge cases, and do not expect the roadmap to be finished, since Shadow Parts and Adopted Stylesheets are described as work in progress.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 16 days ago.
- What is it written in?
- Mainly HTML, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Three specifications, one suite
The Web Components polyfills are a suite of JavaScript libraries that implement Web Components APIs for browsers that do not have built-in support.
The three pieces they cover are the ones a component library depends on. Custom Elements, which is the mechanism for defining a new element type. Shadow DOM, which is the encapsulation boundary. And the template element, which is how the content of a component is declared before it is instantiated. If you use Custom Elements, Shadow DOM or the template element, directly or through a library such as LitElement, the README says you can use these polyfills to make your application work in older browsers like IE11.
The suite is not finished, and the README says so in the same breath. The project is also working on polyfills for new Web Components APIs that are not built into all modern browsers yet, like Shadow Parts and Adopted Stylesheets. That is a useful signal about the direction: the focus has moved from the original three specifications to the newer parts of the standard.
The repository is a monorepo where each package lives under `packages/<package>`, and the root `package.json` is private, licensed BSD-3-Clause, and authored by the Polymer Authors. The last push was on 2026-10-07, and the repository has no GitHub releases, so the npm packages are the distribution channel.
The loader picks the polyfills each browser needs
Most people install one package, `@webcomponents/webcomponentsjs`, which contains the loader together with pre-minimized bundles of the full suite. The loader's job is to detect what the current browser is missing and download only the polyfills that browser needs, rather than shipping the whole suite to everyone.
Installation is:
npm install --save @webcomponents/webcomponentsjsAnd the usage is two script tags in the head, in a specific order:
<html>
<head>
<!-- If your application is compiled to ES5, then load this script first. -->
<script src="./node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script>
<!-- Add support for Web Components to older browsers. -->
<script src="./node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>
<!-- Load your application code anytime after loader.js -->
</head>
<body>
<!-- Your custom elements will work in older browsers like IE11. -->
<my-custom-element></my-custom-element>
</body>
</html>The first script is the ES5 adapter, and it has to come first when your application is compiled to ES5. The second is the loader, and the application code has to come after it, since the loader installs the polyfills and the application registers custom elements against them. The comment on the body element is the promise the project makes, that your custom elements will work in older browsers like IE11.
The README notes that there are more ways to load the polyfills, and points at the readme of the webcomponentsjs package for the details. The individual polyfills can also be installed separately if you want to control the bundle yourself, which the packages section describes.
Two things the polyfills cannot do for you
The polyfills handle many usage patterns automatically, but the README names the patterns that need direct interaction with the library. There are two groups, and both are about CSS custom properties.
The first is setting a custom property value imperatively, which is done by passing the value to `styleSubtree`. The second is re-computing custom properties after a DOM mutation that affects the matching condition of a CSS rule containing a custom property, for example changing a class attribute, and that also calls `styleSubtree`.
The second group is about registering styles. To use a style in the main document that sets or consumes a CSS custom property, you register it with `addCustomStyle`. To use a style inside a custom element, you pass the element's template to `prepareTemplate` before the first time you clone it. The README notes that LitElement and the Polymer Library perform this registration step automatically, which is a strong argument for one of them if you are starting from scratch and a warning if you are not.
The reason these need help is the shim. On a browser with native shadow DOM and native custom properties, the cascade inside a shadow root handles this. On a browser with the polyfill, the scoping of styles to a shadow tree and the inheritance of custom properties across the boundary have to be emulated, and emulation cannot observe every mutation the way the native engine does. The manual calls exist because the shim cannot know that a class attribute changed and therefore that a rule which sets a custom property now applies.
The rule of thumb the README implies: the more you mutate the DOM in ways that change which CSS rule matches, the more often you have to call `styleSubtree` yourself.
One package per specification, and a monorepo built with lerna
The packages section of the README lists the individual polyfills, and the structure explains how the project is put together.
`webcomponentsjs` is the loader and the pre-minimized bundles for the full suite, and the README says most users only need this package. Then there is `custom-elements`, the polyfill for Custom Elements, linked to both the MDN documentation and the HTML specification. There is `template`, the polyfill for the template element, and `shadydom`, the polyfill for Shadow DOM. Each package has its own documentation, changelog and issue label in the repository, so the packages are released independently rather than as one unit.
The build tooling is the older generation. The root `package.json` uses lerna for everything: `lerna bootstrap --ci --reject-cycles` to install, `lerna run build --stream` to build, `lerna run build:watch --parallel` to watch, and `lerna run test --concurrency 1 --stream --no-bail` to test. There is also a `nuke` target that removes the lock file and the node modules and starts over, which is the kind of script that exists because a monorepo bootstrap occasionally gets into a state nobody wants to debug.
Formatting is split between eslint and prettier, with a `format` script that runs both, and there are two sync scripts, `ignore-sync` and the paired `.eslintignore-sync` and `.prettierignore-sync` files, which copy the ignore lists into the individual packages. The compilation toolchain includes babel-core 6, gulp 4 and the Google Closure Compiler from 2021, so the bundles are produced with a compiler that predates the current toolchain by a wide margin.
The test matrix names the browsers this project exists for
The `test-sauce` script in the root `package.json` is the clearest statement of the project's purpose, because it lists the browsers the test suite runs against:
wct -s 'windows 10/microsoftedge@17' -s 'windows 10/microsoftedge@15' -s 'windows 8.1/internet explorer@11' -s 'macos 10.13/safari@12' -s 'macos 10.13/safari@11' -s 'os x 10.11/safari@10' -s 'os x 10.11/safari@9' -s 'Linux/chrome@41'Internet Explorer 11 on Windows 8.1. Microsoft Edge 15 and 17. Safari 9, 10, 11 and 12. Chrome 41 on Linux. That is the span of browsers the polyfills exist to serve, and the fact that Chrome 41 is in the list shows how old the target is.
The test tooling is `wct`, the web-component-tester, for the browser tests, run through Sauce Labs for the cross-browser matrix, with the per-package tests run afterwards through lerna. The plain `test` script runs wct and then the package tests with a concurrency of one and no bail, and it accumulates an exit code so that both halves of the suite report.
There is a `wct.conf.json` at the repository root for the test runner configuration, a `lerna.json` for the monorepo, a `SECURITY.md`, and a `LICENSE` file for the BSD-3-Clause terms. The GitHub Actions workflow badge in the README points at a `tests` workflow, so at least part of the suite also runs in CI.
Where the polyfills are the wrong choice
The first wrong case is an application that only needs to support browsers with native Web Components. In that situation the loader is dead weight, it downloads a script that decides to do nothing, and the ES5 adapter changes how your classes are transpiled for no benefit. The project even says it is working on newer APIs such as Shadow Parts and Adopted Stylesheets, which tells you where the effort is going, and it is not towards the old browsers.
The second is a build pipeline that cannot emit ES5. The custom elements polyfill needs the ES5 adapter when the application is compiled to ES5, which means the custom element class has to be constructed in a way the polyfill can upgrade. If your toolchain is ESM only and targets modern browsers, the polyfill is not applicable at all.
The third is a codebase that mutates classes and attributes aggressively. The manual `styleSubtree` calls are the tax, and an application that toggles classes on many elements in a tight loop has to think about when the shim recomputes, and the native engine does not have that problem.
The fourth is a project expecting active development. There are no GitHub releases, the build toolchain is from 2021, the closure compiler is pinned to a 2021 version, and lerna with a bootstrap step is a workflow that the JavaScript ecosystem has largely moved away from. The last push was on 2026-10-07, so something is being worked on, but this is a maintenance-mode repository, and a project that needs new Web Components behaviour should plan to implement it.
The fifth is an application with security requirements. A polyfill that emulates custom elements changes how classes are registered and upgraded, and an emulation of the encapsulation boundary is a different security model from a native one. The repository has a `SECURITY.md`, so the reporting path exists, but the model is not the platform's.
Editorial conclusion
Adopt these polyfills if you are shipping a Web Components application to browsers that lack native support, and you control the build pipeline enough to compile to ES5. Do not adopt them for a modern-browser-only application, where the polyfill loader is dead weight and a source of edge cases, and do not expect the roadmap to be finished, since Shadow Parts and Adopted Stylesheets are described as work in progress. Before you start, decide whether you need the loader package or the individual polyfills, and check the two manual steps, since setting custom properties imperatively and registering styles inside a custom element template are not handled automatically.
Frequently asked questions
How do I install the Web Components polyfills?
Install the loader package with `npm install --save @webcomponents/webcomponentsjs`, then load `custom-elements-es5-adapter.js` first if your application is compiled to ES5, and `webcomponents-loader.js` after it, with your application code following the loader.
When do I need to call styleSubtree manually?
When you set a CSS custom property value imperatively, and when a DOM mutation changes the matching condition of a CSS rule that contains a custom property, for example changing a class attribute. The README says to call styleSubtree in both cases.
Do I have to register styles inside a custom element template myself?
Yes, by passing the element's template to prepareTemplate before the first time you clone it. The README notes that LitElement and the Polymer Library perform this registration step automatically.
Which browsers do the Web Components polyfills support?
The test suite runs against Internet Explorer 11 on Windows 8.1, Microsoft Edge 15 and 17, Safari 9 through 12 on macOS and OS X, and Chrome 41 on Linux, which is the range the suite is built to cover.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/webcomponents-polyfills)Was this useful?