Skip to content

Game fields, map and flow ​

These components are the pickers and editors that know about the game: items, vehicles, jobs, zones, animations, voice lines, the world map and a node graph canvas.

Several of these fields hand work to the game (drawing a zone in the world, recording a camera move, hearing a voice line). That part only runs in game. Everything else on this page is the real component, so you can click, type and drag in the previews.

KItemField ​

An inventory item picker: a searchable list with item images once the item catalog is known, or a plain name field when it is not.

KItemField

The catalog is shared by every item field on screen. Your resource answers a getItems NUI callback once, or you hand the list over yourself with setItemCatalog. Item images load from the inventory in game, so the list shows names only on this page.

PropTypeDefaultDescription
v-modelstring''The item name. A name the catalog does not know stays visible as "Not in the item list".
labelstringnoneLabel above the field.
hintstringnoneSmall help text under the field.
placeholderstringPick an item…Shown while empty.
size'sm' | 'md''md'Field height.
clearablebooleanfalseAdds a clear button.
disabledbooleanfalseDisables the field.
vue
<script setup>
import { KItemField, setItemCatalog } from '@/kit'
setItemCatalog([{ name: 'water', label: 'Water', category: 'Food' }])
</script>

<template>
  <KItemField v-model="item" label="Reward item" clearable />
</template>

KVehicleField ​

A vehicle model picker with a searchable list, or a plain model field when the vehicle catalog is not known.

KVehicleField

The catalog comes from a getVehicles NUI callback, or from setVehicleCatalog. The callback may also send an imageBase so vehicle photos appear. Models are matched case insensitively.

PropTypeDefaultDescription
v-modelstring''The model name, for example sultan.
allowCustombooleanfalseAdds a "Type a model name" switch for addon vehicles the list does not have.
labelstringnoneLabel above the field.
hintstringnoneSmall help text under the field.
placeholderstringPick a vehicle…Shown while empty.
size'sm' | 'md''md'Field height.
clearablebooleanfalseAdds a clear button.
disabledbooleanfalseDisables the field.
vue
<KVehicleField v-model="model" label="Vehicle" allow-custom />

KGroupField ​

A picker for framework groups (jobs, gangs or factions), as a single select or as chips for several.

KGroupField

The catalog comes from a getGroups NUI callback, or from setGroupCatalog. Without a catalog the field becomes a text box (a comma separated one with multiple).

PropTypeDefaultDescription
v-modelstring''The group name (single mode).
v-model:liststring[][]The group names (with multiple).
multiplebooleanfalseEdit a list of names with v-model:list instead of one name.
labelstringnoneLabel above the field.
hintstringnoneSmall help text under the field.
placeholderstringPick a group…Shown while empty (single mode).
size'sm' | 'md''md'Field height (single mode and text fallback).
clearablebooleanfalseAdds a clear button (single mode).
disabledbooleanfalseDisables the field.
vue
<KGroupField v-model="job" label="Job" />
<KGroupField v-model:list="allowed" multiple label="Allowed groups" />

KZoneField ​

Draws and edits a polygon, box or sphere zone in the world, with exact values you can type.

KZoneField

Open "Exact values" and edit a coordinate, or remove a point of the polygon. Drawing and "Show in world" happen in the game, so those buttons do nothing on this page. Your resource's Lua registers the drawZone, zonePreview and zonePreviewClear callbacks. The value is null or one of three shapes:

ts
type Zone =
  | { type: 'poly'; points: { x: number; y: number; z: number }[]; thickness?: number | null }
  | { type: 'box'; coords: Vec3; size: Vec3; rotation?: number | null }
  | { type: 'sphere'; coords: Vec3; radius: number }
