Every core Rebind script pattern in one place — each a minimal, correct snippet. For full signatures see the SDK reference; for the package manifest see Package format.

Style

Rebind scripts are Luau. Follow these conventions — every snippet below does.
  • Indent with 2 spaces. Never tabs, never 4.
  • Naming:
    • Runtime hooks and SDK calls are PascalCase — dictated, not a choice (OnDown, HID.Press, Input.IsDown).
    • Your locals and local functions are snake_case (dwell_start, local function set_virt).
    • Module-level constants are UPPER_SNAKE (local OPPOSITE = {...}).
    • The UI config table is conventionally named cfg.
  • local everything. The only globals are the runtime hooks (OnStart, OnDown, …); declare everything else local, including functions.
  • Double-quoted strings. Use [[ ]] long strings for regex patterns and multi-line text.
  • Stay flat — early return. Guard and return rather than nesting; use a and b or c for a ternary.
  • Types are optional but welcome. Luau is typed; annotate where it clarifies (local count: number = 0), don’t annotate the obvious.
  • Comments: explain why, not what, and stay sparse. Write them in lowercase. Use -- for a single line; for anything longer, use one --[[ ]] block instead of stacking several -- lines.
  • Keep lines to roughly 80 characters. Wrap long calls and tables across lines rather than running well past it.
  • Space after commas and around operators; one statement per line.

Handling input: Bind vs hooks

There are two ways to react to input, and they use opposite blocking defaults. Pick by the verb:
  • Bind — you’re claiming a key. Blocks by default (the original key is swallowed). Intercepts only its declared trigger. Use for remaps and per-key actions.
  • On* hooks — you’re watching the input stream. Pass through by default; return false to swallow. Defining OnDown/OnUp makes Rebind intercept every key. Use when you need to read or transform arbitrary input.
Rule of thumb: specific keys → Bind; reading/transforming the whole stream → hooks. A hook that never blocks anything can add key_block=false to its modeline to observe input without intercepting it.

Bind patterns

Do not put return true/return false inside an Async(...) body passed to Bind — the return value has no effect on propagation there.

Hook patterns

Mouse movement (requires a Rebind device)

Any script that defines OnMove or sets mouse_block=true requires a Rebind device (Rebind Link) — the relay refuses to load it in software mode, because the OS draws the cursor upstream of anything a host process could intercept.
Keep OnMove minimal — it fires on every movement.

Output needs a coroutine

HID.Press, HID.Type, and HID.Combo call Sleep() internally, so they must run inside a coroutine context (Run, Async; OnTick is not one). Inside a Bind action, wrap with Async. HID.Down/HID.Up are non-blocking and work anywhere (hooks included).

Modifiers

Canonical names LCtrl/LShift/LAlt/LWin; shorthands Ctrl/Shift/Alt/ Win/Meta/Cmd/Opt also work in combos. Read live state with Input.GetModifiers(){ ctrl, shift, alt, win } or Input.IsDown("LShift").
Software-mode gotcha: a physical modifier the user is holding is merged into your synthetic output (so a swallowed key re-emitted while Shift is down still carries Shift). That’s correct for remaps, but means a macro that types text while a modifier is held will inherit it. For output that must be clean of physical modifiers, prefer hardware mode (the device isolates the stream) or gate on Input.GetModifiers() first.

Timing and async

Lifecycle and cleanup

Always release what you hold. Cancel tasks, stop audio, and HID.Up any keys you pressed with HID.Down — in both OnStop (script stopped) and OnBlur (target window lost focus).

Targeting a specific app

Restrict a script to one process/window via the modeline. When targeted, always add OnBlur cleanup so held keys release when focus leaves.

Config and UI

Multi-file packages

When a script outgrows one file, make it a package: a folder with a rebind.toml. The [package].entry (default main.luau) is what runs; other files are libraries you require. Only ever run/start the entry, never a library.
require paths use / or . separators and resolve <mod>.luau, <mod>.lua, then <mod>/init.luau. A one-shot helper can exit() (alias for Script.Exit()) when done.

Modeline reference

The -- rebind: comment at the top of a standalone script is its manifest. In a package, rebind.toml owns these and overrides the modeline.