DirkScripts
UI
âšī¸ Client Module - All UI functions can only be used on the client
dirk_lib ships a full suite of UI components. The active provider for each component is selected from the Bridging section of /dirk_config. Every UI provider defaults to ox_lib â install ox_lib and you get its native UIs out of the box. Switch any provider to dirk_lib to use the built-in NUI instead.
| Page | Components |
|---|---|
| Notify | Notifications and compatibility wrapper |
| Text UI | Persistent interaction text |
| Context | Context menus |
| Dialog | Dialog trees and camera-focused conversations |
| Progress | Progress bars, circles, active/cancel state |
| Input | Input dialogs |
| Status | Status indicators |
| Quiz | Quiz registration and runtime |
| Key Prompts | Contextual key overlays |
| Utilities | 3D text, help notifications, clipboard, links, theme |
#Theming â read this before adding a UI component
Every shared UI is drawn on dirk_lib's NUI page, inside dirk_lib's provider. So without doing anything, a dialogue opened by a script with its own theme comes up in dirk_lib's colours.
Each element therefore wears the colours of the resource that opened it. Nothing is asked of the calling script: it never passes a theme, and never will â it would be boilerplate at every call site and forgotten at some of them.
#How it reaches the page
- A resource declares a
themeblock in itsschema.jsonand turnsuseOverrideon (see Utilities). modules/scriptConfig/client.luaruns inside that resource's VM, so it is the only code that ever sees its config. On every application of that config it callsexports.dirk_lib:registerTheme(resource, theme). That covers the first hydration and every later edit through the panel â no startup special case, no polling, and nothing on the hot path.- dirk_lib holds a
resource -> thememap (src/ui/client/theme.lua). - Each UI entry point resolves it and ships it with its payload.
- The React side wraps the component in
<Themed>, which scopes a nestedMantineProviderto that element alone.
A resource with no theme block never registers, has no entry, and falls back
to dirk_lib's global appearance â which is what everything did before.
#Adding a new UI component
Lua â at the entry point:
lualib.myThing = function(data) -- FIRST, before anything that can yield. `GetInvokingResource` answers only -- for the frame that crossed the export boundary; a Wait ends that frame and -- it comes back nil, so the element silently wears dirk_lib's colours. local theme = lib.uiTheme(GetInvokingResource() or GetCurrentResourceName()) SendNuiMessage(json.encode({ action = 'MY_THING', data = { ..., theme = theme }, })) end
React â report it, do not apply it:
tsxuseNuiEvent<MyThingProps>('MY_THING', (data) => { setUiTheme('mything', data?.theme); setState(data); });
App.tsx â wrap the component, not its return:
tsx<Themed theme={useUiTheme('mything')}><MyThing /></Themed>
#Three traps, all silent
- Wrapping the component's own return does not work. A component resolves
its hooks in its BODY, which is above the JSX it returns, so anything derived
from
useMantineTheme()up there is still dirk_lib's. The markup looks right and the colours are wrong. Wrap fromAppinstead. GetInvokingResource()must be read before the first yield, for the reason in the snippet above.progressBar,quizandinputDialogall wait before they send, and all read it first.- The scope element needs
data-mantine-color-scheme. Mantine puts the raw palette in.scope { }but everything a component actually reads in.scope[data-mantine-color-scheme="dark"] { }. Without the attribute the palette scopes and the components do not.<Themed>already sets it; anything that scopes a theme by hand has to as well.
#What is deliberately NOT themed
- Notifications â server furniture. They should look the same whichever script raised them.
- Config Studio, the admin panel â dirk_lib's own UI.
- Gizmo, TestBed â developer tooling.
Last updated on 30 September 2026