PropTypeDefaultDescription
v-modelZone | nullnullThe zone.
namestringrequiredCreator name shown while drawing.
zoneLabelstringrequiredCreator label shown while drawing, for example "Garage access zone".
previewKeystringrequiredStable id for the in-world outline, for example garages:12:zone.
labelstringZoneTitle of the row.
descriptionstringnoneText shown while no zone is set.
colournumbernoneOutline color index for the in-world preview.
typesZoneType[]['poly', 'box', 'sphere']Which shapes staff can choose. With one shape the shape switch is hidden.
requiredbooleanfalseShows a "Required" badge while empty.
clearablebooleantrueAllows removing the zone.
teleport(center) => unknownnoneAdds a "Teleport to center" button that calls this with the zone center.
vue
<KZoneField v-model="zone" name="garage" zone-label="Garage access zone" preview-key="garages:12:zone" :types="['poly', 'box']" />

KLookField ​

Stores a ped's appearance: copy what your character is wearing, open the customization editor, or paste raw JSON.

KLookField

"Copy my look" and "Design…" talk to the game, so they only work in game. Opening "Raw JSON", editing the object and pressing "Apply JSON" works here. Your resource's Lua registers the copyLook and designLook callbacks.

PropTypeDefaultDescription
v-modelRecord<string, any> | nullnullThe appearance object, or null for the default look.
labelstringAppearanceTitle of the row.
descriptionstringnoneHelp text under the buttons.
designablebooleantrueShows the "Design…" button.
modelstringnonePed model to start a fresh design from when there is no look yet, for example mp_m_freemode_01.
disabledbooleanfalseDisables the buttons.

Events: picked(appearance) fires after a look is copied or designed, so you can also adopt its model.

vue
<KLookField v-model="look" label="Uniform" model="mp_m_freemode_01" @picked="onPicked" />

KPlacement ​

One in-world placement step: a row with a status, a place button and an optional remove button.

KPlacement

Your setAction usually hides the interface while the admin places the thing in the world, then returns the result. KZoneField is built from this component.

PropTypeDefaultDescription
labelstringrequiredName of the step.
iconstringrequiredIcon, for example lucide:door-open.
isSetbooleanrequiredWhether a placement exists.
detailstringPlacedText shown when set.
descriptionstringnoneText shown when not set.
requiredbooleanfalseShows a "Required" badge while not set.
lockedbooleanfalseGreys the row out and hides the buttons.
lockedReasonstringnoneWhy the row is locked.
disabledbooleanfalseDisables the place button.
setLabelstringPlaceButton text while not set.
redoLabelstringRedoButton text once set.
setAction() => unknownnoneCalled by the place button. The button shows a busy state while it runs.
clearAction() => unknownnoneCalled by the remove button, shown while set.
vue
<KPlacement label="Front door" icon="lucide:door-open" :is-set="!!door" :detail="doorText" :set-action="placeDoor" :clear-action="() => (door = null)" />

KSpeechField ​

Picks one ambient voice line a ped says, from the official speech list, with a choice of how it plays.

KSpeechField

The kit has no game connection of its own, so the play buttons appear only when you pass a preview function; it receives the cleaned line and plays it however your screen does. The value is { name, param?, voice? } or null.

PropTypeDefaultDescription
v-modelSpeechRef | null | undefinedrequiredThe voice line. null means no line.
labelstringnoneLabel above the field.
addLabelstringAdd a voice lineText of the button shown while empty.
hintstringnoneHelp text shown once a line is set.
disabledbooleanfalseDisables the field.
suggeststring[]allSuggestion groups listed first: 'shop', 'greet', 'farewell', 'react'.
preview(value: SpeechRef) => unknownnoneShows the play buttons and is called with what the game should say.
hideVoicebooleanfalseHides the forced voice box, which is rarely needed.
vue
<KSpeechField v-model="greeting" label="Greeting" :suggest="['shop', 'greet']" :preview="(line) => playSpeech(line)" />

KChallengeField ​

Chooses a minigame challenge chain (lockpick, skill check, progress) and shows it as a one line summary.

KChallengeField

The Create and Edit button opens the minigame creator in game, through a minigameCreator NUI callback, so it does nothing on this page. The remove button works here. The creator answers { ok, spec }, { ok = false, cancelled = true } or { ok = false, error }.

