During one front-end cleanup, I found myself looking at a fairly typical jQuery-heavy interface. It had grown in the way many client websites grow. A small interaction had been added here, a plugin had been added there, then a few custom scripts had been layered on top whenever a new section needed to behave differently. Nothing was obviously broken, but the JavaScript was becoming harder to trust.
At the time, my instinct was not to rip jQuery out immediately. That would have been the most satisfying technical decision, but not necessarily the safest one. The site still had working behaviour, and the first job was to understand which parts of that behaviour were genuinely dependent on jQuery and which parts were only using it because that was how the project had started.
Starting With The Existing Behaviour
I began by listing the interactions that already existed. The navigation opened and closed, accordions expanded, a few filters responded to clicks and some form elements had small bits of visual feedback attached to them. I wanted that list before touching the code because it gave me something to test against later. Without that, it is very easy to declare a cleanup successful while quietly removing behaviour somebody was using every day.
The next step was separating plugin-driven code from simple interface code. If a jQuery plugin was doing something complicated, I left it alone initially. If jQuery was being used to add or remove a class, listen for a click or toggle a panel, that became a candidate for plain JavaScript.
Replacing The Small Things First
I started with the smallest interactions because they were easier to verify. A class toggle is a good example. In older projects, I would often see something like this:
$('.menu-toggle').on('click', function () {
$('.site-navigation').toggleClass('is-open');
});
There is nothing wrong with that code in isolation. The question I asked was whether jQuery was still earning its place for this specific behaviour. In this case, the plain JavaScript version was not particularly difficult to read:
const button = document.querySelector('.menu-toggle');
const navigation = document.querySelector('.site-navigation');
if (button && navigation) {
button.addEventListener('click', () => {
navigation.classList.toggle('is-open');
});
}
That was the pattern I followed. I did not convert everything in one pass. I replaced the simple cases, tested them, then moved on to the next section. This kept the work controlled, and it meant I could stop at any point without leaving the interface half-rewritten.
Keeping The Cleanup Boring
The useful part of this kind of work is that it should feel boring. I did not want a dramatic rewrite. I wanted fewer dependencies, clearer event handling and behaviour that could be understood without remembering which jQuery helper did what. The important thing was keeping the interface stable while making the code easier to live with.
Retrospectively, the best decision was treating the cleanup as maintenance rather than a migration. If I had started with the goal of removing jQuery completely, I probably would have created unnecessary risk. By starting with the actual behaviour, I could improve the code where it made sense and leave the more complex parts until there was a better reason to touch them.
That is usually how I approach older JavaScript now. I do not assume old code is bad because it is old. I look at what the interface does, how often that behaviour changes and how much effort it takes to understand it. If replacing a small jQuery interaction makes the project easier to maintain without changing the user experience, that is usually worth doing.