# esx_menu_default (/docs/core/esx_menu_default)

esx_menu_default is the classic ESX list menu: a vertical list of buttons and
sliders that players navigate with the arrow keys and Enter. It does not take
mouse focus, so the player can keep moving while the menu is open. You open it
with `ESX.UI.Menu.Open('default', ...)` from a client script.

## Overview [#overview]

| | |
| --- | --- |
| Resource | `esx_menu_default` |
| Side | Client only |
| Menu type | `default` |
| Dependencies | `es_extended`, `esx_lib` (loaded via `@esx_lib/imports.lua`) |
| UI | React build in `web/build`, Font Awesome icons |

<Callout type="warn">
The manifest only declares `es_extended` as a dependency, but the resource loads
`@esx_lib/imports.lua`. Start `esx_lib` before `esx_menu_default` or the
resource fails with `xLib` errors.
</Callout>

The resource has no `config.lua`. Menus are stacked: when you open a second menu,
it is shown on top, and closing it brings back the previous one.

## ESX.UI.Menu.Open [#esxuimenuopen]

```lua
ESX.UI.Menu.Open(menuType, namespace, name, data, submit, cancel, change, close)
```

| Name | Type | Description |
| --- | --- | --- |
| `menuType` | `string` | `'default'` for this resource. |
| `namespace` | `string` | Usually `GetCurrentResourceName()`. Menus in this namespace are closed when the resource stops. |
| `name` | `string` | Unique menu name inside the namespace. |
| `data` | `table` | Menu data, see below. |
| `submit` | `function(data, menu)?` | Enter pressed on a usable element. |
| `cancel` | `function(data, menu)?` | Backspace pressed. |
| `change` | `function(data, menu)?` | Highlight moved or a slider value changed. |
| `close` | `function()?` | Runs after `menu.close()`. |

