Skip to main content

Why More Are Dropping IE/Legacy Browser Support 2026

·PkgPulse Team
0

TL;DR

IE11 is below 0.5% global usage. Most packages dropped it. The real question is Safari/iOS legacy. Internet Explorer 11 was officially retired by Microsoft in June 2022. By 2026, it's irrelevant for almost all developers. The more nuanced discussion: older iOS versions (iOS 14/15) and Android WebView are now the actual legacy browser constraint. Packages dropping ES5 transpilation and adopting modern APIs (ES2020+, top-level await, dynamic import) is unambiguously good for performance and developer experience.

Key Takeaways

  • IE11 < 0.3% global usage — irrelevant for most web apps in 2026
  • iOS Safari 14/15 — ~3% usage; the real constraint for modern JavaScript features
  • ES2022 target — the new baseline for most npm packages
  • browserslist — the standard for declaring browser support in your project
  • Dropping legacy = smaller bundles — fewer polyfills = less JavaScript shipped

The Legacy Browser Timeline

Browser End-of-Life (EOL) dates:
IE11:           June 2022 (Microsoft official EOL)
Chrome 90:      Auto-updated; "legacy" doesn't apply
Firefox ESR:    ~1 year release cycle
iOS Safari:     ~2 years of support per iOS version

Global usage shares (March 2026):
IE11:           ~0.25%  (mostly government/enterprise locked systems)
iOS Safari 14:  ~1.2%   (iPhone 6s users, released 2015)
iOS Safari 15:  ~2.8%   (still receiving security updates)
Chrome 100+:    ~65%
Safari 16+:     ~18%
Firefox 120+:   ~4%

What "Dropping Legacy Support" Actually Means

Modern JavaScript Features Major Packages Now Require

// Features enabled by dropping IE11/ES5 target:

// 1. Native ESM (import/export)
import { useState } from 'react';  // No CommonJS wrapper needed

// 2. Optional chaining and nullish coalescing (ES2020)
const name = user?.profile?.name ?? 'Anonymous';
// Instead of: (user && user.profile && user.profile.name) || 'Anonymous'

// 3. Logical assignment operators (ES2021)
settings ||= { theme: 'dark' };
// Instead of: settings = settings || { theme: 'dark' }

// 4. Top-level await (ES2022)
const data = await fetch('/api').then(r => r.json());
// Instead of: IIFE or module-level init function

// 5. Class fields (ES2022)
class Store {
  #state = {};          // Private field (# prefix)
  count = 0;            // Public field
}

// 6. structuredClone (2021)
const copy = structuredClone(deepObject);
// Instead of: JSON.parse(JSON.stringify(deepObject)) or lodash.cloneDeep

// 7. Object.hasOwn (2022)
if (Object.hasOwn(obj, 'key')) { ... }
// Instead of: Object.prototype.hasOwnProperty.call(obj, 'key')

Bundle Size Impact

Polyfill savings when dropping IE11 support:

@babel/polyfill (IE11 compat):    ~90KB gzipped
core-js/stable (IE11 compat):     ~55KB gzipped
No polyfill (modern browsers):    0KB

A 500KB bundle with IE11 compat → ~420KB without
That's a 16% reduction from polyfills alone.

Plus: modern syntax is more compressible than ES5 transpiled code
ES2022 arrow functions, destructuring → smaller gzip output than var/function

Key Packages That Dropped Legacy Support

# React 19 (Dec 2024)
# Dropped: IE11, legacy React APIs
# Requires: modern browser with Promises, fetch, ResizeObserver

# Vite 5+ (2023)
# Requires: Node.js 18+, modern browsers
# No longer ships polyfills by default
# build.target defaults to 'es2015' (can set to 'es2022' for smaller output)

# Next.js 15 (2025)
# Drops Node.js 16 (requires 18.17+)
# Client target: modern browsers (ES2020+)

# TanStack Query v5 (2023)
# Dropped: IE11, older React versions
# Requires: React 18+

# Prettier 3 (2023)
# Node.js 14+ required (dropped Node.js 12)
# Output uses more modern syntax

# Storybook 8 (2024)
# Dropped: IE11, legacy Webpack configs

# Vitest 2 (2024)
# Node.js 18+ required
# Native ESM only

Configuring Browser Support

browserslist (The Standard)

# .browserslistrc — tells bundlers/Babel what to target
# Modern: no IE, no legacy Android

