Aros HIG/Vol. I/FoundationsFoundations
Volume I

Foundations

The tokens, measurements, and laws from which every component and pattern is derived. Nothing in later volumes may contradict this volume. If a problem isn't answered here, resolve it in the spirit of the principles, document it, and propose it for the next revision — never improvise silently.

1.0 · Principles

Principles

Four laws. Every rule in this book serves one of them. When the book is silent, decide in their favour.

P1 — Precise

Nothing approximate.

Engineered geometry, tokenised values, exact alignment. If a measurement isn't on the scale, it's wrong. Precision earns trust before a word is read.

P2 — Resilient

Calm under load.

Feel like something that stays up when things go down. Solid surfaces, steady rhythm, no visual panic — even at P1 severity.

P3 — Transparent

State, shown plainly.

Legibility over decoration. Surface truth — green when healthy, red when not — never obscured by ornament or ambiguity.

P4 — Alive

Always watching.

The pulse is the soul. Motion and signal show the estate is observed in real time. Used with restraint, it makes the product feel awake.

1.0 · Principles

Rule grammar

Rules are graded. Keywords are absolute, read as in RFC 2119.

MUSTMandatory. No conforming interface omits or contradicts it.
NEVERProhibited. Disallowed under any circumstance.
SHOULDStrong default. Deviate only with a documented reason.
MAYOptional. A legitimate choice either way.

Token names (--p500, --s4) are normative. Reference tokens, never raw values.

1.1 · Foundations

Color

Obsidian dark, one signal green. Green is the pulse — used sparingly so it always carries meaning. Click any swatch to copy.

1.1.1 Core ramps

Obsidian — surfaces

Pulse — the accent

Neutrals — text & light mode

1.1.2 Elevation by tint

Dark UIs express depth with surface tint and a hairline border, not heavy shadow. Each step up the surface ramp is one level of elevation.

obs-950 · base
obs-900 · card
obs-850 · raised
obs-800 · popover
SHOULDPair every raised surface with a 1px --hair border. Tint alone reads flat on low-contrast displays.

1.1.3 Semantic & data

TokenHexMeaningUse
--p500#22C55ESuccess / healthy / liveOperational state, primary action, the pulse
--info#38BDF8InformationalNeutral notices, links, in-progress
--warn#F59E0BCaution / degradedDegraded nodes, soft warnings
--danger#F43F5EError / offline / destructiveFailures, destructive actions
--violet#8B5CF6Data series 4Charts only — never UI chrome

Data-viz series order: --p500, --info, --warn, --violet, then tints. Status colours keep their meaning in charts — green is always "good."

1.1.4 Contrast

Measured, not estimated (WCAG 2.x relative luminance). The v1.0 table quoted approximations; two of them were wrong in the direction that matters, so every pairing below is the number a checker will give you.