Returns the `menu` object (see [Menu object](#menu-object)).

### Menu data [#menu-data]

| Field | Type | Description |
| --- | --- | --- |
| `title` | `string` | Menu header. HTML is rendered. |
| `align` | `string?` | `top-left`, `top-right`, `bottom-left`, `bottom-right`, `center`, `left-center` or `right-center`. Defaults to `right-center`. |
| `elements` | `table` | Array of [elements](#element-fields). |

### Element fields [#element-fields]

| Field | Type | Description |
| --- | --- | --- |
| `label` | `string` | Text of the row. HTML is rendered. |
| `name` | `string?` | Your identifier, used to branch in callbacks. |
| `description` | `string?` | Second line below the label. HTML is rendered. |
| `icon` | `string` \| `table` | Font Awesome icon name, e.g. `'burger'` (solid set), or `{ 'fab', 'discord' }` for another set. |
| `type` | `'slider'?` | Omit for a button. |
| `value` | `number?` | Current slider value. For sliders with `options` it is the zero-based index into `options`. |
| `min` | `number?` | Slider minimum (default `0`). Ignored when `options` is set. |
| `max` | `number?` | Slider maximum (default `0`). Ignored when `options` is set. |
| `options` | `string[]?` | Text values for a slider. The slider cycles through them. |
| `unselectable` | `boolean?` | Skipped by arrow navigation, Enter does nothing. Good for headers. |
| `usable` | `boolean?` | `false` keeps the row selectable but blocks submit, change and slider steps. |
| `disableRightArrow` | `boolean?` | Hides the arrow icon on a button. |

Sliders wrap around: pressing Right on the maximum jumps to the minimum and vice
versa. Any extra fields you add (for example `price`) are passed back to your
callbacks unchanged.

## Callback data [#callback-data]

`submit` and `change` receive:

| Field | Description |
| --- | --- |
| `data.current` | The highlighted element (a copy with `selected = true`). |
| `data.elements` | All elements, each with a `selected` flag. |
| `data._namespace`, `data._name` | Menu identifiers. |

`cancel` receives only `_namespace` and `_name` in `data`.

Before `change` runs, the menu writes each element's `value` and `selected`
state back into `menu.data.elements`, so `menu.data` always matches what the
player sees.

## Example [#example]

```lua title="client/main.lua"
local function openShopMenu()
    local elements = {
        { label = 'Food', unselectable = true, icon = 'utensils' },
        { label = 'Bread', name = 'bread', price = 5, type = 'slider', value = 1, min = 1, max = 20, icon = 'bread-slice' },
        { label = 'Water', name = 'water', price = 3, type = 'slider', value = 1, min = 1, max = 20, icon = 'bottle-water' },
        { label = 'Payment', name = 'payment', type = 'slider', value = 0, options = { 'Cash', 'Bank' }, icon = 'wallet' },
        { label = 'Close', name = 'close', icon = 'xmark', disableRightArrow = true },
    }

    ESX.UI.Menu.Open('default', GetCurrentResourceName(), 'shop', {
        title = '24/7 Shop',
        align = 'top-left',
        elements = elements,
    }, function(data, menu)
        local item = data.current

        if item.name == 'close' then
            return menu.close()
        end

        if item.name == 'payment' then return end

        local payment = menu.data.elements[4].value == 0 and 'money' or 'bank'
        TriggerServerEvent('my_shop:buy', item.name, item.value, payment)
    end, function(data, menu)
        menu.close()
    end)
end

RegisterCommand('shop', openShopMenu, false)
```

## Menu object [#menu-object]

`ESX.UI.Menu.Open` returns a menu object. The same object is passed as `menu` to
every callback.

| Member | Description |
| --- | --- |
| `menu.close()` | Closes this menu and runs the `close` callback. |
| `menu.refresh()` | Re-sends `menu.data` to the UI. Call it after changing data. |
| `menu.update(query, newData)` | Merges `newData` into every element matching all `query` fields. |
| `menu.setElement(index, key, value)` | Sets one field of the element at `index`. |
| `menu.setElements(elements)` | Replaces the element list. |
| `menu.setTitle(title)` | Changes the title. |
| `menu.removeElement(query)` | Removes elements that match any field in `query`. |
| `menu.data` | The data table you passed in. |
| `menu.type`, `menu.namespace`, `menu.name` | Identifiers. |

```lua
menu.update({ name = 'bread' }, { label = 'Bread (sold out)', usable = false })
menu.refresh()
```

<Callout type="info">
The setters only change Lua data. Nothing changes on screen until you call
`menu.refresh()`.
</Callout>

## Other menu functions [#other-menu-functions]

These are generic `ESX.UI.Menu` helpers that work for every menu type
(`default`, `dialog`, `list`).

| Function | Description |
| --- | --- |
| `ESX.UI.Menu.Close(type, namespace, name, cancel)` | Closes a specific menu. With `cancel = true` the `cancel` callback runs instead of `close`. |
| `ESX.UI.Menu.CloseAll(cancel)` | Closes every open menu. |
| `ESX.UI.Menu.GetOpened(type, namespace, name)` | Returns the menu object or `nil`. |
| `ESX.UI.Menu.IsOpen(type, namespace, name)` | Alias of `GetOpened`; truthy when open. |
| `ESX.UI.Menu.GetOpenedMenus()` | Returns the internal list of open menus (may contain `nil` holes). |
| `ESX.UI.Menu.RegisterType(type, open, close)` | Registers a custom menu type. Used by the menu resources themselves. |

## Controls [#controls]

All keys are FiveM key mappings, so players can rebind them under
**Settings > Key Bindings > FiveM**.

| Default key | Key mapping | Action |
| --- | --- | --- |
| Enter | `+menu_default_enter` | Submit the highlighted element (200 ms debounce) |
| Backspace | `+menu_default_backspace` | Cancel |
| Arrow Up | `+menu_default_top` | Move up (skips unselectable rows, wraps) |
| Arrow Down | `+menu_default_down` | Move down |
| Arrow Left / Right | `+menu_default_left` / `+menu_default_right` | Change slider value |

Keys are ignored while the pause menu is open.

## Common issues [#common-issues]

- **`attempt to index a nil value (global 'xLib')`**: `esx_lib` is missing or
  starts after this resource.
- **Menu does not close**: `submit` and `cancel` never close the menu
  automatically. Call `menu.close()`.
- **Same menu opens twice**: `ESX.UI.Menu.Open` does not check for duplicates.
  Guard with `if ESX.UI.Menu.IsOpen('default', GetCurrentResourceName(), 'shop') then return end`
  or call `ESX.UI.Menu.CloseAll()` first.
- **Changes to elements are not visible**: call `menu.refresh()` after
  `setElement`, `update` or `setTitle`.
- **Untrusted text breaks the menu**: `label`, `description` and `title` are
  rendered as HTML. Escape player names or other user input before you show them.

## Related [#related]

- [Client UI functions](/docs/es_extended/client/ui)
- [esx_menu_dialog](/docs/core/esx_menu_dialog)
- [esx_menu_list](/docs/core/esx_menu_list)
- [esx_context](/docs/core/esx_context) for mouse-driven menus
- [esx_lib](/docs/core/esx_lib)