> 1% in US        # >1% usage in US
last 2 versions   # Last 2 versions of each browser
not dead          # Exclude browsers with 0% usage
not IE 11         # Explicit IE11 exclusion
not iOS < 15      # Drop iOS 14 and below
// package.json — browserslist field
{
  "browserslist": {
    "production": [
      ">0.5% in US",
      "last 2 Chrome versions",
      "last 2 Firefox versions",
      "last 2 Safari versions",
      "not IE 11"
    ],
    "development": [
      "last 1 Chrome version",
      "last 1 Firefox version"
    ]
  }
}
// vite.config.ts — set build target
export default defineConfig({
  build: {
    target: 'es2022',  // Modern — no IE transpilation
    // target: 'esnext',  // Bleeding edge
    // target: ['chrome90', 'firefox90', 'safari14']  // Explicit versions
  },
});

Handling the iOS Safari Gap

iOS Safari 14/15 still miss some ES2023 features:

// Check caniuse.com before using new syntax in iOS-heavy apps

// iOS 14 missing:
// - Error.cause (use message wrapping instead)
// - Array.prototype.findLast (polyfill or use array.length-1)
// - Object.fromEntries needs polyfill on iOS 12

// iOS 15 missing:
// - structuredClone (use polyfill or JSON.parse/stringify)
// - Import assertions

// iOS 16+ (85%+ of active iOS users in 2026):
// - All ES2022 features supported
// - No polyfills needed

// Strategy: target ios >= 15.0 in browserslist
// Only ~1% of iOS users would be excluded

What to Do If You Support Legacy Browsers

Some enterprises and government apps must support IE11 or older Android:

# Option 1: Use older package versions (CRA approach)
# Pin to versions that still support legacy:
# react@17 (last IE-supporting major)
# Create React App v4 (last Webpack 4 version with legacy targets)

# Option 2: Transpile at build time
# @vitejs/plugin-legacy — adds legacy support to Vite output
npm install -D @vitejs/plugin-legacy

# vite.config.ts
import legacy from '@vitejs/plugin-legacy';
export default defineConfig({
  plugins: [
    legacy({
      targets: ['ie 11', 'since 2019', 'edge >= 18'],
      additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
    }),
  ],
});
# Result: two bundles — modern (fast) + legacy (IE compat)
# Browser gets the right bundle via module/nomodule

# Option 3: Use a CDN that polyfills
# polyfill.io — serves only needed polyfills based on browser UA
<script src="https://polyfill.io/v3/polyfill.min.js"></script>

The Real Legacy Browser Problem: Android WebView

Android WebView statistics (2026):
Android 8 (2017): ~3% of Android — WebView based on Chrome 58
Android 9 (2018): ~5% of Android — WebView based on Chrome 68
Android 10+:      ~85% — modern WebView, auto-updates

Note: Android WebView on older Android versions doesn't auto-update.
If you build mobile web apps (React Native WebView, PWAs, Capacitor):
  - Test on Android 9 (Chrome 68 equivalent)
  - Missing: CSS Grid (partial), async/await transpilation may be needed

Practical Decision Guide

Does your app serve government/healthcare/enterprise IE11 users?
├── Yes → Use @vitejs/plugin-legacy or pin to older package versions
└── No  → Set target to es2022, drop polyfills

Does your app serve users in India, Southeast Asia, Africa?
├── Android WebView matters → test on Android 9+ devices
└── Mostly tech-savvy users → modern targets fine

Does your npm library need to support legacy consumers?
├── Yes → Ship CJS + ESM, target ES2015 minimum in output
└── No (internal tool) → ES2022 target, ESM only

The Real Legacy Browser Problem: Android WebView

IE11 is a solved problem. By 2026, its global usage share is below 0.3% and confined almost entirely to locked-down government and enterprise environments that have separate IT processes for handling compatibility. The actual problematic "legacy browser" in 2026 is Android WebView — and it behaves very differently from a versioned desktop browser.

Android WebView is the embedded browser engine used by Android apps that display web content natively, including React Native WebView, Capacitor, and PWAs installed on older devices. Unlike Chrome on Android, which auto-updates through the Play Store, WebView on older Android versions updates separately and can lag behind Chrome significantly. Apps installed before Android 5.0 on devices that have stopped receiving OS updates may have WebView versions functionally equivalent to Chrome 58 or older.

