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 |
14px–18px |
Between a header and its content block |
$aj-pad-card |
10px 12px–14px |
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.