Every EndCore interface, from the HUD to character select to the inventory, renders from one stylesheet and one small JavaScript runtime served by en-ui. Follow the rules on this page and your resource will look like it shipped with the framework.
This page covers the look and the rules. For the building blocks, see CSS tokens and components, NUI runtime and Icons.
Using it
Load the stylesheet before your own, and the runtime before your own script:
<link rel="stylesheet" href="https://cfx-nui-en-ui/ui/encore.css">
<link rel="stylesheet" href="style.css"> <!-- layout only -->
<script src="https://cfx-nui-en-ui/ui/encore.js"></script>
<script src="app.js"></script>-- fxmanifest.lua
dependencies { 'en-ui' }cfx-nui-<resource> serves any file listed in that resource's files {}, which is how assets cross resource boundaries in NUI. en-ui lists both files. It has no dependencies of its own, so any resource can depend on it.
en-loading is the one exception. A loading screen renders before cfx-nui-en-ui is reliably reachable, so en-loading carries its own copy of the :root token block.
The rule
Your resource's
style.csscontains layout only. No colours, no typefaces, no easing curves, no durations. If a value you need is missing, add it toencore.cssinstead of defining it locally.
This is what keeps a hunger bar the same colour in the HUD, character select and the consumables menu, and what lets the whole framework change its look in one place.
Art direction: Field Equipment
Interfaces read as scavenged military hardware: warm near-black housings, hairline rules, stencil labels and amber phosphor readouts. Nothing is glossy, nothing is rounded, nothing bounces.
The game is always behind the interface. Panels are translucent and the palette stays desaturated, so the world reads as continuous rather than covered.
| Never | Pure grey (it reads as "dark mode website"), rounded corners, drop shadows on panels, bouncy easing, more than one accent colour |
| Always | Warm charcoal surfaces, 1px hairlines, uppercase wide-tracked labels, tabular numerals, translucency so the world shows through |
One piece of geometry repeats everywhere: the clipped top-left corner on buttons. It is what ties a button to a panel to a badge. Toggles are rectangular sliding blocks, not pills, and panels take corner brackets like a targeting reticle.
Type
FiveM runs on Windows, so EndCore uses faces that ship with the operating system. There are no font downloads, no flash of unstyled text, and no content security policy problems.
| Face | Used for | Notes |
|---|---|---|
| Bahnschrift | Display type and UI chrome | A variable-width industrial DIN. Condense large type with font-variation-settings: 'wdth' 85 to 92 |
| Consolas | Every number | Always with font-variant-numeric: tabular-nums, so digits don't shift width as values tick |
Letter-spacing does most of the typographic work:
| Text | Tracking |
|---|---|
| Stencil labels | 0.22em |
| Display type | 0.14em |
| UI text | 0.06em |
| Body text | 0.012em |
Colour
There is one signal colour, amber (#d9a441), plus oxide red (#b33a2b) for danger. That is all the colour the chrome gets.
Survival stats have fixed hues, so a stat is the same colour everywhere it appears:
| Stat | Token | Colour |
|---|---|---|
| Health | --enc-health | #b8453a |
| Hunger | --enc-hunger | #c98a3c |
| Thirst | --enc-thirst | #4d94a8 |
| Radiation | --enc-radiation | #a8c23a, a sickly yellow-green |
| Infection | --enc-infection | #8b5a9e, purple |
| Immunity | --enc-immunity | #7a9440 |
| Armour | --enc-armor | #6d8ba8 |
| Stamina | --enc-stamina | #8a9a4a |
| Oxygen | --enc-oxygen | #8fb8c9 |
| Cold | --enc-cold | #5a8fa8 |
| Heat | --enc-heat | #c4622f |
Use .enc-bar--hunger and the other stat modifiers instead of colouring a bar by hand. See Gauges.
Fullscreen menus and persistent overlays
Menus the player opens and overlays that stay on screen during play follow different rules:
| Fullscreen menu | Persistent overlay (HUD, notifications) | |
|---|---|---|
| Texture | Encore.mountTexture() | None. Grain over hours of play is tiring |
| Surfaces | --enc-panel | --enc-glass, with the --enc-legible text shadow on bare text |
| Sizing | rem off the default root | Put class="enc-overlay" on html. The root font size tracks screen height (16px at 1080p), so everything in rem scales as one object |
| Presence | Always shown while open | Contextual: show a readout when it needs attention, let it recede when it doesn't |
Motion
Everything decelerates and nothing overshoots.
| Use | Easing | Duration |
|---|---|---|
| Hover | --enc-ease | 150ms (--enc-d-fast) |
| State changes and transitions | --enc-ease | 240ms (--enc-d-base) |
| Entrances and reveals | --enc-ease-hard | 650ms (--enc-d-reveal) |
Any set of things that appears together (a roster, an inventory grid, a quest list) should cascade in with .enc-stagger and Encore.stagger(nodes). The cascade is what makes an interface feel authored rather than dumped on screen.
prefers-reduced-motion is honoured globally: animations and transitions collapse to almost nothing, and the grain and haze stop drifting. Motion is decoration, never information, so nothing breaks when it is off.
Texture
Encore.mountTexture() injects the texture layers once, so you never repeat the markup:
| Layer | Default | What it does |
|---|---|---|
| Grain | on | Drifting SVG film grain. The biggest single contributor to a filmic rather than flat look |
| Scanlines | on | Very faint horizontal lines, like a CRT readout |
| Vignette | on | Darkens the edges and pulls focus inward |
| Haze | off | Slow drifting amber dust |
NUI pages stay mounted over the game after their menu closes. If the texture stays visible, players see the world through grain and scanlines. So:
- Mount the texture hidden:
Encore.mountTexture({ visible: false }). - Call
Encore.showTexture(true)when your menu opens. - Call
Encore.showTexture(false)when it closes.
Encore.mountTexture({ visible: false });
Encore.onMessage({
open: () => { root.hidden = false; Encore.showTexture(true); },
close: () => { root.hidden = true; Encore.showTexture(false); },
});Persistent overlays never mount texture.
Security: player-authored strings
Character names, chat messages, item labels, group names and placed content are written by players. If you insert them as HTML, one player can inject markup into another player's interface.
Always build player-authored text with Encore.el(), which sets textContent. Never assign server data to innerHTML, and never build HTML strings from it.
// Safe: the label is inserted as text
row.appendChild(Encore.el('span', 'enc-label', item.label));
// Unsafe: never do this with anything from the server
row.innerHTML = `<span class="enc-label">${item.label}</span>`;The en-ui services (notifications, prompts, progress bars, dialogs and NPC conversations) already insert text safely.
Shared services
Don't rebuild notifications, progress bars, input dialogs or prompts in your own page. en-ui renders them for every resource. Call them from Lua through the library: encore.notify, encore.progress, encore.inputDialog, encore.showPrompt and encore.hidePrompt. Don't message en-ui's page directly. See UI services.
If your resource needs a component that doesn't exist yet, add it to en-ui as a service rather than building a one-off.
Checklist before shipping
style.cssdefines no colours, fonts, easing or durations.- Numbers use
.enc-num, or--enc-font-monowith tabular figures. - Lists and grids cascade in with
.enc-staggerandEncore.stagger. - Every player-authored string goes through
Encore.el(). - Modals use
Encore.trapFocus. - Fullscreen menus mount texture hidden and call
Encore.showTexture(false)on close. - The layout holds at both 1280×720 and 2560×1440.
- The interface leaves the centre-right of the screen clear when a ped or the world must be seen.