FiveM NUI — React + Vite
Language rule: Internal reasoning is in compact English. All messages and output displayed to the user must be in the user's language.
Stack: React 18 + TypeScript + Vite + Tailwind CSS v3.4.17 + Zustand
Fundamental Rules
base: "./"invite.config.ts— MANDATORY for assets to load in FiveM- Never fix Vite
rollupOptions.outputfilenames without[hash]— FiveM CEF caches NUI; fixed names (assets/[name].css) leave stale CSS for some players - Overlay/shell fill on transparent html: opaque hex +
background-image: linear-gradient(#111,#111)— neverrgba()/ Tailwindbg-*/70/opacityon the same rounded element as the panel fill (CEF bug on some iGPUs). Screen dim = sibling layer withoutborder-radius(::beforeinset-0). Toggle withdisplay: flex|none— never jQueryfadeIn/fadeOuton the overlay container - Use
remfor ALL sizes — NEVERpxfor layout (scales with player resolution) - Tailwind v4 uses OKLCH, which FiveM CEF does not support — use Tailwind v3.4.17
- FORBIDDEN:
backdrop-filter: blur(),filter: blur(),filter: drop-shadow()— cause FPS drop - FORBIDDEN: framer-motion, GSAP, react-spring — use pure CSS transitions/keyframes
- Global
overflow: hiddenanduser-select: none - Independent modules (Notify, Progress, HUD) outside
VisibilityProvider - Main interface (panels, crafts, dialogs) inside
VisibilityProviderwithSetNuiFocus isEnvBrowser()for data mocking in dev- Communication:
observe()to listen to NUI messages,Post.create()to send callbacks
Architecture
// main.tsx
ReactDOM.createRoot(document.getElementById("root")!).render(
<ThemeProvider>
{/* Always visible — do not block game input */}
<NotifyComponent />
<ProgressComponent />
{/* Controlled by VisibilityProvider — requires NuiFocus */}
<HashRouter>
<VisibilityProvider>
<AppContent />
</VisibilityProvider>
</HashRouter>
</ThemeProvider>
);
NUI Hooks
observe — Listen to Lua messages
observe<NotifyData>("module:notify", (data) => {
addNotify(data);
});
Post — Send callbacks to Lua
await Post.create("buy", { item: "water", qty: 1 });
Lua side
-- Open
SendNUIMessage({ action = "setNui", nui = "panel", data = { ... } })
SetNuiFocus(true, true)
-- Close callback
RegisterNUICallback("removeFocus", function(data, cb)
SetNuiFocus(false, false)
cb("ok")
end)
fxmanifest.lua Integration
ui_page "src/ui/build/index.html"
files {
"src/ui/build/index.html",
"src/ui/build/**/*", -- covers index-[hash].js/css
"src/ui/project/public/**/*",
}
Recommended Dependencies
{
"dependencies": {
"react": "^18", "react-dom": "^18",
"react-router-dom": "^7", "zustand": "^4",
"clsx": "^2", "tailwind-merge": "^3",
"lucide-react": "^0.5", "tailwindcss": "^3",
"autoprefixer": "^10", "postcss": "^8"
},
"devDependencies": {
"@vitejs/plugin-react-swc": "^3",
"typescript": "^5", "vite": "^5"
}
}
Avoid: MUI, Chakra UI, Ant Design, framer-motion, styled-components — all too heavy for FiveM CEF.
fxmind agent vision (NUI dump)
Prefer structured state over screenshots. The agent wires and unwires — do not ask the user to patch scripts:
fxmind_fivem_nui_wire{ resource }— patches fxmanifest + injects DOM probe intoui_pageensurebridge + resource; user opens NUI in-gamefxmind_fivem_nui_dumpfxmind_fivem_nui_unwirebefore finishing (mandatory cleanup)
Optional permanent integration (not required for agent debug): registerFxmindNuiDump(() => useStore.getState()) from bridge snippets.
For the full implementation guide (project structure, vite.config, responsive system, CSS restrictions, hooks source, Visibility/Animation/Theme providers, debugger): ui-guide.md