backdrop-filter in FiveM's CEF only blurs what the page painted: the game is composited
below afterwards, so the glass becomes a flat layer. The way out is to get the game frame
inside the page and draw the glass in WebGL.
The core lives in one place: the public mri-fivem-liquid-glass package (npm; repo
mur4i/mri-fivem-liquid-glass; docs in
English and pt-BR, demo on GitHub Pages, example resource). In-game log: [liquid-glass].
@mriqbox/ui-kit (4.34.0+) depends on it and only re-exports startGameGlass
(src/lib/gameGlass.ts); what belongs to the suite stays in the kit: suiteGlass.ts
(liquid theme), story Lib/GameGlass, THEMING.md.
- Glass fixes and improvements go to the package. There, a
feat:/fix:commit onmainbumps the version and publishes by itself (trusted publisher). Before pushing:npm run typecheck,npm run build,npm test(visual test in headless Chrome). - The kit picks up the new version through
^1.xon the next install; an API break becomes a major in the package and requires bumping the dependency in the kit. - ui-kit: a push to
mainpublishes a version. - Never in a copy inside the resource.
How it works
- cfx hook: a WebGL texture created with 1 blue pixel and the
texParameterfsequence onWRAP_T(CLAMP, MIRRORED_REPEAT, REPEAT, CLAMP) becomes the game frame. Origin: citizenfx'scfx-ui;screencapturedoes the same (packages/gameview/webgl.ts). The call order is what the hook recognizes. - Two game images: one at 1/2 of the screen, sharp (for refraction), and one at 1/4, blurred with a gaussian in short passes and temporal smoothing.
- Per-element clipping: fixed canvas behind the UI; on each
[data-glass]it draws with that element'sborder-radius(SDF), the parents' opacity and the clip of the parent withoverflow. - Liquid (
data-glass="liquid"): in the bevel band the surface "tilts", pulling the sharp background from outside in (background lines bend), with color separation and specular highlight along the light direction. The middle stays blurred, for reading. - Broken frame: a 32x18 thumbnail read without stalling (PBO + fence). Black or a sudden darkening: keep the last good frame. Black for ~3 s: redo the hook.
- Background tone: sets
data-glass-backdrop="bright|dark"so text can switch color.
Technique decisions and details: references/technique.md.
Not WebGPU
FiveM's CEF is Chromium 103 (libcef.dll 103.0.5060.141); WebGPU only exists from 113 on,
and the game hook is a GL texture. WebGL2 works on 103.
Apply to a resource
- Resource with the kit:
@mriqbox/ui-kit4.32.0 or newer (4.34.0+ already uses the package). With a bundler and no kit:npm i mri-fivem-liquid-glass. Plain HTML without a bundler (e.g. ox_target'sweb/): copydist/mri-liquid-glass.iife.min.jsfrom the package into the resource (exposeswindow.MriLiquidGlass.startGameGlass), list it in the fxmanifestfilesand note the version. The jsDelivr CDN also works, but needs internet on the client. - Once at NUI boot:
Options:import { startGameGlass } from '@mriqbox/ui-kit' startGameGlass({ fallbackImage: import.meta.env.DEV ? '/dev-bg.jpg' : null })scale(0.25),blur(sigma in px, 14),saturation(1.15),darken(1),temporal(0.55; 1 turns it off),light([-0.6, -0.8], light from the top left),zIndex(0),selector. Returns{ refresh, stop }. - CSS:
html,bodyand the root transparent; root above the canvas (position: relative; z-index: 1), or the blur covers the UI;- on the element: no
backdrop-filter; a low translucent background (.04to.10) as tint, a 1px light border, aninset 0 1px 0highlight, a shadow below; - text:
[data-glass-backdrop="bright"] { color: <dark> }.
- Choosing the effect:
data-glass: a large panel with content (list, form). A bevel distracts there.data-glass="liquid": a small highlighted piece (button, pill, orb, dock, HUD).- Fine tuning:
data-glass-bezelanddata-glass-refraction(px),data-glass-dispersionanddata-glass-specular(0..1). Any of them turns the bevel on.
- Suite theme: with
startSuiteGlass()instead ofstartGameGlass, the/uiconfigliquidtheme (data-theme='liquid'on<html>) puts glass on every top-level.mri-surface, without marking anything; on other themes only manualdata-glassapplies. Don't usemri-surfaceon an always-open screen (HUD): each page with glass redraws every frame. - A suite resource doesn't hardcode glass: the surface is
mri-surface border bg-background(follows the/uiconfigtheme and opacity), withoutdata-glassor its own tint. Bevel (data-glass="liquid") only whendata-themeisliquid, switched by theme state. Focus through Tailwind'sring-*: liquid replacesbox-shadowbut adds--tw-ring-shadow. Example:mri_Qpausemenu(web/src/lib/theme.ts,useBevel).
Rules and limits
- The canvas sits below the whole UI: the glass only shows the game. Glass over another opaque NUI panel doesn't blur the panel; glass over glass works.
scaleandtranslatework; rotation doesn't. Radius fromborder-top-left-radius. Clip only by the first parent withoverflow: a glass element inside a scrolling list leaks out of the panel. Glass only on the top surface;startSuiteGlass(kit 4.33.1+) already ignores.mri-surfacenested in another.- Radius, clip and attributes are cached; changed through inline
style:refresh(). - The loop runs while there is a
[data-glass]in the DOM and redraws the canvas every visible frame. Hidden doesn't draw; no element, it stops. Screen closed: remove it from the DOM. - DUI (
?mode=dui) and outside the game withoutfallbackImage: it doesn't start. mri_Qadmin iframe: untested.
Verify
- Browser: the site's playground (
npm run docs:devin a clone ofmri-fivem-liquid-glass, http://localhost:5173/mri-fivem-liquid-glass/playground, accepts a dropped GTA screenshot; live at mur4i.github.io/mri-fivem-liquid-glass/playground), the kit'sLib/GameGlassstory (npx storybook dev -p 6016in a clone ofmri-ui-kit) or the resource's dev withfallbackImage, and the screenshot:
It waits in real time (withnode scripts/shot.mjs "http://localhost:6016/iframe.html?id=lib-gameglass--showcase&viewMode=story" ./glass.png 10000--virtual-time-budgetrAF barely runs). Chrome comes fromscripts/cdp.mjs. The first Storybook load optimizes dependencies: if it comes out empty, run again. Check: blur only inside, corners, circle, opacity, lines bending in the bevel, thin highlight. - Game (only it shows): frame arriving, first seconds, loading and pause, fast camera
(flicker), FPS and
resmonwith the screen open. Client log inFiveM.app\logs\CitizenFX_log_*.log(FiveM folder), filter[liquid-glass](kit up to 4.33.x:[gameGlass]). - Say what was seen where (browser or game) and what was left untested.
Scene backgrounds (mockup)
Real game screenshots from an MRI server, without black bars or FiveM branding, in assets/.
They serve as fallbackImage, as background for screenshots and mockups of any NUI outside
the game, and as project demo images:
| File | Scene | Good for |
|---|---|---|
gta-sunset-player.webp (1920x864) |
character from behind at sunset, MRI sign on the hill | contrast background (bright sky, dark body), shows the blur well; used by the liquid glass demo |
gta-vinewood-hill.webp (1920x862) |
hill with the MRI QBOX sign, blue daytime sky | clean bright background, testing text over a bright scene |
Use as object-fit: cover (2.2:1 ratio; at 16:9 the sides get cropped). In a project, copy the
file into it, don't point at the skill folder.