JSPM

fivem-ui-core

2.1.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 3
  • Score
    100M100P100Q37904F
  • License MIT

Core FiveM UI kit for Svelte: NUI event API, theming, and common components.

Package Exports

  • fivem-ui-core
  • fivem-ui-core/dist/fivem-ui-core.cjs.js
  • fivem-ui-core/dist/fivem-ui-core.es.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (fivem-ui-core) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

fivem-ui-core

English version first. Scroll for Polish below.

What this solves

  • Universal UI: works with ESX, QB and standalone — framework-agnostic.
  • One events API: consistent UI ↔ Lua communication. Use onEvent and sendEvent.
  • Themable: colors, fonts, radius — match your server branding.
  • Fast start: from install to working HUD in minutes.

Installation

npm i fivem-ui-core
  • Requires Svelte ^5 as a peer dependency.
  • Build your app (HUD, phone, inventory) in Svelte and import components and API from fivem-ui-core.

Quick start (Svelte)

  1. Wrap your app with ThemeProvider (default theme works out of the box, or pass your own):
<script>
  import { ThemeProvider, Notification, ProgressBar } from 'fivem-ui-core';
  let hp = 75;

  const theme = {
    colors: {
      primary: '#4CC9F0',
      success: '#2ecc71',
      warning: '#f39c12',
      error: '#e74c3c',
      surface: '#161616',
      onSurface: '#ffffff',
      border: '#2a2a2a'
    },
    radius: '10px',
    fontFamily: 'Inter, system-ui, sans-serif'
  };
</script>

<ThemeProvider {theme}>
  <Notification type="success" message="Logged in successfully!" />
  <ProgressBar value={hp} max={100} />
</ThemeProvider>
  1. Receive an event from Lua and display a notification:
import { onEvent } from 'fivem-ui-core';

onEvent('showNotify', (data) => {
  // { type: 'info' | 'success' | 'warning' | 'error', message: string }
  console.log('Notification:', data);
});
  1. Send a callback from UI to Lua:
import { sendEvent } from 'fivem-ui-core';
await sendEvent('callbackDone', { ok: true });

NUI communication (UI ↔ Lua)

From UI (Svelte) to Lua

  • Use sendEvent(eventName, data) — it POSTs to https://<resource>/<eventName> under FiveM.
  • In dev mode (outside FiveM) it logs and returns a mock Response { ok: true } so development stays smooth.
import { sendEvent } from 'fivem-ui-core';

await sendEvent('playerReady', { name: 'John' });

createNuiClient — timeouts, retries, JSON

Need more resiliency? Use the client with built-in timeouts, retries (exponential backoff) and logging.

import { createNuiClient } from 'fivem-ui-core';

const client = createNuiClient({
  timeoutMs: 5000,     // default 5000
  retries: 2,          // default 2 attempts
  backoffMs: 300,      // default 300ms, grows exponentially per attempt
  logger: (level, message, meta) => console[level]?.('[nui]', message, meta)
});

// raw Response
const res = await client.send('inventory:save', { items: [] });
if (!res.ok) throw new Error('Save error');

// auto JSON parse
const data = await client.sendJson('player:getProfile', { id: 123 });

Best practices:

  • Avoid event spam: batch updates and use debounce/throttle.
  • For critical actions use sendJson with response validation.
  • Show progress in UI; on error, retry or offer a retry action.

Lua side (client.lua):

-- Register a callback with the same name
RegisterNUICallback('playerReady', function(data, cb)
  print('UI said:', json.encode(data))
  cb({ ok = true })
end)

From Lua to UI (Svelte)

  • In Lua use SendNUIMessage with action and data.
  • In UI listen to that action via onEvent.
SendNUIMessage({
  action = 'showNotify',
  data = { type = 'info', message = 'New notification!' }
})
import { onEvent } from 'fivem-ui-core';

onEvent('showNotify', (data) => {
  // e.g., show a styled toast based on type
});

Useful FiveM tips

  • Remember focus: SetNuiFocus(true, true) so UI can receive input.
  • On close: SetNuiFocus(false, false).
  • Keep names consistent: SendNUIMessage.actiononEvent('<action>') and sendEvent('<event>')RegisterNUICallback('<event>').

