Skip to content

HUD Layouts

Build a custom tELS HUD overlay with HTML, CSS and optional JavaScript.

A layout is a self-contained HTML file plus assets in a folder under layouts/. It binds DOM elements to state fields with data-hud-* attributes. Folders in layouts/ are auto-discovered and listed in /tels → HUD Layout.

tELS ships CRX-600 and SPX-1800.

Create a layout

Create the folder

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

Add ui.html

All HTML, CSS, and optionally one <script data-hud-script> block. Give the outermost element a fixed size and declare it.

html
<div
  class="ui-root"
  style="position:relative;width:440px;height:214px;overflow:hidden"
  data-radio-width="440"
  data-radio-height="214"
  data-hud-show="inVehicle"
>
  …
</div>

data-radio-width and data-radio-height are the fallback size while the layout is hidden. Without them, a layout that starts hidden never becomes visible.

Add assets (optional)

layouts/MyLayout/
  ui.html
  icons/       PNG / SVG assets
  sounds/      LIGHTS_ON.ogg, TONE_CHANGE.ogg, …

ui.html is served from inside layouts/<name>/, so relative paths resolve against it.

Restart the resource

The name appears without one, but ui.html and your assets 404 until you restart. A marketplace layout needs no restart.

Layouts run isolated

Each layout loads as its own page, in a sandbox: its own document, its own window, no access to the HUD around it or to the game.

Boundary
ScopeEverything inside your ui.html is yours alone, discarded when the player switches skins
StateArrives from tELS. You do not fetch it
OutputButton presses leave as data-hud-btn actions
ReachNo route to the host page, to other resources, or to any native

Data bindings

Processed on every update. Dot paths reach nested state: leds.R1 is state.leds.R1.

AttributeExampleBehaviour
data-hud-textdata-hud-text="patternName"Set textContent
data-hud-htmldata-hud-html="customHtml"Set innerHTML. Careful with untrusted values
data-hud-leddata-hud-led="sirenOn"Toggle .active when truthy
data-hud-showdata-hud-show="inVehicle"Show when truthy
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 the 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
html
<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>

State keys

Only keys the layout references through data-hud-* or data-hud-keys are sent.

Core

KeyTypeDescription
inVehiclebooleanPlayer is in a vehicle
activebooleanEmergency lights on
stagenumber0 off, 1–3
maxStagenumberMax configured stages for this vehicle

Pattern

KeyTypeDescription
patternNamestringCurrent pattern. Empty for lights-only
patternIdxnumber1-based index. 0 is lights-only
patternCountnumberTotal patterns

Siren

KeyTypeDescription
sirenOnbooleanMain siren playing
sirenTonenumberMain tone index, 1-based
sirenToneNamestringMain tone display name
sirenTonesarray[{idx, name}, …] — every configured slot
auxSirenOnbooleanAux siren playing
auxSirenTonenumberAux tone index
auxToneNamestringAux tone display name
hornActivebooleanAirhorn or horn held. Zero-latency, driven by a global flag
chirpingbooleanChirp tone playing
rumblerOnbooleanRumbler variant active
paActivebooleanPA mic transmitting
paAvailablebooleanpma-voice running
paRangenumberCurrent PA range, metres

Modes

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

Vehicle

KeyTypeDescription
modelKeystringModel key, lowercase
modelNamestringDisplay name, e.g. "POLICE"
speedMphnumberRounded mph
engineOnbooleanEngine running
gearnumberCurrent gear. 0 is reverse
rpmnumber0–100
healthnumberEngine health, 0–1000
liverynumberLivery index
lockedbooleanVehicle locked
isDriverbooleanPlayer is in the driver seat

Lights, controls and signals

KeyTypeDescription
headlights / highbeamsbooleanOn
brakebooleanBrake pedal pressed
handbrakebooleanHandbrake engaged
reversebooleanGear 0 and actually moving backward
turnLeft / turnRightbooleanThat indicator on
hazardsbooleanHazards on

Doors

KeyTypeDescription
doorFL, doorFR, doorRL, doorRRbooleanThat door open
hood / trunkbooleanOpen

LEDs

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

Bind one with data-hud-led-color="leds.<name>". LED colours stream at roughly 20 Hz, independently of the ~5 Hz full state update.

Layout scripts

For derived values or dynamic HTML, add a <script data-hud-script> block. tELS looks for exactly the two function names below.

html
<script data-hud-script>
  (function () {
    window.__telsLayoutUpdate = function (state, root) {
      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);
    };

    window.__telsLayoutDestroy = function () {
      // cancel timers, detach listeners
    };
  })();
</script>

Cache your query selectors in the closure — the callback runs every tick. If a script needs a key no attribute binds, declare it, or Lua never computes it and your script gets undefined:

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

What a script can and cannot do

You canYou cannot
Read everything in state and paint it how you likeReach the host page, or any other layout
Build, move and restyle elements inside your own layoutCall a game native or a NUI callback directly
Use timers, requestAnimationFrame, canvas, WebGL, audioRead or write player data, or anything outside your folder
Ask the game to act, with a data-hud-btnOpen a window, navigate the page, or lock the pointer
Load images and sounds from your folder, via markup or new Image() / new Audio()Use fetch or XMLHttpRequest, even against your own folder
Use any font the player's system hasLoad a custom web font with @font-face

To use a specific typeface, embed it as a data: URL.

If a skin comes up empty, check the client console. A script that throws is reported there by layout name and reason, and the rest of the HUD keeps running.

Buttons

data-hud-btn="<action>" fires a tels:hudButton client event with the action string. Wire it to your own handlers in a separate resource, or reuse the built-ins.

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

Sounds

tELS plays a file from your optional sounds/ folder on five actions, named verbatim for the action: layouts/<your-layout>/sounds/<ACTION>.ogg.

FilePlays on
LIGHTS_ON.oggThe lights coming on
LIGHTS_OFF.oggThe lights going off
TONE_CHANGE.oggA siren tone change, including the start of a burst
AIRHORN.oggThe airhorn key
Press.oggA pattern change and the PA key — the generic click

A missing file plays nothing and is not an error, so ship only what you want.

UI Studio

Build and publish layouts with the UI Studio.

Still stuck? Support can answer it directly.

Was this page helpful?