> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rebind.gg/llms.txt
> Use this file to discover all available pages before exploring further.

# rb.overlay

> Draw toasts, menus, text prompts and status widgets over every app on Windows and macOS.

`rb.overlay` draws toasts, menus, text prompts, status strips, progress bars and
surfaces you lay out yourself. A script reaches for it to show its state, or to
ask for a choice or a line of text, without taking focus from the app in front.
They show over every app on Windows and macOS, including full-screen ones,
except a Windows game in exclusive full-screen mode (see
[Overlay limits](#overlay-limits)). Overlays never take focus or clicks, so the
app underneath keeps working. Every call returns at once; Rebind draws the
latest state.

**Needs:** `min_sdk=3.11.1`.

This script counts F7 presses on a strip at the top of the screen and toasts the
count on F8:

```luau theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
-- rebind: min_sdk=3.11.1 name=example version=1.0.0
local count, status = 0, nil
rb.on("start", function()
  status = rb.overlay.status("count 0")
end)

rb.bind("F7", function()
  count += 1
  status.set({ { "count ", "dim" }, { tostring(count), "accent", true } })
end)

rb.bind("F8", function()
  rb.overlay.toast("count is " .. count, { title = "counter" })
end)
```

| Signature | Returns |
| - | - |
| `rb.overlay.toast(text: OverlayText, options: OverlayToastOptions?) -> OverlayToast` | a handle with `toast.close()` |
| `rb.overlay.menu(items: { OverlayMenuItem }, options: OverlayMenuOptions?) -> (OverlayMenu?, string?)` | a handle with `menu.select(i)` and `menu.close()`, or `nil` and a reason |
| `rb.overlay.prompt(label: string, options: OverlayPromptOptions?) -> (OverlayPrompt?, string?)` | a text field with `prompt.text`, `prompt.set(text)` and `prompt.close()`, or `nil` and a reason; `min_sdk` 3.15.0 |
| `rb.overlay.status(text: OverlayText, options: OverlayWidgetOptions?) -> OverlayStatus` | a handle with `status.set(text)` and `status.remove()` |
| `rb.overlay.progress(label: string, fraction: number?, options: OverlayProgressOptions?) -> OverlayProgress` | a handle with `progress.set(fraction, label?)` and `progress.remove()`; `width` option, default 320 |
| `rb.overlay.surface(options: OverlaySurfaceOptions) -> OverlaySurface` | a handle with `surface:set(fields)`, `surface:draw(ops)` and `surface:remove()` |
| `rb.overlay.measure(text: string, size: number?, bold: boolean?) -> (number, number)` | width and height of one line |
| `rb.overlay.visible() -> boolean` | `false` only when the OS is known to hide overlays |
| `rb.overlay.clear() -> ()` | removes every surface of this script |
| `rb.overlay.themes` | the bundled themes, by name |

Widget handles take a dot (`status.set(text)`); a surface takes a colon
(`surface:set(fields)`). The other form fails or draws the wrong text.

A script's overlays are removed when it stops, errors or reloads, and hidden
while it is inactive because focus left its target window.

## Themes and text

Every widget takes `theme`: `"rebind"` (the default), `"rebind-light"`,
`"catppuccin"`, `"tokyo-night"` or `"contrast"`, or a table with the same
roles. Text is the bundled JetBrains Mono, so columns line up. Text is a string
(a newline starts a line) or a list of spans, each a string or
`{ text, color, bold }`. A color is `"#rrggbb"`, `"#rrggbbaa"` or a theme role:
`"text"`, `"dim"`, `"faint"`, `"accent"`, `"positive"`, `"warning"` or
`"danger"`.

This script shows a toast at start in the `tokyo-night` theme, with its first
span bold in the `warning` role:

```luau theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
-- rebind: min_sdk=3.11.1 name=example version=1.0.0
rb.on("start", function()
  rb.overlay.toast({ { "95%", "warning", true }, " full, heading to hive" }, {
    title = "fishing",
    theme = "tokyo-night",
  })
end)
```

## Placement

Widgets and surfaces take an `anchor`: `"top-left"`, `"top"`, `"top-right"`,
`"left"`, `"center"`, `"right"`, `"bottom-left"`, `"bottom"` or
`"bottom-right"`, inside the work area of a `display`: `"active"` (default, the
monitor holding the foreground window), `"primary"` or `"mouse"`. `offset_x`
and `offset_y` move it inward. An anchored overlay follows the foreground
window to another monitor within half a second. Alternatively, `x` and `y`
place the top-left corner in `rb.screen` coordinates. Sizes are logical units,
scaled for each monitor.

## rb.overlay.toast

Shows for `duration` milliseconds (default 4000). Options: `title` and `meta`
add a heading line; `max_width` caps the width (default 640). Toasts at the
same anchor stack. A toast asked for while `visible()` is `false` waits until
overlays show, or gives up after `expires` milliseconds (default 60000).

## rb.overlay.menu

While open, the menu takes Up, Down, Enter, Escape and 1 to 9, and swallows
them so the app underneath does not see them. Enter or a number picks a row:
the menu closes first unless `keep_open` is set, then the item's
`action(index, item)` runs, then `on_select(index, item)`. Escape closes it,
and so does the script losing focus to a window it does not target or the OS
hiding overlays (checked every 500 ms). `on_close` runs when the menu closes in
any of those ways or by `close()`; `rb.overlay.clear()` and the script stopping
close it without `on_close`. Items are strings or
`{ label, hint, action }`; `hint` shows on the right. Options: `title`,
`subtitle`, `width`. The handle's `select(index)` moves the highlight and
`close()` closes it.

One menu or prompt across all scripts holds the keyboard at a time.
`rb.overlay.menu` returns `nil, "a menu is already open"` while this script has
one open, `nil, "another menu is open"` while another script's menu or prompt is
open or while this script is unfocused, and
`nil, "overlays are hidden"` while `visible()` is `false`. A bind the script
made earlier on one of the menu's keys, and that swallows it, takes the key
before the menu does.

This script opens a two-row menu on F8; picking **Start farming** logs `start`:

```luau theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
-- rebind: min_sdk=3.11.1 name=example version=1.0.0
rb.bind("F8", function()
  rb.overlay.menu({
    { label = "Start farming", action = function() rb.log.info("start") end },
    { label = "Pause", hint = "F5" },
  }, { title = "rebind", subtitle = "quick actions" })
end)
```

## rb.overlay.prompt

**Needs:** `min_sdk=3.15.0`.

`rb.overlay.prompt` shows a one-line text field, for the kind of answer a
file-name dialog or a typed setting would ask for.
It types through swallowing binds, as the menu does, so it takes no focus and the app
underneath keeps its mouse and every key the prompt does not take.

While open, the prompt takes every letter, digit and punctuation key, the
numpad keys that type a character (digits, `/`, `*`, `-`, `+` and `.`, whatever
the Num Lock state), Space, Backspace, Enter, numpad Enter and Escape, and
swallows them. Each
key types its US-layout character, and Shift picks the shifted one. `Ctrl+V`
pastes the clipboard (`Cmd+V` as well on macOS): control characters become
spaces, the text stops at `max_length`, and an empty clipboard does nothing.
In a script that declares permissions, pasting needs the
[`clipboard:read`](https://docs.rebind.gg/reference/sdk/modeline#scoped-permissions)
[permission](https://docs.rebind.gg/reference/sdk/modeline.md#permissions); without
it the chord does nothing. Ctrl, Alt and Win chords on any other key reach the
app.

Enter closes the prompt, then runs `on_submit(text)`. Escape, `close()`, the
script losing focus to a window it does not target, and the OS hiding overlays
close it and run `on_cancel()`. `on_close()` then runs after either.
`rb.overlay.clear()` and the script stopping close it without running any of
them. Options: `title`, `value` (the starting text), `placeholder` (shown while
the field is empty), `max_length` (characters, 1 to 256, default 128), `width`
(default 360, or wider to fit the label and title). The handle's `text` holds what was typed, and `set(text)`
replaces it; `value` and `set` turn control characters into spaces and cut the
text at `max_length`, as paste does. When the text is wider than the field, the field shows its end.

A prompt counts as a menu for the reasons above: it returns `nil, "a menu is
already open"`, `nil, "another menu is open"` or `nil, "overlays are hidden"`
under the same conditions, and a menu cannot open while a prompt is open. An error
raised inside `on_submit` lets the Enter press through to the app, as for a
menu pick.

Limits:

* Key names are physical keys, so a layout other than US types the US
  character for each key. Paste is the way to enter other layouts and Unicode.
* Caps Lock is not read. There is no input method editor and no mouse caret;
  typing and Backspace act at the end of the text.
* Rebind does not dispatch key repeat to binds, so holding a key types or
  deletes once.
* A bind the script made earlier on one of these keys, or a bind or blocking
  handler in a script with a higher [`z_index`](https://docs.rebind.gg/reference/sdk/modeline), takes the key
  before the prompt does.

This script asks for a clip name on F8 and toasts the name when the user presses
Enter:

```luau theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
-- rebind: min_sdk=3.15.0 name=example version=1.0.0
rb.bind("F8", function()
  rb.overlay.prompt("Name this clip", {
    title = "rebind",
    placeholder = "clip name",
    on_submit = function(name)
      rb.overlay.toast(name, { title = "Saved" })
    end,
  })
end)
```

## rb.overlay.surface

Draws a list of ops you lay out yourself, in logical units from the surface's
top-left corner. `width` and `height` are required (1 to 4096, and at most
1,000,000 square units together); `opacity` is 0 to 1; `capturable` is
described under [Overlay limits](#overlay-limits). Ops:

* `{ kind = "rect", x, y, w, h, fill, border, border_width, radius, shadow }`;
  `shadow` draws a soft shadow 8 units below, blurred over 24: `true` is 50%
  black, or give its opacity from 0 to 1. Each theme's `shadow` holds the
  opacity its widgets use.
* `{ kind = "text", x, y, size, text, color, bold }` or
  `{ kind = "text", x, y, size, spans }`; `y` is the top of the line, `size` 6
  to 96 (default 14).
* `{ kind = "image", x, y, w, h, png }`; `png` is the bytes of a PNG file, for
  example `rb.file.read_bytes("icon.png")`. `w` and `h` default to the image's
  size in pixels. A PNG that fails to decode draws nothing. An image op
  needs `min_sdk=3.14.0`.

`set(fields)` merges fields into the surface and redraws; `draw(ops)` replaces
the ops. A script may have 16 surfaces at once, covering at most 2,000,000
square units together, with 256 ops, 4,096 bytes of text, 1,048,576 bytes of
PNG and 4,194,304 image pixels (2048 by 2048) in each. Invalid
options raise a message starting `rb.overlay:`. A toast that waited for hidden
overlays and then cannot show is dropped with a warning in the script's log.

## Overlay limits

* A game in exclusive full-screen mode on Windows hides overlays;
  `visible()` returns `false` then. Borderless and windowed games show them.
  On macOS `visible()` turns `false` only after the overlays on screen have
  been covered for 2 seconds; with none on screen it returns `true`.
* Overlays stay out of screenshots and recordings on Windows, and out of
  screenshots on macOS; macOS screen recordings may include them. A widget or
  surface given `capturable = true` shows in screenshots and recordings on
  both; `capturable` needs `min_sdk=3.14.0`.
* Screen reads under an overlay that is not capturable return what is beneath
  it. On macOS, every script's reads under a capturable one include its
  pixels, and a read under any overlay takes 10 to 25 ms, so a script should not
  draw over a region it reads every tick.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.