Theming

  • Provide a theme to ThemeProvider.
  • It generates CSS variables used by components.

Theme keys:

  • colors.primary|success|warning|error|surface|onSurface|border
  • radius — component border radius
  • fontFamily — base font

Minimal example:

<script>
  import { ThemeProvider, defaultTheme } from 'fivem-ui-core';
  // copy defaultTheme and override only what you need
  const theme = {
    ...defaultTheme,
    colors: {
      ...defaultTheme.colors,
      primary: '#00E5A8'
    },
    radius: '12px'
  };
</script>

<ThemeProvider {theme}>
  <slot />
</ThemeProvider>

Theme presets and server palettes

  • Presets available: presetLight, presetDark, and example serverPalettes (red/blue/purple).
import { presetLight, presetDark, serverPalettes } from 'fivem-ui-core';

// Light
const themeLight = presetLight;

// Dark with server palette "purple" as primary
const themeDarkPurple = {
  ...presetDark,
  colors: {
    ...presetDark.colors,
    primary: serverPalettes.purple.primary
  }
};

Components (overview)

Notification

  • Props: type = 'info' | 'success' | 'warning' | 'error', message, icon?
  • Usage:
<Notification type="success" message="Operation completed" />
  • Props: open (bind), title
  • Slots: default, actions
<script>
  let open = true;
</script>

<Modal bind:open title="Sample modal">
  Modal content
  <div slot="actions">
    <button on:click={() => (open = false)}>Close</button>
  </div>
</Modal>

ProgressBar

  • Props: value, max = 100, color?
  • Slot: label (defaults to percentage)
<ProgressBar value={75} />
<ProgressBar value={30} color="#ff4d4f" />

ProgressCircle

  • Props: value, max = 100, size = 48, stroke = 6, color?
<ProgressCircle value={hp} max={100} size={64} />

StatusBar

  • Props: hp, hunger, stamina (0–100)
<StatusBar hp={75} hunger={50} stamina={90} />

Hotbar

  • Props: items: { slot: number; label?: string; icon?: string }[], selected?: number
<Hotbar items={[{ slot: 1, label: 'Pistol' }, { slot: 2, label: 'Medkit' }]} selected={1} />

RadialMenu

  • Props: open, items: { id: string; label: string }[]
  • Events: on:select={(e) => e.detail /* id */}
<RadialMenu
  open
  items={[{ id: 'anim', label: 'Animation' }, { id: 'veh', label: 'Vehicle' }]}
  on:select={(e) => console.log('Selected:', e.detail)}
/>

List

  • Props: items: { id; label; description? }[], selectedId?, onSelect?
<script>
  import { List } from 'fivem-ui-core';
  let selected = null;
</script>

<List items={[{ id: 1, label: 'Option A' }, { id: 2, label: 'Option B', description: 'Desc' }]} onSelect={(id) => selected = id} />

TextInput

  • Props: value, placeholder?, label?, type = 'text'|'password'|'number', disabled?, name?, id?, onEnter?
<script>
  import { TextInput } from 'fivem-ui-core';
  let nick = '';
  function handleEnter(v) { console.log('Enter:', v); }
</script>

<TextInput bind:value={nick} label="Nick" placeholder="Type your nick" onEnter={handleEnter} />

ContextMenu

  • Props: open, x, y, items: { id; label; disabled? }[]
  • Events: on:select={(e) => e.detail /* id */}
<script>
  import { ContextMenu } from 'fivem-ui-core';
  let menu = { open: true, x: 300, y: 200 };
  const items = [{ id: 'copy', label: 'Copy' }, { id: 'del', label: 'Delete', disabled: true }];
</script>

<ContextMenu {items} open={menu.open} x={menu.x} y={menu.y} on:select={(e) => console.log('Selected:', e.detail)} />

Snackbar (toast queue)

  • Usage: Snackbar component + toasts store and helpers push/remove/clear
<script>
  import { Snackbar, push } from 'fivem-ui-core';
  function show() { push('Operation completed', { type: 'success', timeout: 2000 }); }
</script>

<button on:click={show}>Show toast</button>
<Snackbar position="top-right" />

