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
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 :)