Skip to main content
Picture a notes panel in your overlay: the player presses a keybind, a window with a text field opens, they start typing - and their character sprints into a wall, because every keystroke also went to the game. This guide walks through the fix: open a window via keybind, block game input while it’s open, and hand input back when it closes.
This guide covers the block-while-open workflow. For the full input-blocking API (including granular per-key control), see Blocking Input. For per-window input options, see Render Windows.

How Input Blocking Works

Four things to know before writing any code:
  1. The game keeps receiving input by default. Overlay windows render on top of the game, but they don’t steal input from it - the game still acts on every key press and mouse click unless you explicitly block it. Blocking is always opt-in.
  2. There are two levels of control. Each RenderWindow has its own input flags - hasInput, hasInputBlock, and hasCursorOverride, all enabled by default - that govern input on that window. Separately, the global overlay.input module blocks input to the game as a whole: all mouse input, all keyboard input, or a single key, plus forcing the cursor to show.
  3. Blocks are stateful and manual. Nothing releases a block when your window hides or closes. Every block you set stays active until you clear it yourself.
  4. Scopes make blocks safe across windows. overlay.input.scope(name) returns a handle whose blocks are reference-counted: a block engages when the first scope sets it and releases only after every scope that set it has cleared it.

Blocking Input While a Window Is Open

1

Define the keybind and window

Set up a keybind and a hidden in-game window. This is the same wiring covered in Show/Hide Window Keybinds, so we’ll keep it brief:
main.ts
export const overlay = overlayed({
	keybinds: {
		toggleNotes: {
			keys: ["AltLeft", "KeyN"],
			mode: "toggle",
		},
	},
});
notesWindow.ts
import { overlay } from "./main";

const notesWindow = overlay.windows.createInGameWindow({
	width: 480,
	height: 360,
	show: false, // the keybind will reveal it
});
2

Create an input scope

Get a scoped handle for this window’s blocks. Even if you only have one window today, starting from a scope means a second window can block input later without the two fighting over state:
notesWindow.ts
const inputScope = overlay.input.scope("notesWindow");
Scope names are global identifiers - two calls to overlay.input.scope("notesWindow") return handles to the same scope, so one releasing a block frees the other’s too. Give every window its own unique name.
3

Block on show, release on hide

Wire the keybind to show the window and set the blocks together, and clear everything on hide:
notesWindow.ts
function showNotes() {
	notesWindow.show();

	// Keystrokes no longer reach the game, so typing is safe
	inputScope.setGlobalKeyboardBlock(true);
	// Clicks no longer reach the game
	inputScope.setGlobalMouseBlock(true);
	// Force the cursor to show so the user can click the text field
	inputScope.setGlobalCursorOverride(true);
}

function hideNotes() {
	notesWindow.hide();

	inputScope.setGlobalKeyboardBlock(false);
	inputScope.setGlobalMouseBlock(false);
	inputScope.setGlobalCursorOverride(false);
}

overlay.keybinds.toggleNotes.on("toggle", () => {
	if (notesWindow.isVisible()) {
		hideNotes();
	} else {
		showNotes();
	}
});
Pressing Alt + N now opens the panel with the game deaf to input, and pressing it again hands input back.
Pair every set*(true) with a set*(false) on your hide path. Blocks are not tied to window visibility - if you hide the window without releasing them, the game stays input-blocked with nothing on screen.
Releasing a block your scope never set is harmless - the scope just removes itself from the block’s holders, and the block only lifts when no scope holds it. So an unconditional “reset everything” in your hide handler is always safe, even on paths where you never blocked.

Full Block vs. Opt-In Interaction

Blocking everything while the window is visible is right for windows the user summons briefly. For windows that stay on screen (a scoreboard, a HUD), you usually want them passive by default with an opt-in gesture to interact.
The pattern above: the window’s visibility and the input blocks toggle together. Use this when the window only appears when the user wants to interact with it - settings panels, note editors, search boxes. The window is interactive out of the box (hasInput, hasInputBlock, and hasCursorOverride all default to true), so the global blocks are the only extra wiring.
If a window is display-only and should never react to hovers or clicks, turn its input off entirely with hasInput: false at creation (or setInput(false) later). See Render Windows.

Dismissing on Outside Clicks