The users affected are concentrated in markets with high rates of older Android device retention: India, Indonesia, the Philippines, Nigeria, and other markets where mid-range and budget Android devices are the primary internet access point and hardware upgrade cycles are longer. If your application serves these markets, the WebView problem is real and the IE11 framing is a distraction from what actually matters.

Modern JavaScript features like optional chaining (?.), nullish coalescing (??), and ES2022 class fields may not be supported in these older WebView versions. The failure mode is typically a silent JavaScript error that prevents the entire application from loading — worse than a graceful degradation.

The diagnostic step: Chrome UX Report (CrUX) data for your specific URL shows the actual browser distribution of real visitors. If more than 5% of your traffic comes from old Android WebView user agents, you have a concrete problem that deserves a concrete solution, not just a note in the documentation. Vite and webpack with browserslist targeting "defaults and fully supports es6-module" handles the modern cutoff correctly while still transpiling for the browsers that represent real traffic.

Configuring Browser Targets Correctly in 2026

Browser targeting configuration has a number of common pitfalls that lead to either over-broad polyfilling (unnecessary bundle weight) or under-targeted output (real users getting broken JavaScript).

The browserslist "defaults" query is the right starting point for most 2026 projects. It covers browsers with more than 0.5% global usage, excluding officially dead browsers. The frequently used alternative — "last 2 versions" — is misleadingly permissive: "last 2 versions" of Safari includes versions from roughly three years ago, pulling in polyfills you almost certainly don't need. Use "defaults" and add explicit exclusions for edge cases.

For Vite, setting target: 'es2022' in vite.config.ts produces modern output that works natively in Chrome 94+, Firefox 94+, and Safari 16+ — covering roughly 95% of global web traffic in 2026. This is the appropriate default for most projects. The 'esnext' target produces the smallest possible output but assumes the very latest browser versions and is best reserved for internal tools where you control the browsing environment.

The polyfill strategy for teams that need broader support beyond the "defaults" baseline: core-js@3 with @babel/preset-env and useBuiltIns: 'usage' performs static analysis of your source code and adds only the polyfills that your specific code paths actually require. Check the coverage percentage for your target query on Browserslist.dev — it shows the percentage of global browser sessions your configuration supports.

One important infrastructure note: Polyfill.io, which historically served browser-specific polyfill bundles via CDN based on the user's User-Agent, was compromised by a malicious actor in 2023 and should not be used. For projects that need CDN-hosted polyfills, use cdnjs.cloudflare.com-hosted polyfill bundles or — preferably — bundle polyfills into your own build output using core-js so you control the exact code your users receive.


The Tangible Cost of Maintaining Legacy Support

The engineering cost of legacy browser support is rarely quantified, but it is substantial and it compounds over time. Teams that have moved off IE11 support uniformly report significant reductions in build configuration complexity, CI runtime, and cognitive overhead — even before measuring any bundle size improvement.

The direct cost lives in three places. First, the transpilation and polyfill pipeline. A project targeting IE11 needs Babel with @babel/preset-env configured for an ES5 target, core-js polyfills for Promise, fetch, WeakMap, Symbol, and dozens of other APIs, and regenerator-runtime for async/await. Each of these adds a dependency to maintain, a version to keep current, and a potential breaking change surface. The @babel/polyfill approach added roughly 90KB gzipped to every bundle; the more modern useBuiltIns: 'usage' approach with core-js@3 is better but still adds 40–60KB for IE11 targets because the ES5 compatibility surface is enormous.

Second, the test matrix. Every CI test run that covers IE11 — whether via Selenium, Playwright with legacy browser binaries, or BrowserStack — adds wall time and cost. Maintaining a test matrix across IE11, Safari 14, Chrome, and Firefox doubles or triples CI complexity relative to a modern-only target. Teams that have audited their CI spend frequently find that the legacy browser test lanes account for a disproportionate share of total CI cost.

Third, the authoring constraint. Developers writing code that must run in IE11 cannot use optional chaining, nullish coalescing, class fields, top-level await, dynamic import, or a dozen other syntax features that modern JavaScript developers consider standard. Code that is mentally authored in modern JavaScript and then transpiled down is fine; code that must be mentally authored in IE11-compatible patterns to avoid transpilation edge cases is a genuine developer experience tax. The cognitive overhead of "will this work in IE11?" becomes a background hum that slows down development.

