Stop Using JavaScript Libraries for These 15 Things
Your browser already has more built-in features than you think. Let's be honest: how many npm packages have you installed without checking whether JavaScript or the browser could already do the same thing? Need to dete
Your browser already has more built-in features than you think.
Let's be honest: how many npm packages have you installed without checking whether JavaScript or the browser could already do the same thing?
Need to detect when an element enters the viewport? Install a library.
Need to copy text? Install a package.
Need to clone an object? Install Lodash.
Need to make HTTP requests? Install Axios.
Need to format dates? Install Moment.js.
But here's the thing:
Modern browsers have evolved. And some of the dependencies we used to consider essential are no longer necessary for everyday tasks.
The Web Platform provides powerful native APIs that can help you reduce dependencies, simplify your code, improve performance, and keep your bundle smaller.
In this article, we'll explore 15 things you can do without installing another npm package.
Let's get into it.
1. Stop Using jQuery for DOM Selection
Instead of: jquery
Remember when almost every JavaScript project started with jQuery?
Today, modern browsers provide native DOM methods that handle most everyday selection and manipulation tasks.
Before:
const element = $("#app");
element.addClass("active");
element.text("Hello World");
After:
const element = document.querySelector("#app");
element.classList.add("active");
element.textContent = "Hello World";
Need to select multiple elements?
const buttons = document.querySelectorAll("button");
buttons.forEach((button) => {
button.addEventListener("click", () => {
console.log("Clicked!");
});
});
Need to find the closest parent matching a selector?
const card = event.target.closest(".card");
Why use the native API?
- No additional dependency.
- Direct access to browser DOM functionality.
- Less abstraction for simple operations.
Keep jQuery if: You're maintaining a legacy application or relying heavily on its ecosystem and plugins.
2. Stop Using Axios for Simple HTTP Requests
Instead of: axios
The Fetch API is built directly into modern browsers.
You don't need an external library just to make an HTTP request.
Before:
import axios from "axios";
const response = await axios.get("/api/users");
console.log(response.data);
After:
const response = await fetch("/api/users");
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const users = await response.json();
console.log(users);
Posting JSON is straightforward too:
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Hamed",
role: "Developer",
}),
});
const result = await response.json();
One important difference: Fetch doesn't reject its Promise for HTTP errors such as 404 or 500. You must check response.ok.
Keep Axios if: You need interceptors, automatic request/response transformations, extensive client configuration, or an existing shared HTTP infrastructure.
3. Stop Using Lodash for Deep Cloning
Instead of: _.cloneDeep()
JavaScript now has a native structuredClone() function.
It handles many types that JSON-based cloning cannot correctly preserve.
Before:
import cloneDeep from "lodash/cloneDeep";
const copy = cloneDeep(originalObject);
After:
const copy = structuredClone(originalObject);
For example:
const original = {
name: "Hamed",
createdAt: new Date(),
tags: new Set(["JavaScript", "React"]),
scores: new Map([
["JavaScript", 95],
["React", 90],
]),
};
const copy = structuredClone(original);
console.log(copy.createdAt instanceof Date); // true
console.log(copy.tags instanceof Set); // true
console.log(copy.scores instanceof Map); // true
It also supports circular references.
Important: structuredClone() cannot clone functions or DOM nodes. It also doesn't preserve custom class prototypes and methods.
Keep Lodash if: You need specialized cloning behavior or support for values that structured cloning cannot handle.
4. Stop Using Scroll Event Libraries for Visibility Detection
Instead of: lazysizes, custom scroll listeners, or visibility plugins.
Want to know when an element enters the viewport?
Meet IntersectionObserver.
It allows you to observe elements without manually checking their positions on every scroll event.
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log("Element is visible!");
entry.target.classList.add("visible");
observer.unobserve(entry.target);
}
});
});
document.querySelectorAll(".animate-on-scroll").forEach((element) => {
observer.observe(element);
});
Useful for:
- Lazy loading.
- Infinite scrolling.
- Scroll-triggered animations.
- Analytics visibility tracking.
For ordinary image lazy loading, you might not even need JavaScript:
<img src="image.jpg" loading="lazy" alt="Example" />
Keep a library if: You need complex scroll choreography, advanced animation timelines, or sophisticated visibility management.
5. Stop Using Resize Event Hacks for Element Sizing
Instead of: Custom resize listeners and element-resize plugins.
The ResizeObserver API lets you monitor changes to an individual element's dimensions.
Traditional approach:
window.addEventListener("resize", () => {
// Check element dimensions manually
});
Native approach:
const observer = new ResizeObserver((entries) => {
entries.forEach((entry) => {
console.log("Width:", entry.contentRect.width);
console.log("Height:", entry.contentRect.height);
});
});
const container = document.querySelector(".container");
observer.observe(container);
Why is this better?
Because an element can change size without the browser window changing size.
For example:
- A sidebar opens.
- A parent container changes dimensions.
- A grid layout changes.
- Content is dynamically inserted.
ResizeObserver handles these cases.
Keep a library if: You need higher-level responsive layout behavior or complex element measurement utilities.
6. Stop Using Moment.js for Basic Date Formatting
Instead of: moment() or basic date-formatting packages.
JavaScript already provides the Intl API.
Need to format dates based on a user's locale?
const date = new Date("2026-11-15T12:30:00");
const formatted = new Intl.DateTimeFormat("en-US", {
dateStyle: "full",
timeStyle: "short",
}).format(date);
console.log(formatted);
Want a different locale?
console.log(
new Intl.DateTimeFormat("de-DE").format(date)
);
console.log(
new Intl.DateTimeFormat("fa-IR").format(date)
);
console.log(
new Intl.DateTimeFormat("ja-JP").format(date)
);
You can also format relative time:
const relativeTime = new Intl.RelativeTimeFormat("en", {
numeric: "auto",
});
console.log(relativeTime.format(-2, "day"));
// 2 days ago
And currency:
const price = new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
});
console.log(price.format(1299.99));
// $1,299.99
Keep a date library if: You need advanced date arithmetic, parsing, recurring schedules, or complex timezone operations. Intl formats dates; it isn't a complete date manipulation library.
7. Stop Installing UUID Packages for Basic IDs
Instead of: uuid
If you only need a random UUID v4, the browser already provides one.
const id = crypto.randomUUID();
console.log(id);
Example output:
f47ac10b-58cc-4372-a567-0e02b2c3d479
You can use it for:
- Temporary identifiers.
- Client-side records.
- UI keys.
- Local application data.
Important: crypto.randomUUID() requires a secure context in browsers (usually HTTPS or localhost).
Keep the UUID package if: You need other UUID versions, custom generation, or support for environments without the native API.
8. Stop Using Libraries for Query String Manipulation
Instead of: qs or manually splitting URL strings.
JavaScript provides URL and URLSearchParams.
Need to read query parameters?
const params = new URLSearchParams(window.location.search);
const search = params.get("search");
const page = params.get("page");
console.log(search);
console.log(page);
Need to create a URL?
const url = new URL("https://example.com/products");
url.searchParams.set("category", "laptops");
url.searchParams.set("page", "2");
console.log(url.toString());
Need to remove a parameter?
url.searchParams.delete("page");
Need to check whether a parameter exists?
if (url.searchParams.has("category")) {
console.log("Category filter exists");
}
Keep a library if: You need complex nested query structures or serialization rules that URLSearchParams doesn't provide.
9. Stop Using Clipboard Libraries for Copy Buttons
Instead of: clipboard.js
Copying text is now a built-in browser capability.
async function copyText(text) {
try {
await navigator.clipboard.writeText(text);
console.log("Copied successfully!");
} catch (error) {
console.error("Clipboard failed:", error);
}
}
copyText("Hello from JavaScript!");
A practical React example:
function CopyButton({ text }) {
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(text);
alert("Copied!");
} catch {
alert("Unable to copy text");
}
};
return (
<button onClick={handleCopy}>
Copy
</button>
);
}
Important: Clipboard access generally requires HTTPS or localhost and may require user interaction or permission.
Keep a library if: You need extensive legacy-browser fallbacks or advanced clipboard formats.
10. Stop Using Modal Libraries for Simple Dialogs
Instead of: Basic modal implementations with custom JavaScript.
HTML has a native <dialog> element.
<button id="openModal">Open Modal</button>
<dialog id="myDialog">
<h2>Hello!</h2>
<p>This is a native HTML dialog.</p>
<form method="dialog">
<button>Close</button>
</form>
</dialog>
JavaScript:
const dialog = document.querySelector("#myDialog");
document.querySelector("#openModal").addEventListener("click", () => {
dialog.showModal();
});
You automatically get:
- Native modal behavior.
- Escape-key dismissal.
- Focus management.
- A top-layer rendering mechanism.
- A customizable backdrop.
You can style the backdrop too:
dialog::backdrop {
background: rgb(0 0 0 / 60%);
backdrop-filter: blur(4px);
}
Keep a library if: You need advanced animations, complex nested dialogs, or highly customized interaction patterns.
11. Stop Using Libraries for Simple Tooltips and Popovers
Instead of: Basic dropdown and popover implementations.
The native Popover API makes simple interactive UI elements much easier.
<button popovertarget="myPopover">
Open Menu
</button>
<div id="myPopover" popover>
<p>Hello from the Popover API!</p>
</div>
That's it.
No JavaScript event listeners required.
You can also control it programmatically:
const popover = document.querySelector("#myPopover");
popover.showPopover();
popover.hidePopover();
popover.togglePopover();
Useful for:
- Simple menus.
- Share panels.
- Contextual content.
- Lightweight popovers.
Important: The Popover API handles showing and dismissing elements, not all positioning or tooltip behavior. For sophisticated positioning, Floating UI is still valuable.
12. Stop Using Custom Event Listeners for DOM Changes
Instead of: Polling the DOM or repeatedly checking whether an element exists.
Meet MutationObserver.
It lets you observe changes to the DOM tree.
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
console.log("DOM changed:", mutation.type);
});
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
});
You can also watch a specific element:
const target = document.querySelector("#app");
const observer = new MutationObserver(() => {
console.log("Something changed inside #app");
});
observer.observe(target, {
childList: true,
subtree: true,
});
Useful for:
- Browser extensions.
- Third-party DOM integrations.
- Monitoring dynamically inserted content.
- Integrating with legacy interfaces.
Keep a library if: You need a higher-level abstraction for complex DOM synchronization.
13. Stop Using Libraries for Basic Animations
Instead of: Installing an animation library for simple transitions.
CSS already handles many UI animations.
.card {
opacity: 0;
transform: translateY(20px);
transition: opacity 300ms ease, transform 300ms ease;
}
.card.visible {
opacity: 1;
transform: translateY(0);
}
For animations controlled directly through JavaScript, use the Web Animations API:
const element = document.querySelector(".card");
element.animate(
[
{
opacity: 0,
transform: "translateY(20px)",
},
{
opacity: 1,
transform: "translateY(0)",
},
],
{
duration: 400,
easing: "ease-out",
fill: "forwards",
}
);
You can also control the animation:
const animation = element.animate(
[{ transform: "scale(1)" }, { transform: "scale(1.1)" }],
{
duration: 500,
fill: "forwards",
}
);
animation.pause();
animation.play();
animation.reverse();
Keep a library if: You need complex physics-based animations, gesture-driven interactions, orchestration, or sophisticated layout transitions.
14. Stop Writing Custom Request Cancellation Logic
Instead of: Extra utilities just to cancel outdated requests.
The browser provides AbortController.
This is especially useful when users type into a search box and each keystroke triggers a new request.
let controller;
async function searchUsers(query) {
controller?.abort();
controller = new AbortController();
try {
const response = await fetch(
`/api/users?q=${encodeURIComponent(query)}`,
{
signal: controller.signal,
}
);
if (!response.ok) {
throw new Error("Request failed");
}
return await response.json();
} catch (error) {
if (error.name === "AbortError") {
console.log("Previous request cancelled");
return;
}
throw error;
}
}
Now when a new search begins, the previous request can be cancelled.
You can also set a timeout:
const response = await fetch("/api/data", {
signal: AbortSignal.timeout(5000),
});
Keep a library if: You need advanced retry policies, request queues, caching, or complex network orchestration.
15. Stop Using Libraries for Simple Page Transitions
Instead of: Building custom transition animations for every simple view change.
The View Transition API provides a native way to animate visual changes between page states.
function updateContent() {
const update = () => {
document.querySelector("#content").textContent =
"Welcome to the new page!";
};
if (!document.startViewTransition) {
update();
return;
}
document.startViewTransition(update);
}
You can customize the animation using CSS:
::view-transition-old(root) {
animation: fade-out 200ms ease;
}
::view-transition-new(root) {
animation: fade-in 300ms ease;
}
@keyframes fade-out {
to {
opacity: 0;
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
This is particularly useful for:
- Tab transitions.
- Content replacement.
- Filtered lists.
- Simple page navigation.
- Single-page application transitions.
Keep a library if: You need advanced gesture-driven animations, spring physics, complex shared-element choreography, or extensive animation control.
Bonus: A Quick Comparison Table
Here's a quick summary of everything we've covered.
| What you need | Library you might use | Native alternative |
|---|---|---|
| DOM manipulation | jQuery | DOM API |
| HTTP requests | Axios | Fetch API |
| Deep cloning | Lodash | structuredClone() |
| Visibility detection | lazysizes | IntersectionObserver |
| Element resizing | Resize plugins | ResizeObserver |
| Date formatting | Moment.js | Intl.DateTimeFormat |
| UUID generation | uuid | crypto.randomUUID() |
| Query parameters | qs | URLSearchParams |
| Copy to clipboard | clipboard.js | Clipboard API |
| Modal dialogs | Micromodal | <dialog> |
| Popovers | Tippy.js | Popover API |
| DOM observation | Mutation plugins | MutationObserver |
| Simple animations | Anime.js | CSS / Web Animations API |
| Request cancellation | Custom utilities | AbortController |
| Page transitions | Transition libraries | View Transition API |
So, Should You Stop Using JavaScript Libraries?
Absolutely not.
This isn't an argument against npm, open source, or third-party libraries.
Libraries solve real problems. Some provide better abstractions, more consistent behavior, and features that the browser doesn't offer natively.
The real problem is installing dependencies without understanding what they do.
Before adding another package to your project, ask yourself:
- Does the browser already provide this functionality?
- Does the library offer something beyond the native API?
- Do I actually need that additional functionality?
- Does the dependency justify its maintenance and bundle-size cost?
- What browsers and environments do I need to support?
Sometimes the best dependency is the one you never install.
Learn the platform before adding another package.
What's Your Take?
Let's make this interesting.
What's one npm package you used to install regularly but have almost completely stopped using?
For me, the most interesting shift is how many everyday frontend tasks can now be handled directly by browser APIs.
Drop your answer in the comments. I'd love to see which dependencies developers are removing from their projects.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.