Skip to content

Theme ​

Server owners restyle every Twilight Moon interface in game, without a rebuild. The theme is a handful of settings in the tm_lib namespace. The pages turn them into CSS variables, so all UI code uses tokens and never hard coded colors.

Changing it ​

Open the admin panel (/tmadmin) and choose Twilight Moon, then the Theme category. Changes apply to every open page at once. See Settings and the admin panel for who may edit settings.

SettingTypeDefaultEffect
theme.accentcolor#9f8cffReplaces the kit's accent (blue) scale.
theme.surfacecolor#0a0a0df0Main panel background. Keep some transparency in the alpha part.
theme.surfaceRaisedcolor#16161cfaCards, toasts and popovers drawn above a panel.
theme.radiusnumber6Corner radius in pixels, 0 to 24.
theme.scalenumber1Zooms the whole interface, 0.8 to 1.4.
theme.fontselectdefaultdefault (Segoe UI), open-sans, system, serif or mono.

The defaults match the Twilight Moon palette in branding/.

Which pages follow it ​

  • tm_lib's interface (this section): automatically. The page announces itself when it loads and tm_lib answers with the stored theme and the language strings.
  • tm.ui windows (Windows): automatically.
  • A resource with its own NUI page (built with the shared kit): call tm.theme.bind() once and tm.theme.push() when the page reports it is ready.
lua
RegisterNUICallback('myPageReady', function(_, cb)
    cb(1)
    tm.theme.bind()                  -- push again whenever an owner changes a theme setting
    CreateThread(tm.theme.push)      -- send the current theme now
end)

A theme for one resource ​

A resource can have its own look. Add the theme settings to the ones it registers, on the server:

lua
local defs = { --[[ the resource's own settings ]] }
for _, def in ipairs(tm.theme.settings({ accent = '#e11d48' })) do defs[#defs + 1] = def end
tm.settings.register('tm_garages', defs, { label = 'garage.title', icon = 'lucide:car' })

The resource's panel in the admin hub then has a Theme category with a Use its own theme switch and the six tokens (accent, surfaces, radius, scale, font). While the switch is on, the resource's own pages (its NUI page and its tm.ui windows) use those values instead of the shared theme above. Turn it off and they follow the shared theme again. Nothing else changes, and a resource that does not call tm.theme.settings is not affected.

  • tm.theme.settings(overrides?) returns the definitions. overrides may hold enabled = true (start with the resource's own theme on) and any of accent, surface, surfaceRaised, radius, scale, font to start from something other than the Twilight Moon theme. A value that is not valid for its token is ignored and logged. The tokens start as the shared theme's defaults, read from tm_lib's own data/defaults.lua.
  • While the switch is on, all six tokens come from the resource. A token the resource has not received yet falls back to the shared theme.
  • Pages that tm_lib draws for a resource (toasts, menus, dialogs, progress, the radial menu) always use the shared theme. They belong to tm_lib's page.
  • The resource's page applies it through the same call as before: tm.theme.bind() and tm.theme.push(). tm.ui windows already do this.

Functions ​

FunctionNotes
tm.theme.tokens()The current theme as a plain table: accent, surface, surfaceRaised, radius, scale, font. Only tokens the server has already sent are present. For a resource with its own theme turned on these are its own values.
tm.theme.push()Sends the theme to this resource's page. Waits up to 5 seconds for the first sync from the server, so call it from a thread.
tm.theme.bind()Pushes again whenever a theme setting changes, the shared ones or the resource's own. Several changes in a row go out as one push. Safe to call twice.
tm.theme.isOverridden()Client. True while the resource has its own theme and an owner turned it on.
tm.theme.settings(overrides?)Server. The setting definitions that give a resource its own theme (above).

Writing UI that follows the theme ​

In a Vue page built on the kit, use the theme variables and kit components, not literal colors:

vue
<div class="bg-(--color-surface-raised) rounded-lg text-blue-400">...</div>

The accent setting replaces the blue color scale, rounded-* follows the radius setting, --color-surface and --color-surface-raised are the panel backgrounds and --font-sans is the font.

CEF renders some effects as black halos in game, so the theme sticks to opaque ish backgrounds with a ring, no backdrop-filter: blur, and no large shadows. Keep to that in your own pages.