PropTypeDefaultDescription
v-modelChallengeSpec | null | undefinednoneThe spec: { steps: [{ kind, game?, difficulty?, stages?, duration? }] }.
labelstringnoneLabel above the field.
hintstringnoneSmall help text.
disabledbooleanfalseDisables the buttons.
titlestringnoneTitle shown in the creator.
subtitlestringnoneSubtitle shown in the creator.
emptyTextstringNoneText shown while there is no challenge.
vue
<KChallengeField v-model="spec" label="Break in challenge" title="Break in" />

KCameraPathField ​

Records a camera move around a point in the world and plays it back, with start delay, speed and blend sliders.

KCameraPathField

The field does not talk to the game itself. You pass capture (start recording around focus, resolve to the keyframes) and preview (play a path), so you decide how the camera is flown. This demo uses a stand-in capture that returns three made up keyframes; in game yours would fly the real camera. The path is a list of { t, camera: { coords, rotation, fov } } keyframes, where t is milliseconds since the start.

PropTypeDefaultDescription
v-modelCameraPathKeyframe[][]The recorded keyframes.
v-model:settingsCameraPathSettingsDEFAULT_CAMERA_PATH_SETTINGS{ startDelay, speed, transition }, edited by the sliders.
focus{ x, y, z } | nullrequiredThe point the camera orbits. Recording is disabled while it is null.
cameraCameraPathShot | nullnoneA starting camera, passed on to capture.
maxDistancenumber80How far from focus the camera may fly, passed on to capture.
capture(options) => Promise<CameraPathKeyframe[] | null>requiredRecords a path. Receives { focus, camera, maxDistance }.
preview(path) => Promise<unknown>requiredPlays a path.
labelstringCamera pathTitle of the row.
emptyTextstringOptional · record a camera moveText shown while empty.
recordedHintstringnoneExtra text appended to the recorded summary.
recordTitlestringFly and record a camera moveTooltip of the record button.
disabledbooleanfalseDisables the buttons and sliders.
vue
<KCameraPathField v-model="path" v-model:settings="settings" :focus="focus" :capture="recordCamera" :preview="playCamera" />

KGameIconPicker ​

Picks a GTA marker or blip sprite by id, with a browsable gallery, search and a manual id box.

KGameIconPicker

The gallery pictures load from the public FiveM reference site, so they need a connection. In game, a sprite your map resource replaces with its own icon is shown with that icon here too.

PropTypeDefaultDescription
v-modelnumber | nullnullThe sprite or marker id.
kind'marker' | 'blip'requiredWhich reference list to browse.
labelstringnoneLabel above the field.
hintstringnoneSmall help text.
disabledbooleanfalseDisables the field.
vue
<KGameIconPicker v-model="sprite" kind="blip" label="Blip sprite" />

KBlipColorPicker ​

Picks a blip color by id from the named list, with approximate swatches.

KBlipColorPicker
PropTypeDefaultDescription
v-modelnumber | nullnullThe blip color id.
labelstringnoneLabel above the field.
hintstringnoneSmall help text.
disabledbooleanfalseDisables the field.
vue
<KBlipColorPicker v-model="color" label="Blip color" />

KAnimField ​

A field that shows the chosen animation (an emote, a raw clip or a GTA scenario) and opens the animation picker.

KAnimField

The value is an AnimRef: { emote }, { scenario } or a raw clip such as { dict, clip, flag, blendIn, blendOut, speed, duration, props }. Emotes, the saved "My animations" library, checking a raw clip and "Preview in world" all come from the game through callbacks your Lua registers, so previewing only works in game. This page fills the emote list with a few examples. The picker opens as a dialog over the page.

PropTypeDefaultDescription
v-modelAnimRef | nullnullThe chosen animation.
labelstringnoneLabel above the field.
placeholderstringChoose an animation…Shown while nothing is chosen.
size'sm' | 'md''sm'Field height.
disabledbooleanfalseDisables the field.
clearablebooleanfalseAdds a remove button.
previewModelstring | nullnonePreview on this ped model instead of the admin's own clone.
previewAppearanceobject | nullnonePreview with this ped look.
subtitlestringnoneWhat the animation is for, shown in the picker header.
kindsAnimKind[]allLimit the picker to 'emote', 'clip' or 'scenario'. Also hides "My animations".
vue
<KAnimField v-model="idle" label="Idle animation" :kinds="['emote', 'scenario']" clearable />

