Open your package.json and actually read the dependency list. Not the frameworks - the little ones. The modal library. The tooltip library. The scroll-animation library. The deep-clone utility. The date formatter. Be honest: how many of those are solving a problem the browser could not solve when you added them - but can solve perfectly well today?
I would wager a third of the list. Every one of those is worth deleting, and not for tidiness. Each dependency you remove is bundle weight your users stop downloading, an attack surface that leaves your supply chain, and an upgrade you never have to do again. In 2026, the platform quietly grew up - and a lot of our npm installs are now paying rent on space the browser gives away for free.
Here is a tour of the ones I keep deleting.
Modals: delete the modal library → <dialog>
The <dialog> element, opened with showModal(), hands you - for free - the things modal libraries exist to provide: the top layer (it renders above everything, no z-index war), a backdrop you can style, a focus trap, an inert background, and Escape-to-close.
const dialog = document.querySelector("dialog");
openButton.addEventListener("click", () => dialog.showModal());
// Esc-to-close, focus trap, backdrop, top layer — all built in
dialog::backdrop {
background: rgb(0 0 0 / 0.5);
}
That is a whole category of dependency - and its accessibility bugs - gone.
Tooltips, menus, dropdowns: delete the overlay library → Popover API
For non-modal overlays - menus, tooltips, lightweight panels - the Popover API covers the common cases with no JavaScript at all, and pairs with CSS anchor positioning to place the thing next to its trigger:
<button popovertarget="menu">Options</button>
<div id="menu" popover>
<!-- top layer, light-dismiss, Esc-to-close: free -->
</div>
Rule of thumb worth remembering: <dialog> for modal interactions, popover for non-modal overlays. Pick the right one and you have replaced two or three libraries with two HTML attributes.
Scroll effects: delete AOS / ScrollTrigger → CSS scroll-driven animations
Reveal-on-scroll, progress bars, parallax - the reason we pulled in scroll-listener libraries. CSS scroll-driven animations now do it in pure CSS, off the main thread, running in current Chrome and Safari:
.reveal {
animation: fade-in linear both;
animation-timeline: view();
}
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(2rem);
}
}
No scroll listeners, no layout thrash, no library. And because it degrades to “element is simply visible”, it is safe to ship today.
The utilities you forgot were built in
Not everything is a UI widget. Some of the most common dependencies are tiny utilities the language absorbed years ago and we never went back to remove:
lodash.cloneDeep→structuredClone(obj). A deep clone, built in, handling dates, maps, sets, and cycles.- A date/number formatting library →
Intl.Intl.DateTimeFormat,Intl.NumberFormat,Intl.RelativeTimeFormat,Intl.ListFormat- locale-aware formatting, zero install, already in the browser. - A visibility/lazy-load library →
IntersectionObserver. “Is this element on screen?” natively, without scroll math. - jQuery-era selection and layout hacks →
:has(), container queries,fetch. The parent selector we waited fifteen years for is here; a great deal of layout JavaScript is now CSS.
These are the quiet wins - single-function packages that add a supply-chain link for something the platform does in one call.
When NOT to delete
Let me be responsible about this, because “delete all dependencies” is its own kind of dogma. Do not rip a battle-tested library out of working production code just to feel minimal. The rule I use:
- Delete when the dependency exists only to do the now-native thing - a package whose entire job is “render a modal” is pure liability once
<dialog>exists. - Delete on new code by default - reach for the platform first; add a library only when you hit a real limitation.
- Keep the library when it genuinely adds value beyond the primitive - a rich date library with time-zone math and parsing is more than
Intl; a full animation library does things scroll-driven CSS cannot. The primitive replaces the thin wrappers, not the deep tools.
The goal is not zero dependencies. It is that every remaining dependency earns its place.
Why each deletion pays you back forever
A deleted dependency is not a one-time cleanup - it is a recurring cost you stop paying:
- Bytes, on every single page load, for every user, forever. This is the same argument as my case for minimalistic web development: every kilobyte counts because you ship it a million times.
- Vulnerabilities. Every package is a door into your supply chain. The most secure dependency is the one that is not there - it cannot have a CVE, cannot get hijacked, cannot ship a malicious update at 2 a.m.
- Upgrade churn. Every dependency is a future
npm auditwarning, a breaking major, an afternoon lost to a migration guide. Delete it and that afternoon is yours again, permanently.
The estate sale
There is a particular freedom in clearing out a house full of things you bought for problems you no longer have. The bread machine you used twice. The specialized gadget for a task your normal knife does fine. Each one made sense the day you bought it - and each one has been quietly taking up space, needing dusting, ever since.
Your package.json is that house. Every dependency arrived to solve a real problem on a real day. But the platform kept improving underneath you, and now a surprising number of those gadgets are doing a job your kitchen does natively. Walk the shelves. For each one, ask the honest question: if I did not already own this, would I install it today? When the answer is no, hold the delete key without sentiment.
A lean package.json is not minimalism for its own sake. It is a house where everything left on the shelf is there because you would buy it again - and nothing is quietly charging you rent for a problem the browser already solved.