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

# Interact - Options

> The shared vocabulary of every add* export - anchor fields, the option table, condition gates, and handlers

Every `add*` export - `addPoint`, `addSphere`, `addBox`, `addEntity`, `addModel`, and the `addGlobal*` family - accepts the same data table: a handful of **anchor-level fields** plus an **options array**. This page is the reference for both. The geometry each export adds on top (coords, radius, size, entity, bone...) is documented with that export.

Every export returns an anchor id on success, removable with `exports.lation_interact:remove(id)`. An invalid spec prints a console error explaining what's wrong and returns `nil` - the anchor is not registered.

## Anchor Fields

<ParamField path="label" type="string">
  What the anchor is called - the text that grows out of the grain at name range. **Optional**, and its presence decides the anatomy the component renders:

  * **Titled** - the label names the thing (`'Cash Register'`); options unfold beneath a header.
  * **Chip** - no label (or it just repeats the only option's label) and a single option: renders as one actionable chip - label plus keycap, no unfold.
  * **Headerless list** - no label with multiple options: the first option *is* the header line, and the rest grow beneath it.

  When given, `label` must be a non-empty string.
</ParamField>

<ParamField path="options" type="table" required>
  Array of option tables - see [Option Fields](#option-fields) below. At least one is required.
</ParamField>

<ParamField path="iconColor" type="string">
  Explicit color for the header icon (any CSS color, e.g. `'#8be9fd'`). Without it, header icons are light - or the theme accent when `Config.AccentIcons` is on
</ParamField>

<ParamField path="icon" type="string">
  FontAwesome **solid** icon name for the anchor itself (e.g. `'cash-register'`). When omitted, a headerless anchor wears its promoted option's icon; with no icon anywhere, the default dot.
</ParamField>

<ParamField path="stages" type="table">
  Per-anchor distance overrides, in meters: `{ grain = ..., name = ..., reach = ... }`. Defaults come from `Config.Stages` (`8.0` / `4.5` / `2.4`); any subset can be overridden.

  <Note>
    Sphere and box zones derive their own stages from their size - reach is being *inside* the zone - unless you override them here. See [How It Works](/docs/interact/how-it-works#the-three-stages).
  </Note>
</ParamField>

<ParamField path="expand" type="string" default="Config.ExpandMode">
  How the stack opens once you're in reach: `'auto'` (always unfolds on its own), `'manual'` (always waits for the confirm key), or `'smart'` (single-option anchors unfold, multi-option anchors wait). The config default is `'smart'` - see [how lists open](/docs/interact/how-it-works#how-lists-open).
</ParamField>

<ParamField path="reveal" type="string">
  What renders *before* the anchor is focused: `'ambient'` (a grain of light marks it from grain range) or `'focus'` (nothing renders until attention lands on it). Points and zones default to `'ambient'`, entity, model, and global anchors to `'focus'` (`Config.Reveal`) - see [reveal modes](/docs/interact/how-it-works#reveal-modes).
</ParamField>

<ParamField path="debug" type="boolean" default="false">
  Draw the physical interaction shape in-world: zones draw their volume, points and entity anchors the reach sphere at the anchor. Points and zones draw at any distance while the flag is set; entity anchors draw whenever their entity is around.
</ParamField>

<ParamField path="passive" type="boolean">
  Hidden until the interact control is held - no grain, no label, no input - like a classic target. Defaults to `Config.Passive`, or `true` when the registering resource is listed in `Config.PassiveResources`. See [passive mode](/docs/interact/how-it-works#passive-mode).
</ParamField>

<ParamField path="inVehicle" type="boolean">
  Whether this anchor stays live while the player sits in a vehicle. Unset, it follows `Config.InVehicle` (entity anchors hide, points and zones stay). See [in a vehicle](/docs/interact/how-it-works#in-a-vehicle).
</ParamField>

## Option Fields

Each entry in `options` describes one row of the stack.

<ParamField path="label" type="string" required>
  The row's text.
</ParamField>

<ParamField path="icon" type="string">
  FontAwesome **solid** icon name for the row (e.g. `'credit-card'`).
</ParamField>

<ParamField path="iconColor" type="string">
  Explicit color for this option's row icon; overrides the theme's icon color
</ParamField>

<ParamField path="key" type="string">
  Keyboard key that activates this option directly, by name: letters and digits (`'E'`, `'T'`, `'5'`), `F1`-`F24`, `NUMPAD0`-`NUMPAD9`, and named keys like `'SPACE'`. Case-insensitive. Omit to make the option **cursor-only**: it's reached with the confirm key while highlighted, or with Left Alt + click in [cursor mode](/docs/interact/how-it-works#input).

  Option keys are live only while the stack is open - a closed stack opens on the confirm key alone, and while open, the game action sharing a bound key's default bind is suppressed so one press never does both.

  <Warning>
    A key name that doesn't exist fails validation - the whole registration is rejected with a console error.
  </Warning>
</ParamField>

<ParamField path="hold" type="number" default="0">
  Milliseconds the key (or mouse button) must be held - a ring fills while held and the option fires when it completes; releasing early cancels. Omit or `0` for instant activation.
</ParamField>

<ParamField path="keepOpen" type="boolean" default="false">
  Keep the list open after selecting this option, for repeatable actions. By default a `manual` stack folds back to the pill after a selection.
</ParamField>

<ParamField path="allowInVehicle" type="boolean" default="false">
  Keeps this option's anchor live from inside a vehicle when `Config.InVehicle` would hide it - the flag vehicle-aware scripts set on drive-thru style options. Honored on bridged ox\_target / qb-target options too.
</ParamField>

<ParamField path="args" type="any">
  Passed through untouched to the handler payload as `data.args`.
</ParamField>

## Condition Gates

Four per-option gates decide whether a row is visible to this player right now. An anchor whose *every* option is gated away disappears entirely - no grain, no focus - for players who can't use any of it.

Gates are evaluated off the frame path on a dedicated eval thread - every `50ms` while the anchor is focused and roughly 5x/s while merely nearby. A slow `canInteract` - or one that waits inside - only delays its own anchor's rows; the element keeps gliding and input stays live.

<ParamField path="groups" type="string | string[] | table">
  Framework group gate - matches **jobs and gangs alike**, whatever the framework reports as the player's groups:

  * `'police'` - the player has this group
  * `{ 'police', 'sheriff' }` - the player has *any* of these
  * `{ police = 2 }` - any listed group, at that minimum grade

  ```lua theme={null}
  groups = { police = 2, sheriff = 0 }
  ```

  <Note>
    Custom setups can adapt the group lookup in `bridge/client.lua` - it's open code, made to be edited.
  </Note>
</ParamField>

<ParamField path="items" type="string | string[] | table">
  Inventory gate:

  * `'lockpick'` - the player carries this item
  * `{ 'lockpick', 'screwdriver' }` - the player carries *all* of these
  * `{ lockpick = 2 }` - at those minimum counts

  Supported inventories are detected automatically; custom setups can adapt the item lookup in `bridge/client.lua`.
</ParamField>

<ParamField path="state" type="string | table">
  Entity **statebag** gate:

  * `'trunkLocked'` - visible while `Entity(entity).state.trunkLocked` is truthy
  * `{ key = 'trunkLocked', value = false }` - visible while the statebag equals `value`

  <Warning>
    Statebags live on entities, so `state` only works on entity, model, and global anchors. On points and zones the option prints a warning and stays hidden.
  </Warning>
</ParamField>

<ParamField path="canInteract" type="function">
  Arbitrary predicate - return `true` to show the row. Receives one table:

  ```lua theme={null}
  canInteract = function(data)
      -- data.id       the anchor id
      -- data.entity   entity handle (entity anchors only)
      -- data.args     the option's args
      -- data.distance current distance to the anchor, when known
      return not IsPedInAnyVehicle(PlayerPedId(), false)
  end
  ```

  Evaluated off the frame path, never per render frame - it's safe to call exports or even wait inside, at the cost of your own rows updating later.

  <Note>
    `canInteract` is **not re-run at activation** - whatever row is rendered can be selected. If an action must be re-validated at the moment it fires, validate in the handler (or server-side).
  </Note>
</ParamField>

## Handlers

When an option activates, its handlers fire - any combination of the three, in this order:

<ParamField path="onSelect" type="function">
  Called with the payload table below.
</ParamField>

<ParamField path="event" type="string">
  Client event triggered with the payload table.
</ParamField>

<ParamField path="serverEvent" type="string">
  Server event triggered with the payload table **minus `entity`** - entity handles are client-local, so the server receives `id`, `index`, `args`, `netId`, and `coords`.
</ParamField>

All three receive the same payload:

```lua theme={null}
{
    id     = 1000001,          -- the anchor id the add* export returned
    index  = 2,                -- the option's REAL index, in registration order
                               -- (stable even while hidden siblings renumber the view)
    args   = ...,              -- the option's args, verbatim
    entity = 735,              -- entity handle (entity anchors only; omitted in serverEvent)
    netId  = 918,              -- network id, when the entity is networked
    coords = vec3(x, y, z)     -- anchor coords, or the entity's coords at activation
}
```

Handlers dispatch from a spawned thread, so they can freely await - open an inventory, hit a server callback - without ever stalling the frame loop.

## Full Example

A trunk anchor on a spawned mission vehicle, exercising most of the vocabulary - the statebag is set server-side when the vehicle spawns (`Entity(vehicle).state:set('trunkLocked', true, true)`):

```lua theme={null}
local vehicle = NetToVeh(netId)

local trunk = exports.lation_interact:addEntity(vehicle, {
    label = 'Trunk',
    icon = 'car-rear',
    bone = 'boot',
    options = {
        {
            label = 'Open Trunk',
            icon = 'box-open',
            key = 'E',
            state = { key = 'trunkLocked', value = false },
            onSelect = function(data)
                TriggerServerEvent('cargo:openTrunk', data.netId)
            end
        },
        {
            label = 'Lockpick Trunk',
            icon = 'unlock',
            key = 'G',
            hold = 2500,                    -- ring fills for 2.5s
            items = 'lockpick',             -- hidden without one
            state = 'trunkLocked',          -- only while still locked
            serverEvent = 'cargo:lockpickTrunk',
            args = { difficulty = 'hard' }
        },
        {
            label = 'Impound Vehicle',
            icon = 'truck-ramp-box',
            groups = { police = 2, mechanic = 0 },
            canInteract = function(data)
                -- only while the vehicle is empty
                return IsVehicleSeatFree(data.entity, -1)
                    and GetVehicleNumberOfPassengers(data.entity) == 0
            end,
            serverEvent = 'cargo:impound'
        }
    }
})

-- later, when the job is done
exports.lation_interact:remove(trunk)
```

While the trunk is locked, a passerby with no lockpick sees nothing at all - every option is gated, so the anchor is invisible to them. A player carrying a lockpick sees one row; an on-duty sergeant standing by an empty vehicle sees two. Once the statebag flips to `false`, `Open Trunk` appears for everyone and the lockpick row retires itself.
