HUD Layouts

Build custom HUD overlays for tELS with HTML, CSS, and optional JavaScript.

layouts/Layout Authors

Overview

A HUD layout is a self-contained HTML file plus assets in a folder under layouts/. Lua pushes vehicle state to the NUI every tick, and the layout binds DOM elements to state fields with data-hud-* attributes. Lua does no presentation logic — the layout decides how to display what it receives.

Folders in layouts/ are auto-discovered by the server and appear in the HUD Layout selector in /tels.


Create a layout

Create the folder

layouts/MyLayout/ — the folder name is what players see in the layout selector.

Add ui.html

This is the layout's entry point. It can contain all HTML, CSS, and (optionally) a single <script data-hud-script> block for presentation logic.

Add assets (optional)

layouts/MyLayout/
  ui.html
  icons/       PNG / SVG assets
  sounds/      On.ogg, Off.ogg, Press.ogg (optional)

Reference assets relatively. ui.html is served from inside layouts/<name>/ — use paths like icons/foo.png.

Restart the resource

Layouts are enumerated on server start. Restart tELS and the new layout appears in /tels → HUD Layout.


Data bindings

Attach data-hud-* attributes to DOM elements. The binder processes them on every update.

AttributeExampleBehavior
data-hud-textdata-hud-text="patternName"Set textContent to the state value
data-hud-htmldata-hud-html="customHtml"Set innerHTML (be careful with untrusted values)
data-hud-leddata-hud-led="sirenOn"Toggle .active class when truthy
data-hud-showdata-hud-show="inVehicle"Show when truthy, hide when falsy
data-hud-hidedata-hud-hide="engineOn"Hide when truthy
data-hud-eqdata-hud-eq="taMode:left"Toggle .active when state[key] === value
data-hud-attrdata-hud-attr="gear:data-gear"Copy state value to an HTML attribute
data-hud-led-colordata-hud-led-color="leds.R1"Set background-color from {r, g, b, a}
data-hud-btndata-hud-btn="toggleSiren"Click fires a Lua tels:hudButton callback with this action ID
data-hud-keysdata-hud-keys="sirenTones,maxStage"Declare extra data dependencies for scripts (see below)

Use dot paths for nested state: leds.R1 resolves to state.leds.R1.

Example

<div data-hud-show="inVehicle">
  <span data-hud-text="patternName">—</span>
  <div data-hud-led="sirenOn" class="indicator">SIREN</div>
  <div data-hud-eq="taMode:left" class="arrow">◄</div>
  <div data-hud-led-color="leds.R1" class="pip"></div>
  <button data-hud-btn="cycle_pattern">Next</button>
</div>

Available state keys

Lua sends raw data only — no derived strings or presentation. Every key below is optional: the binder only sends keys the layout references (via data-hud-* attributes or data-hud-keys).

Core

KeyTypeDescription
inVehiclebooleanPlayer is in a vehicle
activebooleanEmergency lights are on
stagenumberCurrent stage (0 = off, 1–3)
maxStagenumberMax configured stages for this vehicle

Pattern

KeyTypeDescription
patternNamestringCurrent pattern name (empty for lights-only)
patternIdxnumber1-based pattern index (0 = lights-only)
patternCountnumberTotal patterns for this vehicle

Siren

KeyTypeDescription
sirenOnbooleanMain siren playing
sirenTonenumberMain siren tone index (1-based)
sirenToneNamestringMain siren tone display name
sirenTonesarray[{idx, name}, ...] — all configured tone slots
auxSirenOnbooleanAux siren playing
auxSirenTonenumberAux siren tone index
auxToneNamestringAux siren tone display name
hornActivebooleanAirhorn or horn held (zero-latency, driven by global flag)
chirpingbooleanChirp tone currently playing
rumblerOnbooleanRumbler variant active

Modes

KeyTypeDescription
cruisebooleanCruise on
takedownbooleanTakedown on
taModestring"off", "left", "right", or "center"
presencebooleanPresence detection armed

Vehicle

KeyTypeDescription
modelKeystringVehicle model key (lowercase)
modelNamestringDisplay name (e.g. "POLICE")
speedMphnumberRounded speed in mph
engineOnbooleanEngine running
gearnumberCurrent gear (0 = reverse)
rpmnumber0–100 scaled RPM
healthnumberEngine health (0–1000)
liverynumberLivery index
lockedbooleanVehicle locked
isDriverbooleanPlayer is in the driver seat

Lights

KeyTypeDescription
headlightsbooleanHeadlights on
highbeamsbooleanHigh beams on

Controls

KeyTypeDescription
brakebooleanBrake pedal pressed
handbrakebooleanHandbrake engaged
reversebooleanGear 0 + actually moving backward

Signals

KeyTypeDescription
turnLeftbooleanLeft indicator on
turnRightbooleanRight indicator on
hazardsbooleanHazards on

Doors

KeyTypeDescription
doorFL, doorFR, doorRL, doorRRbooleanThat door is open
hoodbooleanHood open
trunkbooleanTrunk open

LEDs

KeyTypeDescription
ledsobjectLive LED colors, keyed by LED name (e.g., { R1: { r, g, b, a } })

Bind individual LEDs with data-hud-led-color="leds.<name>". The LED colors stream at roughly 20 Hz, independent of the full state update.


Layout scripts

For derived values, dynamic HTML, or anything beyond simple bindings, add a <script data-hud-script> block. The script registers a callback that runs after each binder pass.

<script data-hud-script>
(function () {
  window.__telsLayoutUpdate = function (state, root) {
    // state: raw data from Lua
    // root:  this layout's container element

    var el = root.querySelector('.reverse-indicator');
    if (el) el.style.display = state.reverse ? '' : 'none';

    var kphEl = root.querySelector('.speed-kph');
    if (kphEl) kphEl.textContent = Math.round((state.speedMph || 0) * 1.609);
  };

  // Optional cleanup when the layout is unloaded.
  window.__telsLayoutDestroy = function () {
    // cancel timers, detach listeners, etc.
  };
})();
</script>

Declaring extra data

Lua only sends keys the layout references in data-hud-* attributes. If a script needs a key that isn't bound to any attribute, declare it with data-hud-keys:

<div data-hud-keys="sirenTones,maxStage,sirenTone">

</div>

Without this, Lua skips computing those keys and your script receives undefined.


Buttons

data-hud-btn="<action>" fires a tels:hudButton client event with the action string:

AddEventHandler("tels:hudButton", function(action)
  if action == "cycle_pattern" then
    ExecuteCommand("+lbar_pattern")
  end
end)

Wire it to your own handlers in a separate resource, or reuse the built-in ones.


Sounds

Optional sounds/ folder. The HUD plays:

  • On.ogg — on toggle-on actions
  • Off.ogg — on toggle-off actions
  • Press.ogg — on button press

File paths resolve relative to the layout folder: layouts/<name>/sounds/<file>.ogg.


Browser preview

You can preview layouts in a regular browser without launching FiveM by including the devtools script:

<script src="../devtools.js"></script>

It injects a floating control panel with toggles and sliders for every state key. The script detects the NUI environment and is a no-op when running in-game.

Build devtools from the resource root: bun run build.


Performance notes

  • Lua only computes keys the active layout references. data-hud-* attributes and data-hud-keys together drive this filter.
  • Full state updates run at ~5 Hz. LED colors (leds.*) update separately at ~20 Hz.
  • Cache query selectors in your script closure — it runs on every tick.

On this page

Need help?

Ask on Discord