Skip to content

UI Design System

The shared visual language every panel in Code/ draws from — colors, spacing, type, and a small set of reusable component patterns. Exists so the rewrite doesn't accumulate one hand-styled look per panel author: AdminPanel.razor and a future PlayerMenu should read as the same product on sight. See 11_UI_ARCHITECTURE.md for panel mechanics (BuildHash, registration, on-demand vs. standing) — this document is what those panels look like, not how they're wired.


1. Scope and adoption

This is not a big-bang reskin. Applies going forward to every new panel (PlayerMenu and its tabs, §5), and to AdminPanel/DevMenuPanel/the HUD elements the next time each is touched for an unrelated reason — not a standalone milestone item to reskin all six existing panels at once. A panel not yet migrated is not a bug; a new panel that ignores these tokens is.

2. Palette

Dark, single-theme — this is a game HUD/menu, not a light/dark-switchable desktop app, so there is exactly one palette, not two. Defined once as SCSS variables and shared across every panel's stylesheet (§6 flags how that sharing mechanism itself is unverified).

Token Value Use
$aj-bg-rail #131416 Nav rail, deepest layer
$aj-bg-panel #191b1e Menu/panel shell
$aj-bg-card #212327 Cards, rows, item tiles
$aj-border rgba(255,255,255,0.06) Hairline separators, shell outline
$aj-text-primary #f2f2f0 Titles, primary labels
$aj-text-secondary #8a8d90 Supporting text
$aj-text-muted #7a7d81 Section labels, hints, inactive icons
$aj-accent #2f8fe0 Primary buttons, active nav icon, focus states
$aj-accent-hover #46a6f5 Hover/pressed state of the above
$aj-accent-tint rgba(47,143,224,0.16) Selected nav icon background, selected row border tint
$aj-on-accent #ffffff Text/icon on a solid $aj-accent fill

Blue, not orange — deliberate choice over this document's own first draft, to match the legacy cider_Menu's own button color (legacy/Applejack/gamemode/derma/cl_character.lua's "Become" buttons) rather than introduce a new brand color unrelated to what players who remember the original already associate with an affirmative action.

One accent, used sparingly. $aj-accent marks exactly one thing per screen: the active nav icon, or a primary call-to-action ("Become", "Save"). A row that is merely joined (not actionable) uses a neutral badge ($aj-text-secondary on a faint rgba(122,125,129,0.18) fill), never the accent — see the mockup's "Joined" vs. "Become" distinction. Two accented elements competing on one screen is the single most common way this look degrades into noise.

3. Typography

One sans-serif family throughout (S&box's default UI font — no custom font asset is proposed here). Sizes, in the roles actually used across the mockup:

Role Size Weight Example
Panel title / character name 16px 500 "Rosa Ibarra"
Card title / row label 13px 400–500 "Ashford Police Department"
Body / supporting line 12px 400 "Patrol Officer · Ashford PD"
Section label 11px 400, uppercase, letter-spacing: 0.04em "FACTIONS"
Micro / badge text 10–11px 400–500 "x3", "Become"

No size below 10px. Two weights only (400, 500) — never bold beyond 500, matching the same restraint this codebase already applies everywhere else (no unnecessary complexity, per 00_CONSTITUTION.md).

4. Spacing and radius

Token Value Use
$aj-radius-shell 14px Outer menu/panel container
$aj-radius-card 10px Cards, rows, item tiles, nav icons
$aj-radius-pill 7px Badges, small buttons
$aj-gap-sm 8px Between stacked rows
$aj-gap-md 14px18px Between a header and its content block
$aj-pad-card 10px 12px14px Interior card padding

5. Component patterns

Nav rail (left, 64px fixed width, $aj-bg-rail). One 40×40px icon button per top-level tab, $aj-radius-card corners. Active tab: $aj-accent-tint background, $aj-accent-tinted icon (#57aef2 — the accent lightened for legibility at icon size, not the raw accent hex). Inactive: no background, $aj-text-muted icon. A close (×) icon anchored to the rail's own bottom, not the content pane — consistent across every tab instead of each tab drawing its own close control the way legacy's per-frame DFrame close button did.

Card / row ($aj-bg-card, $aj-radius-card, $aj-pad-card). The one repeated container for a faction entry, an inventory tile, a config line — anything that is "one item in a list." A card that represents the currently selected state (e.g. the faction a character has already joined) gets a 1px solid $aj-accent-tint-strength border; a card with no special state gets none — border presence itself carries meaning, so it is not decorative.

Primary button ($aj-accent fill, $aj-on-accent text, $aj-radius-pill, 11px/500, horizontal padding ~12px). Reserved for the one actionable verb per row ("Become", "Save", "Grant"). Never more than one solid-fill accent button visible per card.

Secondary / status badge (rgba(122,125,129,0.18) fill, $aj-text-secondary text, same pill radius). Non-actionable state labels: "Joined", permission tags, counts.

Icon — see §6; every icon is single-color, sized 18–20px inline, never decorative-only without an accessible label if it is the sole content of a control (nav rail icons need a tooltip or equivalent, since S&box Razor panels have no aria-label guarantee this document can confirm — named as an open question, not assumed solved).

6. Open dependency: the icon set

The mockups this document is drawn from used Tabler's outline icon font, available in the mockup tool's own sandbox. S&box does not ship Tabler, or any icon font, by default — no icon or font asset exists anywhere under Assets/ today (confirmed by searching the tree). Before PlayerMenu's nav rail can be built as designed, one of the following needs deciding, not assumed:

  • Import a webfont/SVG icon set as a real Assets/ asset S&box's Razor panels can reference — license and file-size cost unverified.
  • Use S&box's own built-in editor icon set if one is exposed to runtime game UI, not just the editor chrome — unconfirmed against engine source, same confidence level as every other "ASSUMED, NOT VERIFIED THIS PASS" engine claim in this codebase (ROADMAP.md § Verification debt).
  • Hand-author a small sprite sheet for the ~6–8 icons the nav rail actually needs (user, backpack, scale/gavel, info, close) — the smallest-scope option, and the one that needs the least unverified engine behavior.

This document does not pick one — that's an implementation-time decision for whoever builds PlayerMenu's nav rail first, recorded here so it isn't silently guessed at in code.

7. Shared stylesheet mechanism (unverified)

The intent is one Code/Ui/_tokens.scss (or equivalent) holding §2–§4's variables, imported by every panel's own .razor.scss, the same way IHudRegistry centralizes registration instead of letting each module reinvent it. Whether S&box's Razor panel styling actually supports a shared partial import this way — versus requiring each panel to inline its own copy of the same values — is unconfirmed against the real compiler, the same caveat 11_UI_ARCHITECTURE.md already attaches to every other Razor-panel claim in this codebase. If imports aren't supported, the fallback is duplicating the token block per panel file with a comment pointing back here as the source of truth — worse, but not a blocker.

8. Reference mockups

Two mockups produced during design discussion (not committed as assets — described here for anyone picking this up later): a PlayerMenu Character tab (identity summary, details/gender cards, a faction list with "Become"/"Joined" states) and the same shell's Inventory tab (a 5-column icon grid replacing the legacy text-row list, cider_Inventory_Item in legacy/Applejack/gamemode/derma/cl_inventory.lua). Both use the nav-rail-plus-content-pane layout §5 describes.