fivem-ui-core
v2.1.0
Published
Core FiveM UI kit for Svelte: NUI event API, theming, and common components.
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
onEventandsendEvent. - 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)
- 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>- 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);
});- 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 tohttps://<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
sendJsonwith 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
SendNUIMessagewithactionanddata. - In UI listen to that
actionviaonEvent.
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.action↔onEvent('<action>')andsendEvent('<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|borderradius— component border radiusfontFamily— 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 exampleserverPalettes(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" />Modal
- 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:
Snackbarcomponent +toastsstore and helperspush/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
esxandqbadd 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 tohttps://<resource>/<event>. - In dev (browser outside FiveM),
sendEventdoes not send network requests — it logs and returns a mockResponse { 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:watchScope 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
onEventisendEvent. - 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)
- 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>- 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);
});- 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 dohttps://<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
sendJsonz 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
SendNUIMessagezactionidata. - Po stronie UI nasłuchujesz tego
actionprzezonEvent.
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.action↔onEvent('<action>')orazsendEvent('<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|borderradius– promień zaokrąglenia komponentówfontFamily– 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,presetDarkoraz przykładoweserverPalettes(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" />Modal
- 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+ storetoastsoraz helperypush/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
esxiqb, 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 dohttps://<resource>/<event>. - W trybie dev (przeglądarka poza FiveM)
sendEventnie 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:watchPrzykł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 :)