Adapters (ESX and QB)

  • Convenience adapters esx and qb add prefixes for actions/events (e.g. esx:notify, qb:ready).
import { esx, qb } from 'fivem-ui-core';

// Listen for Lua event (SendNUIMessage action = 'esx:notify')
const off = esx.on('notify', (data) => {
  console.log('ESX notify:', data);
});

// Send response to Lua (RegisterNUICallback 'qb:ready')
await qb.send('ready', { ok: true });

off();

Dev mode vs. FiveM

  • Under FiveM, UI → Lua uses GetParentResourceName() and POST to https://<resource>/<event>.
  • In dev (browser outside FiveM), sendEvent does not send network requests — it logs and returns a mock Response { ok: true } so you can develop UI without errors.

Tests (Vitest + Testing Library)

  • Environment: jsdom, Svelte compilation via Vite plugin.

Scripts:

npm test         # once
npm run test:watch

Scope examples:

  • NUI (onEvent/onceEvent/offEvent/sendEvent)
  • Theme (themeToCssVars, presets)
  • Components (List, TextInput, Snackbar)

Roadmap / ideas

  • Integrations: more helpers for inventory/phone/commands
  • More components: panel menu, tables, tooltips, dropdowns, buttons/icon-buttons
  • Accessibility: better keyboard and ARIA support

PRs/issues welcome.


License

MIT

Author

Bl4ck3d :)


Polski

Przyjazny core UI dla FiveM w Svelte. Dostajesz wspólne API do komunikacji NUI (UI ↔ Lua), prosty system motywów (theme) oraz gotowe komponenty, z których korzysta większość serwerów (Notification, Modal, ProgressBar, StatusBar, Hotbar, RadialMenu).

Co to rozwiązuje

  • Uniwersalne UI: działa z ESX, QB i standalone – bez zależności od frameworka.
  • Jedno API zdarzeń: koniec z każdym pluginem pisanym inaczej. Masz onEvent i sendEvent.
  • Personalizacja wyglądu: kolory, czcionki, promienie – dostosujesz do brandingu serwera.
  • Szybki start: od instalacji do działającego HUD-a w kilka minut.

Instalacja

npm i fivem-ui-core
  • Wymaga Svelte ^5 jako peer dependency.
  • Budujesz swoją aplikację (HUD, telefon, inventory) w Svelte i po prostu importujesz komponenty oraz API z fivem-ui-core.

Szybki start (Svelte)

  1. Owiń aplikację w ThemeProvider (domyślny theme działa od razu, ale możesz podać własny):
<script>
  import { ThemeProvider, Notification, ProgressBar } from 'fivem-ui-core';
  let hp = 75;

  const theme = {
    colors: {
      primary: '#4CC9F0',
      success: '#2ecc71',
      warning: '#f39c12',
      error: '#e74c3c',
      surface: '#161616',
      onSurface: '#ffffff',
      border: '#2a2a2a'
    },
    radius: '10px',
    fontFamily: 'Inter, system-ui, sans-serif'
  };
</script>

<ThemeProvider {theme}>
  <Notification type="success" message="Zalogowano pomyślnie!" />
  <ProgressBar value={hp} max={100} />
</ThemeProvider>
  1. Odbierz zdarzenie z Lua i pokaż notyfikację:
import { onEvent } from 'fivem-ui-core';

onEvent('showNotify', (data) => {
  // { type: 'info' | 'success' | 'warning' | 'error', message: string }
  console.log('Powiadomienie:', data);
});
  1. Wyślij callback z UI do Lua:
import { sendEvent } from 'fivem-ui-core';
await sendEvent('callbackDone', { ok: true });

Komunikacja NUI (UI ↔ Lua)

Z UI (Svelte) do Lua

  • Użyj sendEvent(eventName, data) – wewnętrznie robi POST do https://<resource>/<eventName>.
  • Działa w FiveM. W trybie dev (poza FiveM) loguje do konsoli (no-op), aby nie blokować pracy.
import { sendEvent } from 'fivem-ui-core';

await sendEvent('playerReady', { name: 'John' });

createNuiClient – timeouty, retry, JSON