KAnimPicker ​

The picker dialog itself, for when you want to open it from your own button instead of a field.

KAnimField already contains one, so you only need this directly for custom triggers. It is a dialog with tabs for emotes, raw clips (with props and timing), GTA scenarios and the saved library.

PropTypeDefaultDescription
openbooleanrequiredWhether the dialog is shown.
valueAnimRef | nullnoneThe animation to start from.
previewModelstring | nullnonePreview on this ped model.
previewAppearanceobject | nullnonePreview with this ped look.
subtitlestringnoneText under the title.
kindsAnimKind[]allLimit the tabs. Also hides "My animations".

Events: close() and select(anim).

vue
<KButton label="Pick an animation" @click="open = true" />
<KAnimPicker :open="open" :value="anim" @close="open = false" @select="(a) => { anim = a; open = false }" />

The picker is built from smaller pieces (a clip editor, a flags editor and a timing editor) that stay inside the kit. You normally use KAnimField, KAnimPicker and KAnimTweaks.

KAnimTweaks ​

Overrides a few fields of an animation you otherwise leave as it is: flags, blend in and out, speed and duration.

KAnimTweaks

Each field follows the original until you change it. tweaks is edited in place and keeps null for every field that is not overridden. Call tweaksResult(tweaks) for the value to save (null when nothing is overridden), and tweaksFrom(saved) to load one back.

PropTypeDefaultDescription
tweaksAnimPlaybackrequiredThe working copy: { flag, blendIn, blendOut, speed, duration }, each a number or null. Start from blankTweaks().
baseAnimRefrequiredThe original animation, shown as the placeholder of each field.
lengthnumber | nullnoneLength of the clip in seconds, shown as a hint under the timing fields.
vue
<script setup>
import { ref } from 'vue'
import { KAnimTweaks, blankTweaks, tweaksResult } from '@/kit'
const tweaks = ref(blankTweaks())
const save = () => tweaksResult(tweaks.value)
</script>

<template>
  <KAnimTweaks :tweaks="tweaks" :base="emote" :length="6.2" />
</template>

How the game connection works ​

The pickers above share one rule: the kit never calls the game on its own. Each one either takes the data as a prop or setter, or posts a named NUI callback that your resource registers. A screen with no callback still works as a plain text field.

KMap ​

A pannable, zoomable world map that draws zones and markers in game coordinates.

KMap

The map needs a parent with a fixed height, because it fills it (the preview above is 400 pixels tall). No map pictures ship with the kit, so on this page the zones and markers sit on a flat sea colored background. In game, the server's map resource supplies the picture tiles and the calibration, and the same component then shows the real atlas. You can supply your own with setMapConfig:

ts
import { setMapConfig } from '@/kit'

setMapConfig({
  calibration: { sx: 0.6583, sy: 0.6583, ox: 3756, oy: 5523 }, // game meters to atlas pixels
  tiles: {
    width: 8704,
    height: 10752,
    list: [{ url: 'https://example.com/map/0.png', x: 0, y: 0, w: 1450, h: 1536 }] // one entry per picture, in atlas pixels
  }
})
PropTypeDefaultDescription
zonesMapZone[][]Areas to draw. A MapZone is { id, label, shape, color?, dim? }, where shape is the same poly, box or sphere zone KZoneField edits.
markersMapMarker[][]Points to draw: { id, x, y, label?, icon?, color? }, with a lucide:<name> icon.
player{ x, y } | nullnullThe player's own position, drawn as a dot. The locate button goes to it.
selectedIdstring | nullnullThe zone to highlight.
labelsbooleantrueShow zone names on the map.
readoutbooleanfalseShow the game coordinates under the cursor.
focusIdstring | nullnullZone to center on and zoom to when focusTick changes.
focusTicknumber0Change it to (re)center on focusId or focusAt, even when the target is the same.
focusAt{ x, y } | nullnullA game point to center on (used when there is no focusId). It also applies when the map first appears.
focusMinZoomnumber3The least zoom a focusAt goes to. A closer zoom is kept.
placingbooleanfalsePlacement mode: crosshair cursor, and a click emits place instead of selecting.