PairingRatioVerdict
Snow on Obsidian-95017.0:1AAA · body
Snow-2 on Obsidian-95014.2:1AAA · running text
Mist on Obsidian-9509.8:1AAA · secondary
Slate on Obsidian-950 (v1.0, #6B7585)4.3:1Fails AA at 13px — retired
Slate on Obsidian-950 (v1.1, #7A8598)5.3:1AA · labels only
Pulse-400 on Obsidian-95011.4:1AAA · status ink
Pulse-500 on Obsidian-9508.7:1AAA · fills, large text
Danger on Obsidian-9505.4:1AA · fills only — text uses --danger-ink (10.5:1)
Warn / Info on Obsidian-9509.2:1AAA · inks 11.9:1
Obsidian-900 on Paper17.3:1AAA · body (light)
Paper status inks on their 12% tints over Snow-2 (worst Paper surface)positive 4.76 · warn 4.80 · danger 5.03 · info 5.10 · violet 5.71AA · measured where status text actually sits
On-accent on Pulse-5007.5:1AAA · primary button, both themes
MUSTEvery status colour is a fill + ink pair. The fill (--danger) is for dots, borders and tints; text and icons on a page surface use the ink (--danger-ink). A fill used as text fails contrast on one of the two surfaces — and the console's hig-lint gate rejects it.
NEVERConvey state by colour alone, or set small body text in Pulse-500 on obsidian. Pair every status colour with a glyph, label, or shape.

1.1.5 Paper — the light theme

Obsidian is the brand and the default. Paper exists for daylight, print, and the customer portal's calmer register. It is the same system: every role keeps its name, every status keeps its hue; only the surfaces flip and the inks darken so every pairing still clears AA. Toggle it with the switch in the top bar of this book.

RoleObsidianPaper
--bgobs-950 #080A0Fpaper #F4F6FA
--surface · --surface-2 · --surface-3obs-900 · obs-850 · obs-800white · paper-2 · snow-2
--text · --text-2 · --text-3snow · snow-2 · slateobs-900 · #2B3441 · #4B5563
--border · --border-stronghair · hair-2snow-2 · mist
--accent (primary button)p500 + on-accent inkp500 + on-accent ink — unchanged
--positive-inkp400#166534
--warn-ink · --danger-ink · --info-ink#FBBF24 · #FCA5B4 · #7DD3FC#92400E · #9F1239 · #075985
--surface-code#06080Cobs-900 — code stays on obsidian
--shadow-*black, .40–.50ink, .08–.16
MUSTReference roles, never ramp values, in product code. A component that reads --obs-900 directly is wrong on Paper; one that reads --surface is right on both.
SHOULDKeep code, terminal output and the mark's pulse on obsidian in both themes. They are the instrument; the page is the bench.
1.2 · Foundations

Typography

Three families, three jobs, no overlap.

AROS
Archivo Expanded · Display

Wordmark & ≥24px headings only

Estate
Atkinson Hyperlegible Next · Text

All readable copy & UI

192.168.1.251
IBM Plex Mono · Data

Data, code, labels, metadata

NEVERSet body or UI text in Archivo Expanded, or use it below 24px. It is a display face — illegible and off-brand small.
SHOULDShip Archivo only where display type is actually set. The wordmark is a supplied vector (assets/aros-wordmark.svg, traced once from Archivo at wdth 125 / wght 800) — an app that shows the wordmark and no display headings ships no display font at all.

1.2.1 Type scale

TokenFamilySize / lineWeight · trackingUse
display-lDisplay52 / 56800 · +0.01emPage hero
display-mDisplay40 / 44700 · +0.02emSection hero
h1Sans32 / 40700Page title
h2Sans26 / 32600Subsection
h3Sans22 / 30600Block heading
h4Sans18 / 26600Minor heading
body-lSans20 / 32400Lead paragraph
bodySans17 / 28400Default text
body-sSans16 / 26400Secondary
captionSans15 / 22400Captions, help
labelMono13 / 20500 · +0.16em · UPPEREyebrows, tags — the floor
codeMono15 / 24400Code, data values
axisMono11 / 16500 · tabularChart axis ticks only — never running text
MUST13px is the floor. Nothing a person reads is set smaller — no 10px captions, no 11px metadata, no 9px "just this once". The one sanctioned exception is axis for chart ticks, which are read as marks, not as text. The operator console's hig-lint gate rejects any arbitrary pixel size.

1.2.3 Density: the book vs the instrument

This book is set at comfortable (body 17). The operator console is an instrument and renders at dense: UI text 14, labels 13, controls 36px tall, table cells 12×14. Same families, same floor, same tokens — only the scale moves. The customer portal sits between the two.

RoleComfortable (book, portal prose)Dense (console)
UI text16 / 2614 / 20
Page title (h1)32 / 40 · 70024 / 32 · 600
Card title18 / 26 · 60016 / 22 · 600
Label / eyebrow13 / 20 · mono13 / 20 · mono — never lower
Control height40px36px (sm 32 · lg 44)
Table cell14 × 1612 × 14 (compact 8 × 12)

1.2.2 Numerics & truncation

MUSTUse tabular figures for any column of numbers, metrics, timestamps, or live values so digits don't jitter. IBM Plex Mono is tabular by default.
SHOULDTruncate overflowing identifiers with a trailing ellipsis + tooltip carrying the full value. Never wrap a hostname mid-string.
1.3 · Foundations

Spacing & layout

Everything is a multiple of 4px. One base unit is what makes unrelated screens feel like one product.

1.3.1 The scale

--s14pxHairline gaps, icon-to-text nudge
--s28pxTight inset, chip padding
--s312pxCompact gaps, control padding
--s416pxDefault gap between elements
--s624pxCard inset, related groups
--s832pxSection sub-blocks
--s1248pxMajor block separation
--s1664pxSection padding (vertical)
MUSTEvery margin, padding, and gap is a value from this scale. 13px, 15px, 30px do not exist.

1.3.2 Inset · stack · inline

Inset

Padding inside a container. Cards --s6. Compact controls --s3×--s4.

Stack

Vertical gap. Default --s4; related --s2; unrelated blocks --s8+.

Inline

Horizontal gap. Icon-label --s2; button group --s3; toolbar sections --s6.

1.3.3 Density

Comfortable is the book's default; the console runs dense (§1.2.3), and compact tables step each inset/stack token down one more. Density is a property of the whole surface, chosen once.

SHOULDPick one density per surface and hold it. NEVER mix densities within a single view.
1.4 · Foundations

Grid & breakpoints

A 12-column fluid grid inside a fixed content well, with a persistent navigation rail.

Columns12Subdivide 2 / 3 / 4 / 6
Gutter--s4 → --s6Compact → comfortable
Content max1080pxReading & app well
Page margin--s6 → --s11Scales with viewport
Nav rail288px fixedPersistent ≥ lg; off-canvas below

1.4.1 Breakpoints

TokenMinBehaviour
sm480pxSingle column, stacked controls
md768pxTwo-column grids permitted
lg1024pxNav rail persistent; full grid
xl1280pxComfortable gutters & margins
xxl1536pxWell centred, margins grow
MUSTBelow lg, the nav rail collapses to an off-canvas drawer. The content well never sits under a fixed rail it can't clear.
1.5 · Foundations

Elevation & shape

Depth is layered and ordered; corners follow one radius scale.

1.5.1 Z-layers

z-base0Page content
z-raised10Cards, hovered rows
z-sticky30Sticky headers, toolbars
z-rail40Navigation rail / drawer
z-modal50Dialogs & scrim
z-toast90Toasts, transient alerts

1.5.2 Shadow tokens

none
shadow-sm
shadow-md
shadow-lg
shadow-sm0 1px 2px rgba(0,0,0,.4)Hover lift
shadow-md0 6px 16px rgba(0,0,0,.45)Popovers, menus
shadow-lg0 18px 40px rgba(0,0,0,.5)Modals only
SHOULDPrefer tint + border for elevation; reserve shadows for genuinely floating layers. Heavy shadows on flat cards read cheap.

1.5.3 Corner radius

xs · 4
sm · 6
md · 9
lg · 14
xl · 20
pill
--r-xs (4)Inputs in cards, chips, code blocks
--r-md (9)Buttons, inputs — default
--r-lg (14)Cards, panels, modals
--r-xl (20)Hero surfaces, app-icon tiles
pill / fullStatus pills, avatars, the live dot
NEVERMix radii on one element, or nest a larger radius inside a smaller one. Nested radii step down (9px control in a 14px card).
1.6 · Foundations

Iconography

Icons are a system, not a pile of drawings. They share one grid, one stroke, and a strict convention for when green appears — so the set reads as a single hand and every glyph is distinguishable from its neighbours.

1.6.1 The convention

MUSTChrome icons (search, settings, close, chevrons — generic UI) are monochrome in --ink. Domain icons (node, deploy, audit — things with meaning) carry exactly one green --accent element, placed on the part that is the live / active / verified thing.
NEVERPut two green elements in one icon, colour a chrome icon green, or mix filled and outline icons on the same surface.

1.6.2 Construction grid & keylines

Every icon is drawn on a 24px grid with 1px padding (drawable area 22px). Forms snap to the keyline shapes so square, round and tall icons feel optically equal in weight.

Square 20×20 — blocky forms (host, container).

Circle 22Ø — round forms run slightly larger to match weight (node, observe).

Rectangle wide forms (terminal, database).

1.6.3 Stroke spec

Stroke weight2px @ 24px (scales proportionally)
Caps & joinsRound
Interior corners2px radius
Structure / signal--ink · one --p500 element
FillNone — outline (default variant)
MUSTKeep stroke weight optically constant across the set. Never hand-thin a single icon to fit.

1.6.4 Chrome set · monochrome

1.6.5 Domain set · two-tone

1.6.6 Deliberate metaphors

Where there is a convention, we use it. Where the obvious glyph would collide with a neighbour or mislead, we modify it or design a new one. The reasoning is part of the system.

1.6.7 Variants

Each glyph ships as outline (default) and, where it appears in navigation, solid (filled silhouette) for the active/selected state. The green signal element is retained in both.

outline
solid / active
outline
solid
outline
solid
outline
solid

Optical sizing

16 · inline / table
20 · buttons / UI
24 · toolbars
32 · empty states

1.6.8 Rules

✓ Do
Constant weight, one green signal, geometric.
✗ Don't
No shadow, off-palette colour, or rotation.
SHOULDChoose the most literal metaphor available. An icon's job is recognition, not cleverness — but never reuse one neighbour's silhouette for a different concept.

1.6.9 In the product: Lucide

The bespoke set above is the brand's domain vocabulary and the reference for any new glyph. The operator console and the customer portal draw their UI icons from Lucide — the same 24px grid, the same 2px optical stroke, the same outline-only discipline — because a product with four hundred icon sites needs a set that already exists. The reconciliation is a rule, not a compromise:

MUSTLucide icons are monochrome currentColor and never tinted by hand. The one green element arrives by state: the active rail item's icon turns --positive-ink, a live status carries the pulsing dot beside its glyph. Green is earned by liveness or selection, never painted on.
SHOULDSize Lucide at 16 in rows and buttons, 20 in card titles, 24 in empty states — and keep the mark (ArosMark) as the only two-tone glyph on any product surface.
1.7 · Foundations

Motion

Motion is purposeful, quick, and eased — it communicates state and causality, never decoration. The heartbeat is the one signature animation and the only thing permitted to loop indefinitely.

1.7.1 Principles

Purposeful

Every animation answers "what changed and why." If it explains nothing, remove it.

Quick

UI transitions land in 120–240ms. Slow motion feels broken, not premium.

Eased

Decelerate into rest. Linear is reserved for the continuous pulse loop only.

Restrained

One focal motion per view. Many small animations compete and cheapen.

1.7.2 Duration tokens

TokenValueUse
d-instant0msDirect manipulation (drag)
d-fast120msTaps, toggles, hover colour
d-quick180msButtons, small reveals
d-base240msPanels, cards, menus
d-slow360msModals, page transitions
d-pulse2800ms loopThe mark's pulse trace
d-ping1800ms loopThe live dot's ring
d-shimmer1400ms loop · linearSkeleton sweep — the only other loop, and it must resolve

1.7.3 Easing tokens

1.7.4 Per-component motion

ElementTriggerPropertyDuration · easing
ButtonHover / pressbg, transform 1pxfast · standard
Card / rowHoverborder, translateY 2pxquick · standard
Menu / popoverOpenopacity + 4px risebase · decelerate
ModalOpenopacity + scale .98→1slow · emphasized
ToastEnter / exitopacity + 8px slidebase · standard
TooltipOpenopacity + scale .95→1fast · standard · 300ms delay, none on focus
Live dotAlways (healthy only)ring scale 1→2.6, opacity .7→0ping · standard
PageRoute changeopacity + 14px rise (stagger)base · decelerate

1.7.5 Choreography

When several elements enter together, stagger them 40–60ms in reading order. One orchestrated entrance beats scattered independent animations.

1.7.6 The signature pulse

The heartbeat traces the pulse path over 2800ms on a continuous loop with --e-standard. It is visual proof that the estate is being watched.

MUSTReserve the looping pulse for liveness: the live dot, the active logo, real-time indicators.
NEVERLoop the pulse (or any animation) on a static or decorative element. Indefinite motion that means nothing is noise.

1.7.7 Accessibility

MUSTHonour prefers-reduced-motion. When set, disable loops and large transforms; keep opacity only. The pulse becomes a static green dot, the skeleton a static block — state stays legible without motion. Remove the animation, never shorten it: a 1ms slide is still a slide.
End of Volume I

Next: Components

Foundations are locked. Volume II — Components → builds every element on these tokens and laws.

Copied