Jeśli potrzebujesz większej niezawodności, użyj klienta z wbudowanymi timeoutami, retry (exponential backoff) i loggerem.

import { createNuiClient } from 'fivem-ui-core';

const client = createNuiClient({
  timeoutMs: 5000,     // domyślnie 5000
  retries: 2,          // domyślnie 2 próby
  backoffMs: 300,      // domyślnie 300ms, rośnie wykładniczo per próba
  logger: (level, message, meta) => console[level]?.('[nui]', message, meta)
});

// surowy Response
const res = await client.send('inventory:save', { items: [] });
if (!res.ok) throw new Error('Błąd zapisu');

// automatyczny parse JSON
const data = await client.sendJson('player:getProfile', { id: 123 });

Najlepsze praktyki:

  • Unikaj spamu eventów: batchuj aktualizacje, używaj debouncingu/throttlingu.
  • Dla akcji krytycznych używaj sendJson z walidacją odpowiedzi.
  • W UI pokazuj stan „w toku”, a przy błędzie – spróbuj ponownie lub zaproponuj retry.

Po stronie Lua (client.lua):

-- Rejestrujesz callback o tej samej nazwie
RegisterNUICallback('playerReady', function(data, cb)
  print('UI powiedziało:', json.encode(data))
  cb({ ok = true })
end)

Z Lua do UI (Svelte)

  • Po stronie Lua wołasz SendNUIMessage z action i data.
  • Po stronie UI nasłuchujesz tego action przez onEvent.
SendNUIMessage({
  action = 'showNotify',
  data = { type = 'info', message = 'Nowe powiadomienie!' }
})
import { onEvent } from 'fivem-ui-core';

onEvent('showNotify', (data) => {
  // zrób np. toast ze stylowaniem wg type
});

Użyteczne wskazówki FiveM

  • Pamiętaj o focusie: SetNuiFocus(true, true) aby UI mogło przyjmować input.
  • Po zamknięciu UI: SetNuiFocus(false, false).
  • Zadbaj o zgodność nazw: SendNUIMessage.actiononEvent('<action>') oraz sendEvent('<event>')RegisterNUICallback('<event>').

Theming (motywy)

  • Motyw przekazujesz do ThemeProvider.
  • Wewnątrz generowane są zmienne CSS, które wykorzystują komponenty.

Klucze motywu:

  • colors.primary|success|warning|error|surface|onSurface|border
  • radius – promień zaokrąglenia komponentów
  • fontFamily – główna czcionka

Minimalny przykład:

<script>
  import { ThemeProvider, defaultTheme } from 'fivem-ui-core';
  // możesz skopiować defaultTheme i zmienić tylko wybrane pola
  const theme = {
    ...defaultTheme,
    colors: {
      ...defaultTheme.colors,
      primary: '#00E5A8'
    },
    radius: '12px'
  };
</script>

<ThemeProvider {theme}>
  <slot />
</ThemeProvider>

Presety motywów i palety serwerowe

  • Dostępne są gotowe presety: presetLight, presetDark oraz przykładowe serverPalettes (red/blue/purple).
import { presetLight, presetDark, serverPalettes } from 'fivem-ui-core';

// Light
const themeLight = presetLight;

// Dark z podmienioną paletą primary na serwerową "purple"
const themeDarkPurple = {
  ...presetDark,
  colors: {
    ...presetDark.colors,
    primary: serverPalettes.purple.primary
  }
};

Komponenty (przegląd)

Notification

  • Props: type = 'info' | 'success' | 'warning' | 'error', message, icon?
  • Użycie:
<Notification type="success" message="Operacja zakończona" />
  • Props: open (bind), title
  • Sloty: default, actions
<script>
  let open = true;
</script>

<Modal bind:open title="Przykładowy modal">
  Treść modala
  <div slot="actions">
    <button on:click={() => (open = false)}>Zamknij</button>
  </div>
</Modal>

ProgressBar

  • Props: value, max = 100, color?
  • Slot: label (domyślnie procent)
<ProgressBar value={75} />
<ProgressBar value={30} color="#ff4d4f" />

ProgressCircle

  • Props: value, max = 100, size = 48, stroke = 6, color?
<ProgressCircle value={hp} max={100} size={64} />