Events:

  • selectZone(id): a zone was clicked.
  • selectMarker(id): a marker was clicked.
  • click(point): empty map was clicked, in game coordinates.
  • place(point): a click that did not drag while placing, rounded to two decimals.
  • hover(point): the game point under the cursor, or null when it leaves the map.

Slots: the default slot draws your own content inside the pannable layer (its scope has toLayer(x, y), toLayerRaw(x, y), span, spanH and zoom), banner shows a hint at the top, readout replaces the coordinates row and scale replaces the scale text. A template ref also exposes focusZone(id), focusPoint(x, y, minZoom?) and reset().

vue
<div style="height: 400px">
  <KMap :zones="zones" :markers="markers" :player="me" :selected-id="picked" labels readout @select-zone="picked = $event" />
</div>

KFlowCanvas ​

A node graph editor: pan, zoom, drag nodes, draw links between ports and delete a selection.

KFlowCanvas

Drag from an output dot to an input dot to link two nodes, click a link or node and press Delete to remove it, drag on empty space to pan and use the wheel to zoom. The canvas is controlled: it draws what you pass and reports what the user did, and you decide what that means. That keeps it usable under a dependency map, a visual scripter or a dialogue tree. Like KMap, it fills its parent, so give it a fixed height.

PropTypeDefaultDescription
nodesFlowNodeDef[]requiredThe nodes: { id, x, y, w, h, ports?, minW?, minH?, resizable?, locked?, class? }. With no ports a node has one input on the left and one output on the right.
edgesFlowEdgeDef[]requiredThe links: { id, from, to, color?, dashed?, width?, label? }, where from and to are { node, port }.
selectedstring[]noneSelected node ids. Omit it to let the canvas keep its own selection.
selectedEdgestring | nullnoneThe selected link id. Omit it to let the canvas keep its own.
readonlybooleanfalsePan and zoom only: no dragging, resizing, linking or deleting.
snapnumber8Grid size in pixels that moves and resizes snap to. 0 turns snapping off.
minZoomnumber0.2Smallest zoom.
maxZoomnumber2Largest zoom.
fitOnMountbooleantrueFrame every node once the canvas has a size.
dragMode'pan' | 'select''pan'What a plain drag on empty space does. 'select' draws a selection box and pans with the middle or right mouse button.
canConnect(connection) => string | nullnoneReturn a message to refuse a link, or null to allow it.

A port is { id, side: 'in' | 'out', title?, color?, max?, y?, showLabel? }. max limits how many links it accepts, and y is its distance from the node's top in pixels.

Events:

  • change(changes): final { id, x, y, w, h } of nodes the user moved or resized, sent when the gesture ends.
  • connect({ from, to }): a link was made. from is always the output port and to the input.
  • reject(message): a link the user tried to make was refused.
  • remove({ nodes, edges }): Delete or Backspace on a selection.
  • update:selected(ids) and update:selectedEdge(id): the selection changed.
  • node-dblclick(id) and canvas-dblclick(point): a double click, the point in canvas coordinates.

Slots: node draws the inside of each node (it gets node, selected and drop), edge shows a popover on the selected link, toolbar adds controls at the top left and controls adds buttons next to the zoom controls. A template ref exposes fit(), zoomTo(zoom), zoomBy(factor), focusNode(id), toWorld(x, y), getView() and setView(view).

vue
<div style="height: 400px">
  <KFlowCanvas :nodes="nodes" :edges="edges" @change="applyMoves" @connect="addEdge" @remove="removeSelection">
    <template #node="{ node }">{{ titles[node.id] }}</template>
  </KFlowCanvas>
</div>