The Hidden Complexity Behind a Movable Chrome Extension Button
When this feature first came up in planning, nobody thought twice about it. “Users should be able to drag the extension button anywhere on the screen” sounded like an afternoon’s work, grab the element, track the mouse, done. It didn’t turn out that way. By the time the button behaved the way it should, it had quietly become one of the more interesting frontend problems we’d worked through on Prompt Optimise.
Why This Wasn’t a Normal Drag-and-Drop Problem
A Chrome extension doesn’t run inside an app we control. It runs on top of somebody else’s website, a different one every time the user switches tabs. Each of those sites brings its own CSS, its own scroll behavior, its own DOM that can rewrite itself at any moment. A drag handler that works perfectly on a clean test page can fall apart the moment it meets a site with aggressive layout shifts or a sticky header eating half the screen.
So the button had a longer list of requirements than “follow the cursor.” It needed to move without any perceptible lag, stay inside the visible viewport no matter what, remember where the user left it after a refresh, adapt to different screen sizes, avoid sitting on top of anything important on the page, and keep working even as the host site’s content changed underneath it.
None of those individually sound hard. Together, they exposed the gaps fast. Early versions let the button drift off-screen if you dragged too close to the edge. Resizing the window could leave it stranded in a spot that no longer existed on the visible page. And because click and drag were being handled by roughly the same logic, users would sometimes “click” the button and accidentally nudge it a few pixels, or drag it and have it register as a click instead.
Rethinking the Approach
The fix wasn’t a better drag algorithm, it was a change in how we thought about the button. Instead of treating it as one more draggable div, we treated it as a small, self-contained UI component with its own positioning logic, independent of whatever the host page was doing.
That meant building in a few specific behaviors:
- Clamping the button’s coordinates so it can never end up partially or fully outside the browser viewport, even mid-drag.
- Persisting the final position to local storage, so the user’s chosen spot survives a page refresh instead of resetting every time.
- Recalculating position on window resize, so the button stays sensible on a laptop screen and an ultrawide monitor alike.
- Separating drag events from click events explicitly, with a small movement threshold, so a genuine click never gets swallowed by the drag handler and vice versa.
- Keeping the interaction lightweight – smooth movement without piling on animation work that could introduce lag on heavier pages.
The result behaves less like a widget bolted onto a page and more like something that carries its own rules with it, regardless of what site it happens to be sitting on.
What Actually Stuck With Us
The lesson here was never really about drag-and-drop mechanics. It was about designing UI that stays consistent in an environment we don’t control.
Building a browser extension means building for thousands of different websites at once, each with its own quirks. We can’t assume a clean slate, and we can’t assume the page will hold still. The more self-sufficient the UI is, the less it relies on the host page behaving, the more reliable the extension ends up being in practice, not just in testing.