When a major dependency drops legacy browser support, the decision is effectively made for you. If react@19 requires a modern browser, you cannot ship React 19 features to IE11 users — you can stay on React 17 and accept falling behind the ecosystem, or you can drop IE11 support and upgrade. The package ecosystem's collective decision to move forward creates external pressure that resolves internal debates that would otherwise drag on indefinitely.

How browserslist Encodes the Legacy Support Decision

The browserslist configuration in a project is the canonical source of truth for which browsers that project commits to supporting. It is consumed by Babel, PostCSS Autoprefixer, Stylelint, and a range of other tools to determine what transformations are necessary. A single .browserslistrc file or package.json browserslist field controls the entire browser compatibility surface for the project.

Understanding the semantics of common browserslist queries clarifies what "dropping legacy support" actually means in configuration terms. The query "defaults" — which resolves to "> 0.5%, last 2 versions, Firefox ESR, not dead" — is the practical 2026 default for most projects. It covers roughly 94% of global web traffic and excludes IE11, legacy Android WebView, and other browsers with negligible usage. The query "last 2 versions" sounds modern but is actually broader than many developers realize: it includes every browser's last two major versions regardless of their usage share, pulling in versions that may cover less than 0.1% of traffic.

The practical effect of changing browserslist from an IE11-inclusive to an IE11-exclusive configuration on a medium-sized web application: Babel no longer emits function declarations to replace arrow functions, no longer emits var in place of let/const, no longer wraps async functions in regenerator runtime code, and Autoprefixer no longer generates -ms- vendor prefixes for CSS Grid and flexbox properties. The resulting code is smaller, faster to parse, and significantly easier to debug because it resembles the source code the developer wrote.

The browserslist ecosystem also provides tooling to evaluate configuration choices before committing to them. Running npx browserslist "defaults, not IE 11" in a terminal outputs the full list of covered browser versions; visiting Browserslist.dev and entering a query shows the percentage of global sessions covered. The global coverage percentage is the key calibration metric: a query that covers 95% of sessions is appropriate for most consumer applications; one covering 90% might be appropriate for developer-facing tools where the audience skews toward modern browsers.

For library authors, browserslist interacts differently than for application authors. Libraries don't typically have a browserslist config because they ship source or a compiled artifact and leave the polyfilling to the consuming application. When a library author announces a target change — "we now require ES2020 minimum" — they are documenting the minimum browser environment that their published output assumes. If the consuming application's browserslist config includes browsers that don't support ES2020, the application build process must transpile the library's output or the users of those browsers will receive broken JavaScript. This is the source of many "dropped support" compatibility issues in practice.

The ESM/CJS Split as a Proxy War for Browser Support

The module format split in the npm ecosystem — ES Modules (ESM) versus CommonJS (CJS) — is superficially a Node.js compatibility discussion, but it is also a proxy battle for browser support. The two issues are deeply entangled.

CommonJS (require()/module.exports) is the historical Node.js module format. It cannot be used natively in browsers, which have a different module loading model. The ES Module format (import/export) is the standard for both modern browsers and modern Node.js. A package that ships only ESM is making a statement: it requires an environment that understands static imports natively, which means Node.js 12+ and modern browsers with <script type="module"> support.

Internet Explorer 11 does not support ES Modules at all. This is a hard incompatibility — not a polyfillable one. An IE11 user receiving an ESM bundle will see a silent failure: the script simply won't be evaluated because the browser doesn't understand the module syntax. Bundlers like Webpack and Rollup have historically handled this by compiling ESM source to CJS or IIFE output, making ESM source files compatible with IE11-era browsers at build time. But this works only if the bundler sees the ESM source; packages that ship pre-bundled ESM (rather than raw source) can create compatibility gaps that are harder to debug.

When a library ships pure ESM with no CJS fallback — as many modern packages do in 2026 — it implicitly assumes that consumers have a build toolchain that handles ESM natively or that their deployment targets support native ESM. Packages like node-fetch v3, chalk v5, got v12, and most of the sindresorhus ecosystem made this choice deliberately. The practical consequence: these packages cannot be require()d in CommonJS Node.js environments (pre-Node.js v20 without --input-type=module), and they cannot be used in IE11-era browser deployments even with a bundler that doesn't handle the import syntax.

