Koja Scripts
FREE

UI development

Build the React UI, preview it in a browser, and the NUI messages between Lua and React.

UI development

The HUD's interface is a React 18 + TypeScript app in web/, built with Vite into web/build. This page is for developers who change that UI: how to build it, how to preview it in a browser, and the messages that pass between Lua and React.

You need this page when you edit editable/shared/js.json (it is compiled into the UI), or change anything in web/src. Everything else — config.lua, locales — works without a build.

Building the UI

You need Node.js 18 or newer and pnpm.

cd koja-hud/web
pnpm install      # once, or after updating the HUD
pnpm build        # type-checks, then writes web/build

Restart the resource and reconnect — or press F8 and run ensure koja-hud — to load the new build.

ScriptWhat it does
pnpm buildProduction build into web/build
pnpm startDev server with hot reload, for the browser
pnpm start:gameRebuilds web/build on every save, for testing in-game
pnpm lintESLint

Previewing in a browser

pnpm start opens the UI in a normal browser with sample data: status bars, the speedometer, notifications, a progress bar, the text UI, the killfeed and status effects, and the settings menu. It is the quickest way to work on styles. The sample data lives at the top of web/src/containers/App.tsx.

Project layout

PathContents
web/src/containers/App.tsxRoot: start-up sync with the game, HUD, settings, vehicle menu
web/src/components/Hud/One folder per element: Status, CarHud, Notify, Progressbar, Textui, Informations, Keybinds, Compass, Weapon, Watermark, Killfeed, Warnings, StatusEffects
web/src/components/Hud/*/styles/The style variants of status, speedometer and notifications
web/src/components/Settings/Settings menu, layout editor, controls (switch, slider, colour, dropdown)
web/src/components/VehicleMenu/Vehicle control menu
web/src/providers/Settings (stored in browser storage), locale, visibility
web/src/utils/sanitizeHtml.tsThe allowlist for HTML in notifications

Messages from Lua to the UI

Lua sends SendNUIMessage({ action = name, data = payload }) through KOJA.Client.SendReactMessage(name, payload); React subscribes with useNuiEvent(name, handler).

ActionPayloadEffect
koja_hud:openHudtrueShow the HUD after the character loads
koja_hud:refreshHudpartial HudDataMerge new values — see below
koja_hud:showCarHudbooleanEntered or left a vehicle
koja_hud:toggleHudbooleanToggleHUD export
koja_hud:pauseHudbooleanPause menu opened or closed
koja_hud:hideComponents{ status, carhud, … }Forced-hidden components
koja_hud:setTalkingbooleanVoice chat activity
koja_hud:sendNotifynotificationNew notification
koja_hud:startProgressbar{ label, icon, time, … }Start the progress bar
koja_hud:cancelProgressbar—Stop it
koja_hud:startTextui{ input, type, desc }Show the text UI
koja_hud:cancelTextui—Hide it
koja_hud:killfeed{ killer, victim, weapon, distance, headshot, self }New killfeed entry
koja_hud:killstreakReset{}The player died
koja_hud:statuseffectslist of effectsFull list of active effects
koja_hud:minimapRect{ enabled, left, bottom, width, height }Where the minimap is, for the layout editor
koja_hud:openSettingstrueOpen the settings menu
koja_hud:openVehicleMenuvehicle stateOpen the vehicle menu
koja_hud:closeVehicleMenutrueClose it
setLocalelocale objectTexts from locales/<code>.json

Note the casing: startTextui and cancelTextui here, while the public net events are koja_hud:startTextUI and koja_hud:cancelTextUI.

koja_hud:refreshHud

Every field is optional; whatever is sent is merged into the current state.

KOJA.Client.SendReactMessage('koja_hud:refreshHud', {
    status = {
        { id = 'health', status = 87 },   -- health, shield, food, water, stamina, oxygen, voice, stress
    },
    informations = { cash = 1200, bank = 54000, job = 'Police', id = 12 },
    car = {
        speed = 23,                 -- metres per second; the UI converts to km/h or mph
        gear = 3,                   -- or 'N', 'R'
        engine = true,
        keys = false,               -- vehicle locked
        fuel = { type = 'gas', min = 64, max = 100 },   -- type 'gas' or 'electric'
        nitro = 80,
        seatbelt = true,
        rpm = 0.42,                 -- 0-1
        engineHealth = 96,
        lights = true, highbeam = false,
        indicators = 0,             -- GetVehicleIndicatorLights bitmask
        cruise = false
    },
    compass = { heading = 270 },
    weapon = { active = true, name = 'Pistol', ammo = 9, total = 48 },
    watermark = { text = 'MY SERVER' }
})

Callbacks from the UI to Lua

React calls fetchNui(name, body), which posts to https://koja-hud/<name>; Lua answers in RegisterNUICallback.

CallbackBodyDoes
koja_hud:getConfig—Returns server defaults, enabled features (compass, watermark) and the keys allowed for binds
loadLocale—Lua replies with a setLocale message
koja_hud:closeSettings—Releases NUI focus
koja_hud:updateMinimap{ x, y, scale }Moves and resizes the minimap
koja_hud:updateMinimapPosition{ x, y }Moves the minimap
koja_hud:updateMinimapVisibility{ mode }always, vehicle, foot, never
koja_hud:updateKillfeed{ enabled, anyDistance, distance }Player's killfeed filter
koja_hud:updateCustomBinds{ binds = [{ key, command }] }Player's custom binds
koja_hud:updateBuiltinBinds{ binds = [{ id, key, command }] }Player's keys for the HUD's own actions
koja_hud:closeVehicleMenu—Releases NUI focus
koja_hud:vehicleAction{ action, index? }engine, seatbelt, lights, indicatorLeft, indicatorRight, hazard, door (with index 0–5); returns the new vehicle state

On start the UI calls getConfig and then sends the player's stored binds, minimap position, minimap visibility and killfeed filter back to Lua, so player choices apply from the first frame.

Adding a setting

  1. Add the default under default_settings in editable/shared/js.json.
  2. Add a control to a window in options_settings, with id set to the setting's path — the menu builds itself from that list.
  3. Add its texts to ui.settings in every locale file.
  4. Read it in your component with const { settings } = useSettings().
  5. pnpm build.

The settings type is inferred from js.json, so TypeScript knows about the new field without further changes.