EndCore Framework

Design system

The Field Equipment art direction behind every EndCore interface, and the rules your NUI pages follow so twenty resources read as one product.

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:

html
<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>
lua
-- 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.

Note

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.css contains layout only. No colours, no typefaces, no easing curves, no durations. If a value you need is missing, add it to encore.css instead 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.

NeverPure grey (it reads as "dark mode website"), rounded corners, drop shadows on panels, bouncy easing, more than one accent colour
AlwaysWarm 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.

FaceUsed forNotes
BahnschriftDisplay type and UI chromeA variable-width industrial DIN. Condense large type with font-variation-settings: 'wdth' 85 to 92
ConsolasEvery numberAlways with font-variant-numeric: tabular-nums, so digits don't shift width as values tick

Letter-spacing does most of the typographic work:

TextTracking
Stencil labels0.22em
Display type0.14em
UI text0.06em
Body text0.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:

StatTokenColour
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 menuPersistent overlay (HUD, notifications)
TextureEncore.mountTexture()None. Grain over hours of play is tiring
Surfaces--enc-panel--enc-glass, with the --enc-legible text shadow on bare text
Sizingrem off the default rootPut class="enc-overlay" on html. The root font size tracks screen height (16px at 1080p), so everything in rem scales as one object
PresenceAlways shown while openContextual: show a readout when it needs attention, let it recede when it doesn't

Motion

Everything decelerates and nothing overshoots.

UseEasingDuration
Hover--enc-ease150ms (--enc-d-fast)
State changes and transitions--enc-ease240ms (--enc-d-base)
Entrances and reveals--enc-ease-hard650ms (--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:

LayerDefaultWhat it does
GrainonDrifting SVG film grain. The biggest single contributor to a filmic rather than flat look
ScanlinesonVery faint horizontal lines, like a CRT readout
VignetteonDarkens the edges and pulls focus inward
HazeoffSlow 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:

  1. Mount the texture hidden: Encore.mountTexture({ visible: false }).
  2. Call Encore.showTexture(true) when your menu opens.
  3. Call Encore.showTexture(false) when it closes.
js
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.

Danger

Always build player-authored text with Encore.el(), which sets textContent. Never assign server data to innerHTML, and never build HTML strings from it.

js
// 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.css defines no colours, fonts, easing or durations.
  • Numbers use .enc-num, or --enc-font-mono with tabular figures.
  • Lists and grids cascade in with .enc-stagger and Encore.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.