npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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 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" />

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: 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" />

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