For application developers in 2026, this effectively means: if your bundler is Vite, Rollup, or webpack 5, you handle ESM packages correctly and the distinction rarely surfaces. If you are working in an older toolchain or in a Node.js environment running as CJS, pure-ESM packages are a compatibility wall that requires explicit handling. The ecosystem is in a transition period where both formats coexist, but the direction is clear: ESM is the end state, and CJS support is a backwards-compatibility accommodation that packages will eventually drop.

Which Browser APIs Motivated Specific Library Decisions

The decision to drop IE11 was not abstract for most library maintainers — it was motivated by specific browser APIs they wanted to use natively without wrapping in compatibility shims. Examining which APIs drove which decisions reveals the practical engineering calculus behind the support policy.

The Proxy object was the primary motivation for Immer's continued IE11 incompatibility and the initial impetus to reconsider the framework's browser target. Immer's structural sharing model for immutable updates relies on Proxy to intercept property assignments and build a draft state. Proxy cannot be polyfilled in any meaningful way — there is no ES5 equivalent that replicates its semantics — which meant that Immer was de facto IE11-incompatible even when the rest of the ecosystem still supported it. This was an honest constraint, not a deliberate policy.

WeakRef and FinalizationRegistry, introduced in ES2021, motivated changes in LRU cache implementations and garbage-collection-aware caches. These APIs let JavaScript code hold a weak reference to an object and receive a callback when the object is garbage collected — enabling cache implementations that don't prevent their stored objects from being reclaimed. This is not polyfillable and requires modern engine support.

structuredClone, available natively in Node.js 17+ and modern browsers since 2022, is a deep-clone function that correctly handles circular references, Date objects, Map, Set, ArrayBuffer, and typed arrays — all of which JSON.parse(JSON.stringify()) cannot handle. Once structuredClone became universally available in target environments, utility libraries that previously shipped custom deep-clone implementations had a strong incentive to drop the custom code in favor of the native API. Dropping the custom implementation means dropping the bugs, edge cases, and maintenance burden of that code.

ResizeObserver — now used extensively in layout-aware components, virtual scrollers, and responsive containers — was not available in IE11 and required a polyfill that was expensive to ship and imperfect in its behavior. Once major browser targets all included ResizeObserver natively, UI component libraries were able to drop the polyfill and get correct behavior across their entire target range. React 19's removal of legacy browser support accelerated this for the entire React component ecosystem.

The Enterprise IE11 Holdout Problem

The narrative that IE11 was effectively zero usage by 2023 was accurate for consumer-facing web applications but missed a significant and economically important segment: large enterprise and government environments with locked-down managed device fleets.

Group Policy settings in Windows corporate environments can prevent browser updates and lock employees to specific browser versions. An organization that deployed Windows 10 in 2019 and set IE11 as the default browser via Group Policy had a non-trivial internal user base on IE11 well into 2024. These users were not accessing consumer websites from IE11; they were accessing internal line-of-business applications — expense reporting, HR portals, procurement systems — that were built against IE11 as the target and had not been updated. Microsoft's withdrawal of IE11 support in June 2022 and the subsequent enforcement of Microsoft Edge as the Windows 11 default browser accelerated the organizational transition, but enterprise IT moves on multi-year timescales.

This explains why some applications were "still supporting IE11" as recently as 2024 and why the transition was a business negotiation, not a technical one. The technical staff at most large organizations knew IE11 was obsolete; the constraint was the pace at which device fleets could be updated, Group Policy configs could be revised, and internal applications could be validated against Edge. The 0.25% global IE11 traffic share understates its presence in enterprise internal tool contexts because those applications don't appear in public web analytics.

The practical consequence for library authors: a library that dropped IE11 in 2022 or 2023 was making a forward-looking decision that was correct for the consumer web but required enterprise applications to either pin to an older version or manage compatibility at the application build layer. Libraries that provided an @vitejs/plugin-legacy-compatible build path or continued shipping an ES5 build alongside the modern one were offering a migration ramp that genuinely served this segment. By 2026, that enterprise holdout population has largely transitioned — the IE11 access pattern in server logs has dropped below the noise floor even for enterprise applications — and the ecosystem has moved on without meaningful controversy.


Compare package browser compatibility on PkgPulse — build targets and browser support data included.

See also: Lit vs Svelte and AVA vs Jest, 20 Fastest-Growing npm Packages in 2026 (Data-Backed).

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.