Development
Layout
fxmanifest.lua
import.lua OOP wrapper for other resources (shared_script '@np_menu/import.lua')
shared/lib/ NpMenu namespace, validators, locale loader
shared/config.lua Config.*
shared/config/shops.lua Config.Interact, Config.Context, Config.Shops + shared price functions
shared/locales/ en / de, one file per built-in menu
bridge/framework/client/ 0_common 1_qbx 2_qb 3_esx 9_standalone
bridge/framework/server/ 0_common 1_qbx 2_qb 3_esx 9_standalone
client/core/ 00_state … 13_api: transport, settings, registry, permissions, serializer, back stack,
NUI callbacks, walk-mode input, keys, radial, context, dialogs, exports
client/compat/ox_lib.lua ox_lib compatibility (Config.OxCompat)
client/menus/ built-in menus: dev, quick, radial, context, settings, shop_*
server/core/ payload checks, rate limits, action registry, ace queries, server exports
server/menus/ server halves: admin, interactions, shops
web/ NUI (React 18 + Vite 5 + TS strict + zustand)
design/ Nimbus UI tokens and components (imported unchanged)
docs/ ARCHITECTURE.md (contract), IDEAS.md, mockups/
tests/ plain-Lua harness + run.shFiles are numbered so the glob order in fxmanifest.lua is deterministic. Code resolves NpMenu.X when it's called, never at file load.
NUI
cd web
npm install
npm run dev # http://localhost:5175, browser mock mode + dev panel
npm run build # tsc --noEmit && vite build → web/dist
npx vite preview # http://localhost:4318, serves web/distOutside FiveM, src/dev installs a fake Lua backend that answers every web → Lua callback and drives the UI through the same path SendNUIMessage uses. It paints a game scene behind the UI and ships sample menus: Development (with submenus, an 80-row player list and deep search), every item type and state, the quick grid, a Benny's shop with preview and cart, the staff skin, a vehicle radial, a car context menu and the dialogs.
Keyboard input is forwarded as Lua-style input messages (walk-mode simulation) by default. Switch to NUI focus in the dev panel to use the real keyboard path.
| Key | Action |
|---|---|
| F10 | Dev menu |
| F1 | Quick grid |
| hold R | Radial wheel (release selects) |
| G | Context menu |
| / | Search |
| hold Y | Pin the selected row |
| right mouse | Back |
| URL parameter | Effect |
|---|---|
?scene=dev|types|grid|radial|context|dialog|form|confirm|shop|staff|theme|toasts | Start scene |
&material=liquid | Liquid Glass |
&position=left|right|center | Menu position |
&density=compact | Compact rows |
&brand=%233d74ff&strength=accent|tiles|glass | Theme |
&locale=de | Language |
&focus=walk|nui | Focus mode |
&scale=1.2 | Scale |
&shot=1 | No dev panel, animations off (for screenshots) |
Screenshots
npm run build && npx vite preview
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless=new --disable-gpu --hide-scrollbars \
--virtual-time-budget=6000 --window-size=1920,1080 --screenshot=screenshots/dev.png \
'http://localhost:4318/?shot=1&scene=dev'The renders live in web/screenshots/. Then run npm run sync:images in this docs repo.
Tests
tests/run.sh # VERBOSE=1 shows resource log lines and stubbed nativesIt runs luac -p on every Lua file, then the client and server scenario suites (tests/test_client.lua, tests/test_server.lua). tests/harness.lua stubs FiveM natives, events, exports, KVP and NUI with a cooperative thread scheduler, and boots the resource files in fxmanifest.lua order, each side in its own Lua process. Needs Lua 5.4 (LUA= / LUAC= override the binaries).
Contract
docs/ARCHITECTURE.md is the single source of truth for the menu definition, wire types, NUI protocol, exports and server actions. When code and that file disagree, the code is fixed. See the NUI contract for a summary.