Why your AI-built site won't let visitors scroll
I built a site once with a slow, cinematic intro. A terminal booted up on load, green text typing itself line by line, and I was proud of it. I was so proud of it that I did something I now regret: I froze the page while it played. I locked scrolling so a first-time visitor could not skip past the animation. They would sit there and watch the whole thing, because I had decided they should.
On my laptop it worked perfectly. The page held still, the terminal typed, and when it finished the page let go and scrolled like normal. I even wrote a check to confirm the lock was working, and the check passed. Green. I shipped it feeling clever.
Then people started loading it on their phones, and the site was broken in a way I could not reproduce. Some visitors could not scroll at all. Others got stuck partway down and had to reload. The one thing every single report had in common was a phone, and the one thing I could never make happen was the bug, because I was testing on the machine where it did not exist.
why won't my AI-built site scroll on mobile?
The short version is that the browser quietly ignored the code that was supposed to block scrolling, and then, in the cases where it did take hold, never let go.
Here is the mechanism, because it is worth understanding once. To stop the page from scrolling, my code listened for scroll and touch events and called the function that cancels them. On a desktop with a mouse wheel, that mostly works. On a phone, it does not, and the reason is a browser optimization almost nobody thinks about. Touch and wheel listeners are treated as passive by default, which is the browser promising itself that your handler will not cancel the scroll, so it can start scrolling immediately without waiting for your code. When a handler is passive, the cancel call is silently ignored. No error. No warning. The line runs, returns nothing, and the page scrolls anyway. Or worse, the lock half-applies and the release code never fires, because the whole sequence was built on an assumption the browser had already thrown away.
So on my laptop the lock felt solid. On a stranger's phone it was a coin flip between doing nothing and trapping them. Same code, completely different behavior, and the difference was an environment I never actually stood in.
the check that lied to me
The part that still stings is that I had a test, and the test was green, and the test was wrong.
My check dispatched a synthetic scroll event and then asked the browser whether that event had been cancelled. It had. So the check reported that scrolling was blocked, and I believed it. What I did not understand is that a synthetic event you create in code is cancelable in a way a real finger on real glass is not. The passive-listener behavior that breaks the whole thing in production simply does not apply to the fake event. My test was measuring a surface that no visitor would ever touch, and it was measuring it cleanly.
This is the trap under a lot of "it works on my machine" bugs, and it is worth saying plainly. A check that runs inside the same convenient environment as the thing it is checking will confirm the thing is fine, because it is asking the environment that was built to make it fine. The synthetic event and the passing check were both true. They were just true about a world my users do not live in.
verify in the real environment, not the friendly one
The fix for the diagnosis was not cleverer code. It was standing in the right place. I stopped trusting synthetic events and did the boring thing: loaded the actual site on an actual phone-sized viewport, put a finger on it, and watched the scroll position. Not a fabricated event answering a question I had already rigged in its favor. A real drag on a real surface, the exact motion a visitor makes. The position sweep told the truth in about ten seconds. The page moved when it should not have, and froze when it should not have.
That is the whole discipline compressed into one habit. Verify in the environment your users are actually in, with the input they actually use, before you believe any green light. Preview-green is a claim. A real thumb on a real screen is proof. I wrote before about how a build can look done a full step before it is deployable, and this is the same lesson pointed at interaction instead of deployment. The tool that made it feel finished was never the place it had to work.
the fix was to delete the feature
Once I could see it honestly, the decision got easy, and it was not the one I expected. I did not write a better lock. I removed the lock entirely.
Two things made that the right call. First, the feature could not be verified cheaply, and a guardrail you cannot honestly check is not a guardrail, it is a liability wearing a guardrail's clothes. Second, and I should have led with this, the feature was hostile. I was freezing a stranger's page to make them watch an animation I liked. Nobody asked for that. The honest requirement underneath was much smaller than the thing I had built. There was one message a visitor needed to see before they could interact with the page, and I had wrapped it in a scroll trap to force the timing. The message did not need a trap. It needed to be present. So I made the input stay inactive until that message had rendered, and let the page scroll freely from the first second. The requirement was met at the exact point it mattered, and the whole fragile apparatus around it went in the bin. Less code, no lock, nothing to leak.
questions that keep coming up
My AI-built site won't scroll on my phone but works on my computer. What is it? Almost always something intercepting or blocking touch events, and almost always something that behaves differently under the browser's passive-listener rules than it did on desktop. Load it on a real phone and watch the scroll position while you drag. Do not trust a desktop preview or a synthetic test to tell you a touch behavior is fine.
I want a fancy intro animation but I don't want to trap people. Can I have both? Yes, and the rule is simple: the animation plays, the page never stops scrolling. Let a visitor ride past your intro if they want to. An animation that holds someone in place reads as broken even when it is working, because being unable to scroll is the universal signal for a frozen page.
How do I actually test scroll and touch behavior? On the real thing. A narrow real viewport, a real drag, and your eyes on the scroll position. Fabricated events are useful for a lot, but the moment your bug depends on how the browser treats real input, only real input can confirm the fix.
If you have a vibe coded site that behaves on your machine and breaks for real visitors, and you cannot reproduce it because the bug lives in an environment you are not standing in, /work-with-us. Send me the site and what your visitors are seeing, and I will help you reproduce it in the environment that owes you nothing and pin down what the friendly preview is hiding. Work with VibeKoded.
The animation was never the problem. The problem was that I decided the page should stop for me, tested that decision in the one place it could not fail, and shipped a lock that only ever worked in the test.
// part of the ai websites topic
// grab the free starter kit that makes your AI stop forgetting and stop guessing: get it →
// building with AI? the field manual has the structured lessons.
// hitting this on a real build? this is what I fix →