While the mouse block is active, Overlayed still receives mouse events - only the game stops seeing them. That lets you close the panel when the user clicks anywhere outside it. On mouseDown events, event.window is unset when the click didn’t land on any overlay window:
notesWindow.ts
import type { MouseButtonEvent } from "@overlayed/app";

function onMouseDown(event: MouseButtonEvent) {
	// 0x01 is left mouse; no event.window means the click was outside every window
	if (event.key === 0x01 && !event.window) {
		hideNotes();
	}
}

function showNotes() {
	// ...blocks from above...
	overlay.windows.on("mouseDown", onMouseDown);
}

function hideNotes() {
	// ...releases from above...
	overlay.windows.off("mouseDown", onMouseDown);
}
See Window Events for the full list of global input events.

Sharing Blocks Across Windows

When several windows can block input, naive bookkeeping breaks: window A releases the cursor override on close, and suddenly window B - still open - loses its cursor. Scopes exist to prevent exactly this. Each block tracks which scopes hold it; the first set*(true) engages it, and it only disengages when the last holder calls set*(false). Give each window its own scope and have it set and release blocks as if it were alone - the reference counting resolves the overlap. The scope handle only has setters. To inspect the effective state actually applied to the game, use the raw getters:
overlay.input.raw.getGlobalKeyboardBlock();
overlay.input.raw.getGlobalMouseBlock();
overlay.input.raw.getGlobalCursorOverride();
overlay.input.raw.getKeyInputBlock(0x04);
overlay.input.raw setters write directly to the game and bypass scope reference counting - a raw setGlobalMouseBlock(false) drops a block another scope still holds. Don’t mix raw setters and scopes for the same override; pick one model per block.

FAQ

Yes. The blocking APIs stop the game from receiving input - they don’t stop Overlayed. Keybinds are captured by Overlayed’s native keyboard hook rather than through the game, so the keybind that opened your window still closes it while setGlobalKeyboardBlock(true) is active. The mouse side works the same way: the Blocking Input reference example listens for mouseDown events while the global mouse block is engaged.
The per-window hasInputBlock option (and setInputBlock) blocks input on that window from passing through to the game - it’s about interactions with the window itself. The global setGlobalKeyboardBlock and setGlobalMouseBlock stop the game from receiving that kind of input entirely, whether or not it’s anywhere near your window. Typing needs the global keyboard block: keystrokes aren’t “on” a window the way clicks are.
If both use scopes, the block engages when the first scope sets it and releases only after both have cleared it - closing one window never yanks input handling out from under the other. If you bypass scopes with overlay.input.raw, the last write wins and you own the coordination problem.
Scope handles are write-only. Read the effective state with the raw getters - overlay.input.raw.getGlobalKeyboardBlock(), getGlobalMouseBlock(), getGlobalCursorOverride(), and getKeyInputBlock(key). These report what is actually applied to the game, regardless of which scope set it.
Yes. The mouse block, keyboard block, cursor override, and per-key blocks are all independent - set any combination. setKeyInputBlock(key, true) blocks a single key while everything else flows to the game, which is the basis of the opt-in interaction pattern above.
No. Blocks set through overlay.input are independent of window lifecycle - hiding, closing, or destroying the window leaves them in place. Always release blocks in the same code path that hides the window. Since releasing a block you never set is a no-op, resetting everything unconditionally is safe.
There are two cursor knobs. The per-window override (hasCursorOverride, on by default) forces the cursor to show when it’s over the window. The global override - setGlobalCursorOverride(true) - forces the cursor game-wide, which is what you want while an interactive panel is open so the user can see the pointer travel to it. If the cursor isn’t appearing at all, set the global override.
VirtualKey codes, exported from @overlayed/app. Note these are not the KeyboardEvent#code strings that keybinds use - they’re numeric codes, like 0x01 for left mouse and 0x04 for middle mouse, as used in the Blocking Input reference examples.

Next Steps

Blocking Input Reference

The full input-blocking API: scopes, raw setters, and per-key blocks.

Render Windows

Per-window input, input block, and cursor override options.

Window Events

Global mouse, keyboard, and resolution events on the windows object.

Show/Hide Window Keybinds

Wire the keybind that summons the window you’re blocking input for.