Understanding Baseline and Feature Availability States
In modern web development, dependencies are often installed to fill platform gaps, only to remain in package.json long after browsers natively implement those capabilities. In a typical mid-sized JavaScript application, between 60KB and 90KB (minified and gzipped) of dependencies perform tasks that the web platform now handles natively.
To safely evaluate whether a dependency can be removed, developers can rely on Baseline, an initiative from the WebDX Community Group. Baseline maps browser compatibility across Chrome, Edge, Firefox, and Safari into three distinct status tiers:
- Limited availability: The feature has not yet shipped across all major browser engines and is unsafe to rely on without fallback logic or polyfills.
- Baseline Newly available: The feature has recently landed in all major engines. It operates reliably for users on updated browsers, but older devices may still lack support.
- Baseline Widely available: The feature has been supported across all major engines for at least 30 months. At this stage, it can be broadly implemented with minimal risk.
Browser feature statuses can be verified directly on webstatus.dev, via Baseline badges on MDN reference pages, or programmatically using the web-features npm package.
A Three-Question Decision Framework Before Deleting Libraries
Removing a library simply because a native equivalent exists can introduce regressions or break accessibility. Before removing any dependency, run it through this three-part evaluation framework:
- Is the replacement Baseline-safe for my audience? Distinguish between overall Baseline status and your specific audience requirements. A “Widely available” feature is generally safe. A “Newly available” feature requires checking user analytics or your
browserslistconfig. A public-facing site with legacy Android traffic demands a higher safety threshold than an internal B2B dashboard on evergreen browsers. - What does the swap actually cost? If a native feature requires a heavy polyfill for unsupported browsers, loading that polyfill unconditionally can increase your bundle size beyond the weight of the original library.
- Does the platform feature cover my real use case? High-level libraries often bundle secondary functionality. For example, replacing
axioswith nativefetchmeans managing HTTP error status rejections and request interceptors manually. Verify what functions your codebase actually invokes before removing the package.
Cluster 1: Native Internationalization via the Intl Namespace
Internationalization utilities frequently accumulate hidden bundle weight. The native Intl namespace now replaces several standalone packages with features that are largely Baseline Widely available.
Key package replacements in this cluster include:
timeago.js(1 KB gz) →Intl.RelativeTimeFormatpluralize(2.3 KB gz) →Intl.PluralRulesnumeral(3.9 KB gz) →Intl.NumberFormathumanize-duration(6.6 KB gz) →Intl.DurationFormat- List-joining utility helpers →
Intl.ListFormat
For relative time formatting, Intl.RelativeTimeFormat handles localized output directly:
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
rtf.format(-1, "day"); // "yesterday"
rtf.format(3, "hour"); // "in 3 hours"
rtf.format(-2, "week"); // "2 weeks ago"
Unlike timeago.js, Intl.RelativeTimeFormat requires you to pass the calculated unit explicitly. Determining whether to pass seconds, hours, or days requires a brief arithmetic calculation in your helper function.
For numbers, currencies, and compact formats, Intl.NumberFormat provides robust localized output:
new Intl.NumberFormat("en-US").format(1234567.89); // "1,234,567.89"
new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(1234.5); // "$1,234.50"
new Intl.NumberFormat("en", { notation: "compact" }).format(1200000); // "1.2M"
To join arrays into grammatically correct localized lists, use Intl.ListFormat:
const lf = new Intl.ListFormat("en", { style: "long", type: "conjunction" });
lf.format(["Alice", "Bob", "Carol"]); // "Alice, Bob, and Carol"
Caveat: While Intl.RelativeTimeFormat, Intl.NumberFormat, and Intl.ListFormat are Widely available, Intl.DurationFormat achieved Baseline Newly available status in March 2025 (and is on track for Widely available status in 2027). Public applications supporting older devices must check audience support or provide a fallback before removing humanize-duration.
const df = new Intl.DurationFormat("en", { style: "long" });
df.format({ hours: 1, minutes: 30 }); // "1 hour, 30 minutes"
Cluster 2: Modernizing HTTP Requests with Fetch and AbortSignal
Popular HTTP clients like axios (17 KB gz) and superagent (19 KB gz) are frequently imported for basic GET and POST operations that native fetch and AbortController handle natively.
A basic request migration looks like this:
// axios
const { data } = await axios.get("/api/users");
// fetch
const res = await fetch("/api/users");
const data = await res.json();
For request timeouts, native fetch accepts AbortSignal.timeout():
const res = await fetch("/api/users", {
signal: AbortSignal.timeout(5000), // aborts after 5 seconds
});
Where Fetch Requires Architectural Work
Before replacing axios, note key architectural differences:
- HTTP Errors:
fetchdoes not reject promises on 404 or 500 status codes; developers must checkres.okmanually. - Interceptors:
fetchlacks built-in request/response interceptors. Handling auth token attachment or centralized 401 handling requires wrappingfetchin a custom class or wrapper function. - Automatic Retries: Retry mechanisms must be explicitly implemented.
- Upload Progress: Native
fetchdoes not provide first-class upload progress callbacks. File uploaders with progress indicators may still justify keeping an HTTP library.
Cluster 3: Native UI Primitives: Dialogs, Popovers, and CSS Anchor Positioning
UI utilities often add substantial bundle size to tackle accessibility, focus trapping, top-layer rendering, and positioning. Native web platform additions streamline or eliminate these dependencies:
- Modal libraries (e.g.,
a11y-dialog, 1.8 KB gz) - Tooltip/Popover libraries (e.g.,
tippy.js, 14 KB gz) focus-trap(6.6 KB gz)body-scroll-lock(1.3 KB gz)
Modal Management with the <dialog> Element
The native <dialog> element is Baseline Widely available. Calling showModal() automatically traps focus inside the modal, sets external elements to inert, listens for the Escape key to close, restores focus to the triggering element upon closure, and renders in the browser’s top layer:
<dialog id="confirm">
<form method="dialog">
<p>Delete this file?</p>
<button value="cancel">Cancel</button>
<button value="delete">Delete</button>
</form>
</dialog>
<script>
const dialog = document.querySelector("#confirm");
dialog.showModal();
dialog.addEventListener("close", () => {
console.log(dialog.returnValue); // "cancel" or "delete"
});
</script>
To prevent background scrolling while a modal is open, use the native :modal pseudo-class in CSS, avoiding JavaScript-based scroll locks entirely:
body:has(dialog:modal) {
overflow: hidden;
}
Floating Panels with Popover API and CSS Anchor Positioning
For non-modal floating overlays, dropdowns, and tooltips, the Popover API provides top-layer rendering and native light-dismiss behavior without JavaScript event listeners (Baseline Newly available since January 2025):
<button popovertarget="menu" id="options">Options</button>
<div id="menu" popover>
<!-- menu content -->
</div>
To handle element positioning natively without utility scripts like Popper, CSS Anchor Positioning allows pinning floating elements directly to trigger targets (Baseline Newly available since January 2026, following Firefox 147 release):
#options {
anchor-name: --trigger;
}
.tooltip {
position-anchor: --trigger;
position-area: top;
margin: 0;
}
Cluster 4: Replacing Utility Functions with Native JavaScript Features
While importing full libraries like lodash (25 KB gz) is less common today, micro-utility packages (or single imports like lodash.clonedeep and lodash.groupby) still populate modern codebases unnecessarily.
Array Grouping
Use Object.groupBy or Map.groupBy (Baseline Newly available since March 2024, targeting Widely available status in late 2026):
const products = [
{ name: "Apple", category: "fruit" },
{ name: "Carrot", category: "vegetable" },
{ name: "Banana", category: "fruit" },
];
const grouped = Object.groupBy(products, (product) => product.category);
// Output:
// {
// fruit: [{ name: "Apple", ... }, { name: "Banana", ... }],
// vegetable: [{ name: "Carrot", ... }]
// }
Deep Cloning
Use structuredClone (Baseline Widely available) to duplicate objects, handling nested references, Date, Map, Set, ArrayBuffer, and circular references safely:
const original = { user: { name: "Sam", roles: ["admin"] } };
const copy = structuredClone(original);
copy.user.roles.push("editor");
console.log(original.user.roles); // ["admin"] (unchanged)
Limitations: structuredClone throws an error on functions or DOM nodes and drops prototype chains on class instances. Keep lodash.clonedeep only if cloning prototypes or methods is strictly required.
Native Set Operations
Instead of manual array loops or utility methods, native Set methods (Baseline Newly available since June 2024) offer direct set mathematics:
const admins = new Set(["sam", "alex", "jo"]);
const editors = new Set(["alex", "kim"]);
admins.intersection(editors); // Set { "alex" }
admins.union(editors); // Set { "sam", "alex", "jo", "kim" }
admins.difference(editors); // Set { "sam", "jo" }
Available native Set methods include union, intersection, difference, symmetricDifference, isSubsetOf, isSupersetOf, and isDisjointFrom.
Note: Utilities such as debounce and throttle have no direct native equivalent and remain standard candidates to retain via targeted packages like lodash.debounce.
Cluster 5: Case Study: Why You Shouldn’t Replace Day.js with Temporal Yet
Not all modern platform APIs are ready for instant replacement. Analyzing the modern Temporal API provides a critical practical demonstration of when to delay dropping a library.
The Temporal API reached TC39 Stage 4 in March 2026 and is included in the ES2026 specification. Firefox shipped support in version 139 (2025) and Chrome in version 144 (January 2026). However, Safari has only introduced support in Safari Technology Preview, meaning Temporal is currently in Limited availability status.
Applying our 3-question evaluation framework:
- Audience Safety: Temporal is not Baseline. Relying on it requires a polyfill for widespread production use.
- Cost Evaluation: The official
@js-temporal/polyfillweighs approximately 44 KB gzipped (with lighter alternative variants weighing ~19 KB gzipped). Replacing a lightweight library likedayjs(3 KB gz) with Temporal plus its polyfill increases your bundle footprint by roughly 41 KB gzipped. - Feature Set: Temporal offers superior date math and timezone handling, but the polyfill overhead negates performance gains.
Verdict: Retain libraries like dayjs or date-fns until Temporal completes stable Safari rollout and achieves Baseline status across all engines.
Step-by-Step Dependency Audit Workflow
Integrate dependency auditing into your quarterly maintenance workflow using this systematic process:
- List Production Dependencies: Filter out build-time tools to isolate packages shipped directly to browsers:
npm ls --omit=dev --depth=0 - Measure Bundle Impact: Use Bundlephobia for quick package weight evaluations, or inspect tree-shaken production outputs locally using
source-map-explorerorvite-bundle-visualizer. - Verify Baseline Status: Cross-reference candidates on webstatus.dev or check MDN documentation badges.
- Apply the 3-Question Framework: Verify safety for your target audience, account for polyfill overhead, and confirm feature overlap.
- Implement Progressive Enhancement: For features in “Newly available” status, wrap platform calls in feature detection guards before removing dependencies:
if (typeof Intl.DurationFormat === "function") {
// Execute native platform feature
} else {
// Fall back to simplified custom formatter or utility function
}
Summary of Bundle Weight Reduction
Systematically auditing production dependencies yields substantial savings across typical JavaScript application bundles:
- Internationalization Cluster: ~14 KB gzipped savings
- HTTP Client Cluster: ~17 KB gzipped savings
- UI Primitives Cluster: ~24 KB gzipped savings
- Lodash/Utility Cluster: ~8 KB+ gzipped savings
Executing an audit across these clusters removes 60 KB to 90 KB gzipped (or 180 KB to 270 KB of uncompressed code) from client-side bundles, directly improving page load times and execution performance.
Frequently asked questions
What is WebDX Baseline?
Baseline is an initiative by the WebDX Community Group that tracks browser feature compatibility across Chrome, Edge, Firefox, and Safari, categorizing web platform features into Limited availability, Baseline Newly available, and Baseline Widely available (supported for 30+ months across engines).
How much bundle size can be saved by replacing libraries with native browser APIs?
A typical mid-sized JavaScript application can save roughly 60KB to 90KB gzipped by replacing date/number formatters, HTTP wrappers, popovers, focus traps, deep clone scripts, and array helpers with native web APIs.
Why shouldn't dayjs or date-fns be replaced with the Temporal API yet?
Temporal is currently in Limited availability and lacks stable support in Safari. Relying on a Temporal polyfill adds roughly 19KB to 44KB gzipped to your bundle, which is significantly heavier than lightweight libraries like dayjs (3KB gzipped).
What native features replace Lodash utility functions?
Native alternatives include Object.groupBy and Map.groupBy for array grouping, structuredClone for deep object cloning, and native Set methods (such as union, intersection, and difference) for set mathematics.
Primary reference: Review the original announcement for exact release details. This article is an independent explanation and does not reproduce the source text.
