vMenu Docs
Plugins

Themes

Reading and changing vMenu's theme from a plugin, and adding themes of your own.

vMenu can draw its menus in a few different looks, called themes. The server owner picks one for everybody with a convar, and a plugin can put one player in a different one:

plugin.Themes.Changed += () =>
{
    foreach (var theme in plugin.Themes.Available)
    {
        // theme.Id is what you send back, theme.Name is what a player reads,
        // theme.IsCurrent says whether it is the one on screen.
    }
};

plugin.Themes.Set("dark");   // this player sees the dark theme from now on
plugin.Themes.Reset();       // back to the theme the server picked

vMenu sends the list right after your plugin registers and again on every change, whoever caused it, so build your rows from the Changed event rather than straight after connecting. Available is empty until that first message arrives, and stays empty against a vMenu too old to know about themes.

A theme set this way beats the server's convar, and it belongs to that one player. vMenu saves nothing, so reconnecting puts the server's choice back, and Reset does the same without the wait. Remembering a choice is your plugin's job: keep the id in your own resource's key value store and set it again on the first list vMenu sends you, which is what the Theme Picker plugin does. No permission and no setting gates any of this, because it changes nothing except what that player sees.

Adding themes of your own

A resource can also hand vMenu new themes, so a server does not have to edit vMenu's own files to get a look of its own. This one needs no plugin API and no plugin registration at all, it is a single event that any resource can send:

emit(
    "vMenu.Enhanced:Plugins:RegisterThemes",
    JSON.stringify({
        themes: [
            {
                id: "reddead",
                name: "Red Dead",
                css: "themes/red-dead.css",
                banner: "default",
            },
        ],
    }),
);
  • id is what a convar or a plugin names the theme by. Letters, digits, dashes, underscores and dots, and never one of vMenu's own names.
  • name is what a player reads, falling back to the id.
  • css is a path inside your own resource, or a full https://cfx-nui-<resource>/ url naming another one. vMenu's menu page loads it from there, so nothing is copied into vMenu. Addresses out on the internet are refused.
  • banner is the picture on top of the menu. One of vMenu's own, default, gold, green, red, dark, cartoon, or none for the plain Grand Theft Auto one, or a .png, .jpg or .webp of your own, given the same way as the stylesheet. An image needs a MenuAPI new enough to load banners out of another resource, and falls back to vMenu's default banner otherwise.

The stylesheet styles the button hints in the bottom right of the screen as well. Those go in a #menuapi-buttons block next to the #menuapi one, and every setting is listed in the instructional buttons section of the MenuAPI docs. A theme without that block keeps the hints in the look of the game itself.

vMenu's own screens

Everything else vMenu draws follows the theme too: notifications, staff alerts and announcements, the bar under a menu that asks you to hold back, text prompts, the copy and paste window for sharing saves, the speedometer, the coordinates, the weather forecast and the box above the minimap. These are styled with variables in a :root block, next to the #menuapi one:

:root {
    --vmenu-font: "Barlow Semi Condensed", Arial, sans-serif;
    --vmenu-accent: rgb(196 32 28);
    --vmenu-panel-bg: rgb(10 8 8 / 92%);
    --vmenu-hud-bg: rgb(10 8 8 / 80%);
}

A variable you leave out keeps vMenu's own look, so a theme can set as few or as many as it likes. The palette is shared by every screen:

VariableWhat it colours
--vmenu-font, --vmenu-mono-fontAll text, and the text that shows codes and model names.
--vmenu-text, --vmenu-text-mutedNormal text, and the quieter text under it.
--vmenu-accentHeadings, the selected suggestion, the focused text box and the main button.
--vmenu-accent-hover, --vmenu-accent-text, --vmenu-accent-text-mutedThe main button under the mouse, and text drawn on top of the accent.
--vmenu-accent-soft, --vmenu-accent-edgeThe tinted strip behind a heading, and the line under it.
--vmenu-info, --vmenu-success, --vmenu-warning, --vmenu-errorThe four kinds of notification. Each also has a -soft and an -edge version for its strip.
--vmenu-panel-bg, --vmenu-panel-edge, --vmenu-panel-radius, --vmenu-panel-shadowThe background, border, corners and shadow of every panel.
--vmenu-row-alt, --vmenu-divider, --vmenu-trackEvery other row in a list, the lines between parts of a panel, and the empty part of a progress bar.
--vmenu-scrollbar, --vmenu-scrollbar-hoverScroll bars.
--vmenu-dialog-radius, --vmenu-dialog-shadow, --vmenu-dialog-scrimThe windows you type in, and the dark layer over the game behind them.
--vmenu-field-bg, --vmenu-field-edge, --vmenu-button-bg, --vmenu-button-hoverText boxes and buttons.
--vmenu-hud-bg, --vmenu-hud-edge, --vmenu-hud-radius, --vmenu-hud-shadowThe see through boxes that stay on screen while you play: the speedometer, the coordinates and the box above the minimap.
--vmenu-hud-text, --vmenu-hud-text-shadow, --vmenu-hud-muted, --vmenu-hud-accentThe text in those boxes.
--vmenu-hud-track, --vmenu-hud-chipThe empty part of the speedometer bars, and the compass letter.
--vmenu-good, --vmenu-fair, --vmenu-poor, --vmenu-badThe vehicle health bars, from healthy to almost broken.
--vmenu-idleA loop in the tick overlay that is not running.
--vmenu-code-r up to --vmenu-code-wThe colours a notification gets from codes like ~r~ and ~b~. One per letter: r, g, b, y, o, p, c, m, u and w.

To restyle one screen and leave the rest alone, set its own variables instead. These start out as the palette values above:

ScreenVariables
Notifications--vmenu-notification-bg, -edge, -radius, -shadow
Staff alerts and announcements--vmenu-alert-bg, -radius, -shadow, --vmenu-alert-tone and --vmenu-announcement-tone, each tone with -soft and -edge
The bar under a menu that asks you to hold back--vmenu-menu-lock-bg, -edge, -shadow, --vmenu-menu-lock-tone and --vmenu-menu-lock-confirm-tone, each tone with -soft and -edge. Its corners always match the menu.
Text prompts--vmenu-input-bg, -edge, -radius, -shadow, -scrim
Sharing saves--vmenu-transfer-bg, -edge, -radius, -shadow, -scrim
Tick overlay--vmenu-ticks-bg, -edge, -radius, -shadow
Box above the minimap--vmenu-location-bg, -edge, -radius, -accent
Coordinates--vmenu-coordinates-bg, -edge, -radius, -accent
Speedometer--vmenu-speedometer-bg, -edge, -radius, -accent, -value, -rail-end
Weather forecast--vmenu-forecast-bg, -soft, -edge, -radius, -shadow, -accent, -muted, and the moon with --vmenu-moon-lit, --vmenu-moon-dark and --vmenu-moon-edge

The bottom of the Red Dead theme in the Custom Themes plugin sets the whole palette, which makes it a good starting point.

Every file the stylesheet uses has to be listed in your fxmanifest.lua under files, or the game has no copy of it to serve. Fonts and images inside the stylesheet are resolved relative to the stylesheet itself.

Send the whole set in one go: a second registration replaces everything that resource registered before, and its themes are dropped the moment it stops. vMenu answers on vMenu.Enhanced:Plugins:<your resource>:ThemesRegistered with accepted, errors and warnings, which is where a refused theme tells you why. Send it once when your resource starts, and again whenever vMenu says vMenu.Enhanced:Plugins:Ready, which is what it says when it restarts.

Once registered, a theme is a theme like any other. It shows up for plugins reading Themes.Available, and the vMenu.Enhanced.MenuAppearance.Skin convar accepts its id. If you would rather not write any of this yourself, the Custom Themes plugin is exactly this, driven by a JSON file you edit.

On this page