StatusBar

  • Props: hp, hunger, stamina (0–100)
<StatusBar hp={75} hunger={50} stamina={90} />

Hotbar

  • Props: items: { slot: number; label?: string; icon?: string }[], selected?: number
<Hotbar items={[{ slot: 1, label: 'Pistolet' }, { slot: 2, label: 'Apteczka' }]} selected={1} />

RadialMenu

  • Props: open, items: { id: string; label: string }[]
  • Zdarzenia: on:select={(e) => e.detail /* id */}
<RadialMenu
  open
  items={[{ id: 'anim', label: 'Animacja' }, { id: 'veh', label: 'Pojazd' }]}
  on:select={(e) => console.log('Wybrano:', e.detail)}
/>

List

  • Props: items: { id; label; description? }[], selectedId?, onSelect?
<script>
  import { List } from 'fivem-ui-core';
  let selected = null;
</script>

<List items={[{ id: 1, label: 'Opcja A' }, { id: 2, label: 'Opcja B', description: 'Opis' }]} onSelect={(id) => selected = id} />

TextInput

  • Props: value, placeholder?, label?, type = 'text'|'password'|'number', disabled?, name?, id?, onEnter?
<script>
  import { TextInput } from 'fivem-ui-core';
  let nick = '';
  function handleEnter(v) { console.log('Enter:', v); }
</script>

<TextInput bind:value={nick} label="Nick" placeholder="Wpisz nick" onEnter={handleEnter} />

ContextMenu

  • Props: open, x, y, items: { id; label; disabled? }[]
  • Zdarzenia: on:select={(e) => e.detail /* id */}
<script>
  import { ContextMenu } from 'fivem-ui-core';
  let menu = { open: true, x: 300, y: 200 };
  const items = [{ id: 'copy', label: 'Kopiuj' }, { id: 'del', label: 'Usuń', disabled: true }];
</script>

<ContextMenu {items} open={menu.open} x={menu.x} y={menu.y} on:select={(e) => console.log('Wybrano:', e.detail)} />

Snackbar (kolejka toastów)

  • Użycie: komponent Snackbar + store toasts oraz helpery push/remove/clear
<script>
  import { Snackbar, push } from 'fivem-ui-core';
  function show() { push('Operacja zakończona', { type: 'success', timeout: 2000 }); }
</script>

<button on:click={show}>Pokaż toast</button>
<Snackbar position="top-right" />

Adaptery (ESX i QB)

  • Dla wygody nazw i kompatybilności dodane zostały adaptery esx i qb, które prefixują zdarzenia/akcje (np. esx:notify, qb:ready).
import { esx, qb } from 'fivem-ui-core';

// Nasłuch zdarzenia z Lua (SendNUIMessage action = 'esx:notify')
const off = esx.on('notify', (data) => {
  console.log('ESX notify:', data);
});

// Wysłanie odpowiedzi do Lua (RegisterNUICallback 'qb:ready')
await qb.send('ready', { ok: true });

off();

Tryb dev vs. FiveM

  • W FiveM UI → Lua używa GetParentResourceName() i POST do https://<resource>/<event>.
  • W trybie dev (przeglądarka poza FiveM) sendEvent nie wysyła żądań – loguje dane i zwraca sztuczną odpowiedź Response { ok: true }, abyś mógł normalnie rozwijać UI bez błędów sieciowych.

Testy (Vitest + Testing Library)

  • Środowisko: jsdom, kompilacja Svelte przez wtyczkę Vite.

Skrypty:

npm test         # jednorazowo
npm run test:watch

Przykłady zakresu testów:

  • NUI (onEvent/onceEvent/offEvent/sendEvent)
  • Theme (themeToCssVars, presety)
  • Komponenty (List, TextInput, Snackbar)

Roadmap / pomysły

  • Integracje: dodatkowe helpery pod inventory/telefon/komendy
  • Więcej komponentów: menu panelowe, tabele, tooltipy, dropdowny, przyciski/icon-buttony
  • Accessibility: pełniejsze wsparcie klawiatury i ARIA

Jeśli chcesz coś dodać – PR/issue mile widziane.


Licencja

MIT

Autor

Bl4ck3d :)