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.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
v-model | string | '' | The item name. A name the catalog does not know stays visible as "Not in the item list". |
label | string | none | Label above the field. |
hint | string | none | Small help text under the field. |
placeholder | string | Pick an item… | Shown while empty. |
size | 'sm' | 'md' | 'md' | Field height. |
clearable | boolean | false | Adds a clear button. |
disabled | boolean | false | Disables the field. |
<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.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
v-model | string | '' | The model name, for example sultan. |
allowCustom | boolean | false | Adds a "Type a model name" switch for addon vehicles the list does not have. |
label | string | none | Label above the field. |
hint | string | none | Small help text under the field. |
placeholder | string | Pick a vehicle… | Shown while empty. |
size | 'sm' | 'md' | 'md' | Field height. |
clearable | boolean | false | Adds a clear button. |
disabled | boolean | false | Disables the field. |
<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.
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).
| Prop | Type | Default | Description |
|---|---|---|---|
v-model | string | '' | The group name (single mode). |
v-model:list | string[] | [] | The group names (with multiple). |
multiple | boolean | false | Edit a list of names with v-model:list instead of one name. |
label | string | none | Label above the field. |
hint | string | none | Small help text under the field. |
placeholder | string | Pick a group… | Shown while empty (single mode). |
size | 'sm' | 'md' | 'md' | Field height (single mode and text fallback). |
clearable | boolean | false | Adds a clear button (single mode). |
disabled | boolean | false | Disables the field. |
<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.
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:
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 }| Prop | Type | Default | Description |
|---|---|---|---|
v-model | Zone | null | null | The zone. |
name | string | required | Creator name shown while drawing. |
zoneLabel | string | required | Creator label shown while drawing, for example "Garage access zone". |
previewKey | string | required | Stable id for the in-world outline, for example garages:12:zone. |
label | string | Zone | Title of the row. |
description | string | none | Text shown while no zone is set. |
colour | number | none | Outline color index for the in-world preview. |
types | ZoneType[] | ['poly', 'box', 'sphere'] | Which shapes staff can choose. With one shape the shape switch is hidden. |
required | boolean | false | Shows a "Required" badge while empty. |
clearable | boolean | true | Allows removing the zone. |
teleport | (center) => unknown | none | Adds a "Teleport to center" button that calls this with the zone center. |
<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.
"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.
| Prop | Type | Default | Description |
|---|---|---|---|
v-model | Record<string, any> | null | null | The appearance object, or null for the default look. |
label | string | Appearance | Title of the row. |
description | string | none | Help text under the buttons. |
designable | boolean | true | Shows the "Design…" button. |
model | string | none | Ped model to start a fresh design from when there is no look yet, for example mp_m_freemode_01. |
disabled | boolean | false | Disables the buttons. |
Events: picked(appearance) fires after a look is copied or designed, so you can also adopt its model.
<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.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | required | Name of the step. |
icon | string | required | Icon, for example lucide:door-open. |
isSet | boolean | required | Whether a placement exists. |
detail | string | Placed | Text shown when set. |
description | string | none | Text shown when not set. |
required | boolean | false | Shows a "Required" badge while not set. |
locked | boolean | false | Greys the row out and hides the buttons. |
lockedReason | string | none | Why the row is locked. |
disabled | boolean | false | Disables the place button. |
setLabel | string | Place | Button text while not set. |
redoLabel | string | Redo | Button text once set. |
setAction | () => unknown | none | Called by the place button. The button shows a busy state while it runs. |
clearAction | () => unknown | none | Called by the remove button, shown while set. |
<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.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
v-model | SpeechRef | null | undefined | required | The voice line. null means no line. |
label | string | none | Label above the field. |
addLabel | string | Add a voice line | Text of the button shown while empty. |
hint | string | none | Help text shown once a line is set. |
disabled | boolean | false | Disables the field. |
suggest | string[] | all | Suggestion groups listed first: 'shop', 'greet', 'farewell', 'react'. |
preview | (value: SpeechRef) => unknown | none | Shows the play buttons and is called with what the game should say. |
hideVoice | boolean | false | Hides the forced voice box, which is rarely needed. |
<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.
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 }.
| Prop | Type | Default | Description |
|---|---|---|---|
v-model | ChallengeSpec | null | undefined | none | The spec: { steps: [{ kind, game?, difficulty?, stages?, duration? }] }. |
label | string | none | Label above the field. |
hint | string | none | Small help text. |
disabled | boolean | false | Disables the buttons. |
title | string | none | Title shown in the creator. |
subtitle | string | none | Subtitle shown in the creator. |
emptyText | string | None | Text shown while there is no challenge. |
<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.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
v-model | CameraPathKeyframe[] | [] | The recorded keyframes. |
v-model:settings | CameraPathSettings | DEFAULT_CAMERA_PATH_SETTINGS | { startDelay, speed, transition }, edited by the sliders. |
focus | { x, y, z } | null | required | The point the camera orbits. Recording is disabled while it is null. |
camera | CameraPathShot | null | none | A starting camera, passed on to capture. |
maxDistance | number | 80 | How far from focus the camera may fly, passed on to capture. |
capture | (options) => Promise<CameraPathKeyframe[] | null> | required | Records a path. Receives { focus, camera, maxDistance }. |
preview | (path) => Promise<unknown> | required | Plays a path. |
label | string | Camera path | Title of the row. |
emptyText | string | Optional · record a camera move | Text shown while empty. |
recordedHint | string | none | Extra text appended to the recorded summary. |
recordTitle | string | Fly and record a camera move | Tooltip of the record button. |
disabled | boolean | false | Disables the buttons and sliders. |
<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.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
v-model | number | null | null | The sprite or marker id. |
kind | 'marker' | 'blip' | required | Which reference list to browse. |
label | string | none | Label above the field. |
hint | string | none | Small help text. |
disabled | boolean | false | Disables the field. |
<KGameIconPicker v-model="sprite" kind="blip" label="Blip sprite" />KBlipColorPicker
Picks a blip color by id from the named list, with approximate swatches.
| Prop | Type | Default | Description |
|---|---|---|---|
v-model | number | null | null | The blip color id. |
label | string | none | Label above the field. |
hint | string | none | Small help text. |
disabled | boolean | false | Disables the field. |
<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.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
v-model | AnimRef | null | null | The chosen animation. |
label | string | none | Label above the field. |
placeholder | string | Choose an animation… | Shown while nothing is chosen. |
size | 'sm' | 'md' | 'sm' | Field height. |
disabled | boolean | false | Disables the field. |
clearable | boolean | false | Adds a remove button. |
previewModel | string | null | none | Preview on this ped model instead of the admin's own clone. |
previewAppearance | object | null | none | Preview with this ped look. |
subtitle | string | none | What the animation is for, shown in the picker header. |
kinds | AnimKind[] | all | Limit the picker to 'emote', 'clip' or 'scenario'. Also hides "My animations". |
<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.
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | required | Whether the dialog is shown. |
value | AnimRef | null | none | The animation to start from. |
previewModel | string | null | none | Preview on this ped model. |
previewAppearance | object | null | none | Preview with this ped look. |
subtitle | string | none | Text under the title. |
kinds | AnimKind[] | all | Limit the tabs. Also hides "My animations". |
Events: close() and select(anim).
<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.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
tweaks | AnimPlayback | required | The working copy: { flag, blendIn, blendOut, speed, duration }, each a number or null. Start from blankTweaks(). |
base | AnimRef | required | The original animation, shown as the placeholder of each field. |
length | number | null | none | Length of the clip in seconds, shown as a hint under the timing fields. |
<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.
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:
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
}
})| Prop | Type | Default | Description |
|---|---|---|---|
zones | MapZone[] | [] | Areas to draw. A MapZone is { id, label, shape, color?, dim? }, where shape is the same poly, box or sphere zone KZoneField edits. |
markers | MapMarker[] | [] | Points to draw: { id, x, y, label?, icon?, color? }, with a lucide:<name> icon. |
player | { x, y } | null | null | The player's own position, drawn as a dot. The locate button goes to it. |
selectedId | string | null | null | The zone to highlight. |
labels | boolean | true | Show zone names on the map. |
readout | boolean | false | Show the game coordinates under the cursor. |
focusId | string | null | null | Zone to center on and zoom to when focusTick changes. |
focusTick | number | 0 | Change it to (re)center on focusId or focusAt, even when the target is the same. |
focusAt | { x, y } | null | null | A game point to center on (used when there is no focusId). It also applies when the map first appears. |
focusMinZoom | number | 3 | The least zoom a focusAt goes to. A closer zoom is kept. |
placing | boolean | false | Placement 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 whileplacing, rounded to two decimals.hover(point): the game point under the cursor, ornullwhen 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().
<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.
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.
| Prop | Type | Default | Description |
|---|---|---|---|
nodes | FlowNodeDef[] | required | The 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. |
edges | FlowEdgeDef[] | required | The links: { id, from, to, color?, dashed?, width?, label? }, where from and to are { node, port }. |
selected | string[] | none | Selected node ids. Omit it to let the canvas keep its own selection. |
selectedEdge | string | null | none | The selected link id. Omit it to let the canvas keep its own. |
readonly | boolean | false | Pan and zoom only: no dragging, resizing, linking or deleting. |
snap | number | 8 | Grid size in pixels that moves and resizes snap to. 0 turns snapping off. |
minZoom | number | 0.2 | Smallest zoom. |
maxZoom | number | 2 | Largest zoom. |
fitOnMount | boolean | true | Frame 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 | null | none | Return 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.fromis always the output port andtothe input.reject(message): a link the user tried to make was refused.remove({ nodes, edges }): Delete or Backspace on a selection.update:selected(ids)andupdate:selectedEdge(id): the selection changed.node-dblclick(id)andcanvas-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).
<div style="height: 400px">
<KFlowCanvas :nodes="nodes" :edges="edges" @change="applyMoves" @connect="addEdge" @remove="removeSelection">
<template #node="{ node }">{{ titles[node.id] }}</template>
</KFlowCanvas>
</div>