Your game renders a Start button. You click it. Nothing happens. You click again. Still nothing.
Here is the thing that will save you an hour of frustration: a dead button is almost never a broken button. It is a wiring problem. The button exists visually, but the click never reaches a function that can respond.
This guide walks through the six causes in order of how often they appear, each with a copy-paste fix. Start at the top and work down.
Key Takeaways
- Open the browser console first. The error message usually names the exact cause.
- The six causes: script ran too early, innerHTML destroyed the listener, the handler was called instead of referenced, an invisible overlay is blocking the click, the button is drawn on a canvas, and mobile sends touch instead of click.
- You are usually looking for a missing wire, not a broken button.
Check the Console First
Before you touch a single line, open your browser's developer tools and look at the Console tab. In most browsers, that is F12 or right-click and "Inspect."
The console tells you which of the six causes you are dealing with. Two errors show up constantly:
Uncaught ReferenceError: startGame is not defined- the button is calling a function that does not exist, usually after a rename or refactor.TypeError: Cannot read properties of null (reading 'addEventListener')- your script ran before the button existed.
If the console is empty, add a temporary log to confirm your handler is even firing:
document.getElementById('startBtn').addEventListener('click', () => {
console.log('start clicked');
startGame();
});
If "start clicked" never prints, the click is not reaching your handler. That points to one of the six causes below.
Cause 1: The Script Ran Before the Button Existed
JavaScript runs top to bottom. If your script tries to find the button before the browser has finished building the page, getElementById returns null. Calling addEventListener on null throws the exact error above.
This happens a lot in AI-generated code, because the model writes the script in the <head> and the button in the <body>.
Wait until the page is ready. Wrap your setup in DOMContentLoaded:
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('startBtn').addEventListener('click', startGame);
});
Two alternatives work just as well. Move the <script> tag to just before </body>, or guard against a missing element with optional chaining:
document.getElementById('startBtn')?.addEventListener('click', startGame);
Cause 2: innerHTML Destroyed Your Listener
This one is sneaky because the button still renders. Here is the sequence: your code attaches a listener to the button, then later updates part of the page by overwriting a container's innerHTML. Overwriting innerHTML destroys every element inside it, along with all their listeners. The new button looks identical, but it is a fresh element with no handler.
The fix is to not blow away the elements you have already wired up. Use insertAdjacentHTML, which adds new HTML without touching what is already there:
// Instead of: container.innerHTML = '<button id="startBtn">Start</button>';
container.insertAdjacentHTML('beforeend', '<button id="startBtn">Start</button>');
document.getElementById('startBtn').addEventListener('click', startGame);
Or, if you must rebuild the container, re-attach the listener right after you do.
Cause 3: You Called the Handler Instead of Referencing It
This is a one-character bug that hides in plain sight. addEventListener wants a function reference, not a function call.
This version runs alert immediately, once, when the page loads - and never on click:
button.addEventListener('click', alert('clicked')); // wrong
This version runs alert every time you click:
button.addEventListener('click', () => alert('clicked')); // right
The difference is the parentheses. If you see parentheses right after the function name inside addEventListener, the function is being called immediately.
Cause 4: An Invisible Overlay Is Stealing the Click
Sometimes the button is fine and the handler is fine, but another element is sitting on top of the button. When you click, you are actually clicking the overlay, and the button never receives the event.
This is common in AI-generated games where a full-screen <div> for a menu or a fade effect is left covering the game. The overlay may be fully transparent, so you cannot see it.
The fix is to let clicks pass through the overlay with one line of CSS - pointer-events: none:
.overlay {
pointer-events: none;
}
Or fix the stacking order so the button sits above the overlay. Open the inspector, right-click the button, and check which element is actually receiving the click.
Cause 5: Your "Button" Is Drawn on a Canvas
This is the trap AI-generated code falls into most often. If your Start button is drawn inside a <canvas> element, it is not a real button. It is a rectangle of pixels. There is no ID to reach, and getElementById can never find it.
Listen for the click on the canvas itself, convert the position to canvas coordinates with getBoundingClientRect(), then check whether that point lands on the button's rectangle:
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (x > startBtn.x && x < startBtn.x + startBtn.width &&
y > startBtn.y && y < startBtn.y + startBtn.height) {
startGame();
}
});
If you are using a framework, enable interaction explicitly. In Phaser, call button.setInteractive(). In PixiJS, set sprite.interactive to true. The engine will then handle the hit-testing for you.
Cause 6: Mobile Sends Touch, Not Click
A game that works on your laptop and breaks on your phone is usually this cause. Mobile browsers send touch events, and a click handler does not always fire reliably on touch.
Listen for touchend instead, and read the changedTouches array, not touches. On touchend, touches can be empty:
button.addEventListener('touchend', (e) => {
e.preventDefault();
startGame();
});
Do not bind both click and touchstart for the same action. On many phones you will get two events and the game will start twice. Pick one, or use pointerdown, which covers both mouse and touch in modern browsers.
Frequently Asked Questions
Why does my button work once and then stop?
A listener was destroyed or re-attached. The usual cause is innerHTML rebuilding the element, which wipes the old handler. Re-attach the listener after the update, or use insertAdjacentHTML. [INTERNAL-LINK: scoring and state-reset failures -> state reset guide]
Why does the button work on desktop but not mobile?
Because mobile sends touch events, not clicks. Bind touchend (or pointerdown) and read changedTouches. [INTERNAL-LINK: mobile touch controls -> mobile touch guide]
Should I use onclick or addEventListener?
Use addEventListener. It lets you attach multiple handlers, and it gives you a stable reference so you can remove the listener later if you need to. Inline onclick attributes are harder to reuse and easier to lose track of.
Conclusion
A button that does nothing is a wiring problem with a findable wire. Open the console, then check the six causes in order: script timing, listener destruction, a called handler, an invisible overlay, a canvas button, and mobile touch.
Most AI-generated games break on one of these six, and each has a one-line fix. Run the list against your dead button, and you will find the missing wire in minutes.
