Sagix design system brand layer · v1.5 · August 2026
The Sagix layer every Sagix product inherits
Everything below is brand and platform level: the identity, the colour and type systems, the foundations, the icon rules, the voice and the product-UI conventions that hold across any Sagix interface. Nothing here belongs to one application.
In scope — reuse verbatim
Logo and lockups · colour ramps and semantic families · type scale · spacing, radii, motion, grid · theme and density axes · icon system · voice · chip, control, table, overlay and accessibility conventions · adoption rules.
Out of scope — per application
Screen layouts and page compositions · domain vocabularies and state registers · screen inventories and navigation trees · app chrome and data adapters · anything whose meaning is owned by one product's engine. Those live in the product that owns them.
01
Brand — what the system is for, and the four principles that settle arguments
Adoption — the carry-over kit, the API contract, versioning
Section 01
Brand
Sagix builds instruments for people who are accountable for a record — auditors, controllers, risk owners, regulators. The brand's job is to look like something you could defend in a committee room: exact, quiet, and unmistakably the same product from screen to certificate to PDF.
Principle 01
An instrument, not a brochure
Someone scans 248 rows before their second coffee. Density is the feature: compact is the default for anything tabular, and decoration that costs a row is decoration we cut. Comfortable exists for tablets and for people who ask — never as the design target.
Principle 02
Scarcity is what makes saffron mean something
Saffron marks exactly four things: the one primary action in view, the active location, the focus ring, and a citation. A screen with six saffron elements has none.
Doprimary button · active nav · focus ring · citation · one KPI
Don'tbody text on light · every chip · zebra rows · icons · hover fills · warnings
Principle 03
The record is the product
Every state change is attributable, timestamped, and reversible only by a new event. The UI never implies deletion; it shows supersession. Machine-assisted actions carry a human actor. When the system does not know, it says so rather than rounding to a confident number.
Principle 04
Elegance is legibility at density
Air belongs where people orient — page headers, KPI bands, empty states — and nowhere else. Structure is carried by whitespace and hairlines first; cards only when containment is real. The test: cover the data, and the page should still explain itself.
What the brand never does. No gradients as decoration. No illustration in an empty state. No emoji. No celebration copy. No third brand colour. No second typeface family. No rounded-friendly softening of a consequence — a destructive act is stated in the words of the act.
Section 02
Identity
sagixsagix
sagixsagix
The mark is five bars — 9-unit bar height on a 100-unit grid, alternating full and half widths, the third bar saffron. It reads as a ledger, a rule set, a stack of statements. The geometry is locked: it is artwork, not a layout to be re-drawn.
Typeface
Public Sans SemiBold (600), lowercase, +1% tracking (0.01em). No bolding, stretching or redrawn glyphs.
Colour
Ink #161616 · saffron #E89B22. Two colours only. Reversed lockups use #FFFFFF for the ink parts; saffron holds on both polarities (7.9:1 on ink).
Tittle rule
The dot on the i is the native glyph, recoloured — identical shape, size, height, x-position. Saffron at ≥ 24px; ink below 24px and in every single-colour, embossed, engraved or one-colour print use.
Proportion
Mark height = wordmark baseline → tittle top, optically set to 98% of that measure; mark sits on the wordmark baseline and the g descender hangs below it. Gap mark→wordmark = one bar height = 0.09 × mark height.
Clear space
One bar height on all four sides of the lockup bounding box. Nothing enters it — no rules, no type, no image edges.
Minimum sizes
Lockup 96px digital / 24mm print · wordmark alone 12px · mark alone 16px. Below 16px, set the mark on a solid tile rather than bare.
Variants
Full colour · reversed · one-colour ink · one-colour reversed. The one-colour variants carry no accent at any size.
Don't
No gradients, outlines, shadows or third colours. Don't recolour individual bars, rotate the mark, set the wordmark in another face, or use the saffron tittle under 24px.
Files
sagix-lockup.svg and sagix-lockup-reversed.svg — mark as rects, wordmark as live text, tittle isolated as node #tittle for recolouring. Convert text to outlines before handing files to a vendor without the font. Never hand-author a wordmark.
Naming a Sagix product. The lockup never changes for a product; the product name sits beside it, separated by a mid-dot or a hairline, set in Public Sans Medium at the wordmark's cap height, in ink or #4A4D55 — never in saffron, never in a second face, never as a second mark. A product does not get its own accent colour, its own icon or its own logotype: it is a posture on one identity, not a sibling brand.
Section 03
Colour
Two brand colours, one cool neutral ramp, one warm ecru register for reading surfaces, four semantic families, and a heat sequence. Both themes are authored independently — dark is never derived from light by inversion, and every figure below was measured, not assumed.
Saffron · one primary action, active location, focus ring, citation. Never text on light.
3.1 Neutral ramp · light · thirteen steps
n-0 #FFFFFF
n-25 #FCFCFD
n-50 #F8F8FA
n-100 #F1F2F4
n-150 #E9EAEE
n-200 #DFE1E6
n-300 #C9CCD4
n-400 #A8ACB6
n-500 #878B96
n-600 #62666F
n-700 #4A4D55
n-800 #33353B
n-950 #161616
3.2 Warm wells · the ecru register — reading surfaces, never a page canvas
w-25 #FDFBF7
w-50 #FBF7EF
w-100 #F5EFE1
w-200 #EFE7D6
Ecru is the vocabulary for something authoritative rather than active: a quotation, a document well, a read-only microsite, a settled baseline. Wells, never canvases.
3.3 Surface and text roles · light
bg-canvas
n-50 · the page
bg-surface
n-0 · cards, rows, panels
bg-sunken
n-100 · inset areas, table heads
bg-well
w-50 · reading wells
bg-hover
n-50
bg-selected
#FDF3E4
hairline
n-150 · structure inside a card
border
n-200 · a real edge
border-strong
n-300 · controls, dividers that must be seen
text-primary
n-950 · 16.9:1
text-secondary
n-700 · 8.5:1
text-tertiary
n-600 · 5.8:1
text-muted
#6D717C · 4.88:1 on surface
…on sunken
n-600 · muted's pair token, mandatory there
text-inverse
n-25
paper / ink
#FFFFFF / #161616 · both themes, never inverts
scrim
rgba(18,22,31,.38)
focus ring
2px surface + 2px saffron, two-ring
Four text steps, in that order, never skipped. Muted is never body text — it is for a stamp, an attribution, a count. A text step that fails its floor on the commonest surface in the product is not a text step: move the floor, do not add a second token.
3.4 Accent and semantic families · light · bg / border / text / emph
Accent #FDF3E4 #F3D2A0 #8F4E04 · 6.6:1 #E89B22
Success #E9F6EF #B9E2CB #0E6B41 · 6.6:1 #12885A
Warning #FCF6E4 #E7D49A #7A5B02 · 6.4:1 #C99A0B
Danger #FDECEA #F5C4BE #A32B22 · 6.2:1 #C43B30
Info #ECF3FA #BCD7EE #15537F · 7.3:1 #1F6FB2
Warning is amber and deliberately separable from brand saffron — a status must never read as an action. Info carries two extra plates: #DCEAF7 strong (handed over) and #F5F8FC in-flight with a inset 2px 0 0 leading bar (actively being worked). Aging uses one warm five-step heat sequence — #F1F2F4 · #FDF3E4 · #FBE7C8 · #F6D3AB · #EDBE85 · #C43B30 — never a rainbow.
3.5 Elevation · light raises with shadow, dark raises with surface
elev-1 · rows, quiet cards
elev-2 · popovers, menus
elev-3 · panels, modals
Elevation ink is a cool blue-black at low opacity, and never combined with a border — a raised thing has one edge treatment, not two. In dark, all three shadow tokens resolve to none and separation is carried by two surface steps, so the scrim goes heavier there (rgba(0,0,0,.58)).
Adding a colour family. A new vocabulary gets its own trio, never a borrowed one: two families that paint the same pixels are two vocabularies a reader cannot tell apart, and a border alone is the weakest separation this system permits. Separate on plate and ink — a visible step is ≥ 1.20:1 between plates and a readable one is ≥ 4.5:1 for text — and state the measured figure beside the value. Floors: text ≥ 4.5:1, UI borders ≥ 3:1 against their surface, in both themes.
3.6 Dark theme · authored, not derived
canvas #0D0E10
d-50 #121417
d-100 #16181B
d-150 #1D2024
d-200 #24272C
d-300 #2E3238
d-400 #3D424A
d-500 #565C66
d-600 #767C87
d-650 #8B919B
d-700 #A2A8B2
d-800 #CBD0D7
d-900 #EEF0F3
Accent #2A1F0A / #4D3A11 #F2AE45
Success #0F2419 / #1F4433 #6FD39B · 9.0:1
Warning #241E08 / #4A3F12 #E3C55F · 9.1:1
Danger #2A1513 / #55231F #F19B92 · 8.1:1
Info #0F2130 / #1E3D53 #8CC0E6 · 8.6:1
Dark keeps the same brand saffron for fills and lifts the accent text to #F2AE45. Its text ladder is #EEF0F3 · #A2A8B2 · #8B919B · #7E848F — four steps that stay four steps: a ladder that collapses to three in one theme is a light-only ladder with a dark alias. Muted is barred from wells in dark (4.35:1); use tertiary there.
Section 04
Typography
Public Sans
300 · 400 · 500 · 600 · 700
Open-licensed, drawn for public records and screens. Institutional without being cold, and it carries a 12px table cell as well as a 48px display line. Everything that is not a code, an identifier or an eyebrow is set in it.
IBM Plex Mono
400 · 500
The machine register: identifiers, hashes, timestamps, engine lines, refusal codes, section eyebrows and every value a person may need to read back character by character. It is never body copy and never a heading.
TokenSize / lineWtTrackingWhere it is useddisplay-lg48 / 52600−0.028emcovers, marketing, section openersdisplay34 / 40600−0.022empage title on a landing surfaceh124 / 32600−0.012emscreen titleh219 / 26600—card and section headingh316 / 22600—sub-heading, panel bandbody-lead16 / 27400—the one paragraph that orients a screenbody14 / 22400 / 500—prose, form values; 500 for emphasisbody-sm13 / 20400—secondary prose, help text, empty statestable13 / 18400—comfortable density rowstable-dense12 / 16400—compact density rows — the defaultlabel12 / 16500—field labels, chips, column headscaption11 / 15400—stamps, attribution, footnotesoverline115000.12emmono eyebrows, uppercasemono11 / 15400—ids, digests, engine lineskpi52 / 52600−0.03emone figure per band; unit at 24px
Numerals
Tabular figures everywhere a number can be compared or stacked — tables, KPIs, currency, dates, aging, countdowns, identifiers. A number that moves as it updates is a number nobody can scan.
1,240,000 0.0125 −11 days
Dates and money
Render in the viewer's locale; store and export as ISO 8601. Any cross-entity export states its locale in the header. Currency always shows its symbol, and the grouping follows the locale (lakh/crore where that is the locale). Ambiguous month/day order is never used in email or print — those render 18 Jun 2026.
Emphasis is weight 500 and ink, never italics, never colour alone, never uppercase. Two families, and no third: a product that needs a display face has a layout problem, not a type problem.
Section 05
Foundations
5.1 Spacing · 4px base, eleven steps
4
8
12
16
20
24
32
40
48
56
64
5.2 Radii
xs 3 · sm 4 · md 6 · lg 8 · xl 12 · pill 999
Controls and inputs take sm; cards take lg; panels and modals take lg; a chip is a pill or a 3px box, and which of the two it is carries meaning (§8.1).
5.3 Motion
fast 100ms · base 150ms · slow 200ms ease cubic-bezier(.2, 0, .2, 1)
Motion confirms a state change; it never introduces one. All three durations resolve to 0ms under prefers-reduced-motion, and anything streamed renders as a complete block instead. No parallax, no entrance animation on data, no spinner where a skeleton row will do.
5.4 Grid and breakpoints
12 columns · content max 1600px · gutters follow density (16px compact, 24px comfortable) · breakpoints sm 768 · md 1024 · lg 1280 · xl 1600. The 1280px boundary is where a docked side panel stops being an overlay and becomes part of the layout.
5.5 Two axes · theme × density
Both are attribute scopes on the root — [data-theme] and [data-density] — and never component props. A component that takes a dense prop lets two rows of one table disagree.
Compact is not available below 1024px — the media query forces comfortable values there regardless of the attribute. On touch, an extended invisible hit area keeps every target at 44px even when the row is 32px.
One weight, one grid, no filled variants and no duotone. A dot inside a glyph is the one licensed fill. Colour comes from the text colour of the control the icon sits in — an icon is never coloured on its own.
Delivery
One hidden SVG sprite of <symbol id="ic-name">, injected synchronously by sagix-icons.js; consumers reference <use href="#ic-name">. The injection must target document.documentElement when there is no body yet: a sprite that arrives after its references is a sprite that arrives too late, and <use> does not reliably re-resolve later.
6.1 Size ladder and accessibility
12px
disclosure chevrons
14 – 16px
inside buttons, menu items, chips
18px
navigation destinations, in both the wide column and the icon-only rail
aria
Every icon is aria-hidden, without exception. The accessible name lives on the surrounding control — never on the glyph.
distinctness
A new glyph must be tellable apart from its neighbours at 16px, because an icon-only rail is the hardest case. Two destinations that share a glyph make the rail lie about identity.
one meaning
One glyph, one meaning, one owner. If a second concept wants an existing glyph, draw a new one — and record what it had to stay distinct from.
6.2 The set · 46 glyphs
dashboard requests evidence trail entities chart admin shield search filter plus minus
check close chevron-down chevron-right alert info inflight upload download lock eye user
more external flag link bell mail calendar clip refresh grid-rows table door
Roughly two thirds are universal platform glyphs; the remainder are domain marks a product may not need. Take the whole sprite anyway — it is one file, and a partial copy is the start of two sprites. A missing glyph is a change to the system, not a local drawing.
Section 07
Voice
Calm, precise, specific. Present tense. No exclamation marks, no emoji, no oops, no congratulation. State the fact, then the consequence, then the option. Numbers over adjectives. Never blame the reader, and never dress a machine action as a personality.
Write
“Nine items are overdue in the western region. The oldest has been unassigned for 11 days.”
“Returned to the preparer. A reason is required before the item leaves review.”
“12 records exist here; your grant covers one entity only.”
Not
“Uh oh! Looks like some items need attention!”
“Great job — you're almost there!”
“Something went wrong. Please try again later.”
“Thinking really hard about this…”
Empty states
A sentence, not a graphic — no illustration, no icon, no “get started” button. Three facts: what happened (nothing matched), why (which filter is doing the work, and the count it would return), what to do (one escape, one reset). It sits exactly where the rows would have been, and the section is rendered rather than hidden: a section that disappears when empty teaches the reader it does not exist.
Withheld ≠ empty
If the emptiness is a permission boundary, say so instead of implying the data is absent, and always state the count of withheld items. A reassurance where a refusal belongs is the most expensive sentence in the product.
Refusals
A refusal is content, not an error state. Render it where the press was, carrying the code and the system's own sentence verbatim — never re-worded, never softened, never swallowed into a toast that fires while the surface is still loading.
Failures
A load failure is a banner in the refusal treatment with role="alert" and the message verbatim; a runtime failure is a toast, because it has a rendered page to appear on. “Not ready” always says which not-ready it is — connecting, or failed.
Destructive acts
Two stated verbs, never Cancel / OK: a stated keep and a stated act — “Keep it” / “Deactivate”. A confirm never dismisses by doing the thing.
Counts
The count comes from the selection, never from a typed number, and the label carries it: “Approve 4”, “Share 12 documents”. Pluralisation is a formatter's job so the noun agrees at one and at many.
Machine actors
An assistant has a short product name and nothing more. Never anthropomorphise it beyond that name — no thinking, no feeling, no first-person enthusiasm — and its trail entries always name the human who acted through it.
Section 08
Product UI
These are conventions, not layouts. Nothing here prescribes how a screen is composed — no page templates, no mandated column split, no fixed placement of headers, panels or tables. Each product designs its own screens; what must be identical across Sagix is how a state is painted, how a control behaves, how layers stack and how a failure speaks. Where a dimension appears below, it is the default of a shared component (the nav shell's own two widths) or a shared grid ceiling — not a rule about where anything sits on a page.
A state — something a person owes a person. Semantic family, optional icon, and an in-flight state carries a 2px leading inset bar.
Box (3px)
A grade or a fact about the thing — severity, tier, lifecycle. Boxed so it can never be mistaken for a state at a glance.
Tag
A classification — framework, applicability, version. Facts of one class share one plate and are told apart by their word, not by a colour each.
Marker
Ink only, no plate. For a flag that qualifies a row rather than describing it. Derived markers (overdue, escalation tier) layer onto a state — they are never an extra state.
The label
Comes from the register that owns the vocabulary, never from a map held beside it. A map keyed by a register's codes is a second copy of that register, and it breaks when the register grows and when it shrinks.
Unknown key
An unmapped value takes the dashed unrecognised treatment and prints the raw code un-prettified. It never takes a sibling's paint — a value dressed as its neighbour is a defect the paint is hiding.
8.2 Controls
Primary actSecondaryGhostDeactivateNot ready⌘K
Geometry
Height and horizontal padding are density tokens (28/10 compact, 36/14 comfortable); radius sm; label at 12.5–13px weight 500; icon 14–16px.
One primary
Exactly one saffron action in view, and its ink is #161616 — 7.9:1 on the accent. Destructive actions are the danger emphasis with white ink, and are never the primary style.
Disabled
A control that cannot act is genuinely disabled — the DOM property, not an aria attribute alone, and never a handler wired to a no-op. A disabled control tells the truth about its state; a supplied no-op lies quietly.
Absent vs disabled
Where a capability does not exist for this reader at all, the control is absent, not disabled. Disabled means “not now”; absent means “not yours”.
Focus
One ring everywhere, declared once at the root and never per component: outline: none; box-shadow: 0 0 0 2px surface, 0 0 0 4px accent on :focus-visible. Two rings so it survives overflow: hidden. Never removed, never replaced by a colour change alone.
8.3 Tables, rows and navigation
Rows
Hairlines between rows, no zebra. Hover is a surface change, never a saffron fill. A selectable row is keyboard-reachable and marks the current one with aria-current; the detail it drives is a rail beside the table, not a navigation.
Headers
Sticky in the in-flow band (z 0–9) on the sunken plate. Sortable columns are buttons in the header cell and the sort state is in the button's accessible name, not in a glyph alone. A long table repeats its <thead> in print.
Filters
Every filter set carries a Clear — per-filter where there are several, plus one “clear every filter” — and the empty state names how many filters are active.
Nav shell
Where a product uses the shared nav component, its own defaults are a 216px labelled column collapsing to a 48px icon-only rail; a product free-composes its shell if it needs to. Group headings are <button aria-expanded aria-controls>; the active destination takes the accent plate, an inset 2px 0 0 saffron bar and accent-text ink. Every destination is an <a href>, never a button that sets state — middle-click, copy-link and the browser's own focus behaviour come free.
One definition
The navigation is one data definition rendered by one component. A screen passes which item is current and nothing else; a hardcoded nav array in a screen is how twenty-seven screens end up disagreeing.
8.4 Layering · the z-band contract
BandWhat lives there0 – 9in-flow stacking only — sticky table headers, watermarks50 – 69chrome-anchored popovers — account, notifications, entity scope, search70the navigation drawer80the assistant / side panel and its scrim90 – 94modals and full-screen viewers95 – 99toasts
The panel sits below modals, because a consequence flow opened from the panel must be able to cover the conversation that raised it; and below toasts, because a write's receipt must survive whatever is on top of it. The bands are published, and every screen that mounts an overlay is checked against them.
8.5 Overlays and toasts
One boolean
A screen owns exactly one boolean per overlay, with one opener and one closer. Two ways in is two states to disagree.
Three dismissals
Always all three: the scrim, a labelled close button (26–28px, 14–15px glyph), and Escape registered on mount and removed on unmount. Escape precedence is published where several layers can be open.
Focus
Overlay mode traps Tab and returns focus to the trigger on close. A docked panel deliberately has no trap — walking out into the page is the point of the mode.
Toasts
A toast is a write's receipt and belongs to the host screen, never to a component — a component that needs one takes a callback. Identifiers inside it are mono, and it never carries the only copy of a fact.
8.6 Accessibility commitments
Contrast
Text ≥ 4.5:1, UI borders ≥ 3:1 against their surface, in both themes, re-measured whenever a surface moves. Saffron never carries text on light — the accent text step does.
Keyboard
Full paths for the product's core journeys, not just for its forms. Focus order follows visual order.
Labels
Every chip, dot, heatmap cell and derived marker exposes a text label — including the derived part (“in progress, 9 days overdue, tier 3”). Icons are decorative.
Motion
100–200ms, and zero under reduced-motion, with streamed content rendered as complete blocks.
Targets
≥ 44px on touch via extended hit areas, even at 32px rows. Compact is unavailable below 1024px.
Demo chrome. A demo build may move the clock or wear another persona's lens; both are visibly not product. Dashed border, mono type, square 3–4px corners, no fill, neutral colour only — never saffron and never a semantic family, because demo chrome must never read as a status. Always dismissible, always in the flow above the content, and absent from a production bundle rather than hidden. A lens never grants data: it re-renders what the session may already see, or shows the honest empty state.
Section 09
Adoption
9.1 The carry-over kit · upload to the new project root, names unchanged
FileWhat it carriestokens.cssthe whole token layer — light × dark, compact × comfortable. The source of record.tokens.jsonthe same tokens, machine-readable, for tooling and native targetssagix-icons.jsthe sprite — all 46 glyphs, injected synchronouslysagix-chips.jsthe chip silhouettes, the paint families and the unknown-key treatmentsagix-lockup.svg sagix-lockup-reversed.svgthe identity. Never hand-author a wordmark.sagix-alerts.js sagix-redaction.js sagix-firstrun.jsthe shared treatments for banners and watches, redacted values, and first-run / empty statesnot the app shell not the data adapterChrome and engine are per application. A different product's navigation tree, screens and adapter are its own — copying them is how two applications become one confused one.not the domain registersState vocabularies, roles and lifecycles belong to the product whose engine defines them. Take the chip system; author your own registers.
Token names are the API
Product code uses --color-* · --text-* · --space-* · --radius-* · --elev-*. A raw hex in a product file is a bug, not a shortcut — it is the one thing that cannot be re-themed, re-measured or fixed centrally.
Reference, don't fork
A screen that needs a variant extends the component here; it never keeps a private copy. Two products needing the same thing is the threshold for adding it to the system.
Parity, not freeze
The never-edit rule protects against divergence, not against improvement: when a shared asset has a defect, fix it in the system, then every consuming project re-copies. Never fix it only in the receiving project — that is the fork the rule exists to prevent.
Define once
One definition per fact: one nav definition, one chip table, one focus ring, one session owner. A copy of something a register owns goes stale the moment the owner grows — and a copy that is merely too big fails silently forever.
Point by name
Never point at a thing by position — “the middle card”, “the last column” — in code or in prose. Positions re-aim themselves the day something is inserted. Name it, or count it by asking.
Versioning
A minor version may re-skin and may add tokens; it may not rename a token or change what a state means. Anything that breaks either is a major. Additions land at the end of the token file, each value beside the finding that changed it, so a diff between versions is one contiguous block.
Verify by value
When checking that a rule holds, exercise the code and read what the interface actually renders — never scan for a shape you imagined. A check written around one shape finds one shape. Force the failure, then ask what the template received.
In one line
Take the identity, the tokens, the icons, the voice and the conventions verbatim; author your own vocabulary; and when something is wrong, fix it here so every Sagix product inherits the fix.