Stratify
Design system

Stratify Design System V1

docs/stratify_design_system_v1.md

Source updated 03. Aug. 2026

STRATIFY — Design System Spec v1.0

Editorial Dark Finance Production-ready design spec — both for hand-implemented Stratify UI and as the foundational DESIGN.md that goes into any AI-assisted UI generation prompt. Specificity is the whole point — generic AI design defaults gravitate to warm cream + Fraunces + terracotta. This spec actively overrides that.

1. BRAND POSITION (one line)

Stratify is a curated investment-research publication for European retail investors — pre-license phase, research only, no execution (licensed copy-investing infrastructure re-enters on FMA grant). The interface must read as structured finance, not consumer fintech. Closer to a Bloomberg terminal than a Robinhood. Closer to a financial newspaper than a Material Design app.

2. VISUAL REFERENCES (named, concrete)

  • Fey (fey.com) — moody minimalist investment storytelling, cinematic data
  • Wealthsimple Trade — editorial calm-confidence
  • Linear — precision typography, hairline borders, restrained accent use
  • The Browser Company / Arc — playful but never childish
  • Financial Times mobile app — editorial finance dark mode
  • Stripe documentation — typographic hierarchy, density discipline

Reference *against*: Robinhood (too consumer), Trade Republic (too generic German bank), eToro (CFD-noisy), default shadcn/ui (too SaaS-bland).

3. COLOR TOKENS (exact hex codes — do not interpolate)

Dual-mode (v2.1): The product ships both a dark and a light theme. Dark is the default; light follows the OS setting and can be overridden in-app (System / Hell / Dunkel toggle on the Account screen). Single source of truth: packages/shared/src/tokens.ts (colorsDark + colorsLight). The earlier "dark-only" rule (§11) is superseded. Neither mode uses pure black or pure white — dark is a softened near-black (#0F1117), light is a warm stone off-white (#F5F4F1), so the editorial-finance feel holds in both.

Dark mode (default)

Surface

--bg-base:        #0F1117   /* softened near-black, blue-grey undertone */
--bg-elevated:    #161B22   /* raised cards, modals */
--bg-interactive: #1E242D   /* hover, pressed states */
--bg-overlay:     rgba(15, 17, 23, 0.88)  /* modal scrim */

Text

--text-primary:    #ECEBE6   /* warm off-white, never pure white */
--text-secondary:  #9DA5B1
--text-tertiary:   #636C7A
--text-disabled:   #45505D
--text-inverse:    #0F1117   /* on accent backgrounds */

Border (hairline-first)

--border-subtle:   #1D222A   /* default card edges */
--border-default:  #282F39   /* input fields, dividers */
--border-strong:   #373F4B   /* focus states */
--border-accent:   #D43E2D   /* active selection */

Accent — single, signal red

--accent:          #D43E2D   /* signal red — conviction, structured finance */
--accent-hover:    #E94E3D
--accent-muted:    rgba(212, 62, 45, 0.12)

Data semantics (numerical only — never used for UI states)

--data-positive:   #4ADE80   /* gains, only on numbers */
--data-negative:   #F87171   /* losses, only on numbers */
--data-neutral:    #9DA5B1

Light mode (warm stone — like a printed financial report)

Surface

--bg-base:        #F5F4F1   /* warm stone off-white, never pure white */
--bg-elevated:    #EDEDEB
--bg-interactive: #E4E2DE
--bg-overlay:     rgba(19, 21, 25, 0.72)

Text

--text-primary:    #131519
--text-secondary:  #4C5565
--text-tertiary:   #7E8899
--text-disabled:   #AAB3BE
--text-inverse:    #F5F4F1

Border

--border-subtle:   #DEDBD6
--border-default:  #D1CEC9
--border-strong:   #B8B4AE
--border-accent:   #B83325

Accent — darkened signal red for contrast on light surfaces

--accent:          #B83325   /* darker than dark-mode #D43E2D — readable on stone */
--accent-hover:    #9E2A1E
--accent-muted:    rgba(184, 51, 37, 0.10)
Why a darker accent in light mode: #D43E2D on a light stone background is too hot and fails contrast on small text. #B83325 keeps the signal-red identity while staying legible. This is the one intentional break from the single-accent rule — same hue family, mode-tuned luminance.

Data semantics

--data-positive:   #15803D   /* darker greens/reds for light-bg legibility */
--data-negative:   #DC2626
--data-neutral:    #7E8899
Why signal red over amber: common AI design tools gravitate to terracotta/amber as a warm accent. To break that default decisively, we use a sharper red. Reads as "finance with conviction" not "warm hospitality."

4. TYPOGRAPHY (Google Fonts — exact families)

Font stack

  • Display: Newsreader, serif — weights 200, 300, 500. Editorial but not Fraunces/Playfair (common AI design defaults).
  • Body: IBM Plex Sans, sans-serif — weights 400, 500, 600. Technical precision, not Inter.
  • Mono: IBM Plex Mono, monospace — weight 400, with font-feature-settings: "tnum". All numerical data lives here.
Critical: Never substitute Inter, Roboto, Open Sans, Fraunces, Playfair, or system-ui. Those are common SaaS defaults. Newsreader + IBM Plex is the signature.

Type scale

Display XL    56 / 64    Newsreader 300    -0.02em tracking
Display L     40 / 48    Newsreader 300    -0.02em tracking
Display M     32 / 40    Newsreader 300    -0.01em tracking
Headline      24 / 32    IBM Plex Sans 500   0em tracking
Body L        17 / 26    IBM Plex Sans 400   0em tracking
Body          15 / 24    IBM Plex Sans 400   0em tracking
Body S        13 / 20    IBM Plex Sans 400   0em tracking
Caption       11 / 16    IBM Plex Sans 500   +0.08em tracking, UPPERCASE
Mono L        24 / 32    IBM Plex Mono 400   tabular-nums
Mono          15 / 24    IBM Plex Mono 400   tabular-nums
Mono S        13 / 20    IBM Plex Mono 400   tabular-nums

Type rules (non-negotiable)

  • All currency, percentages, AUM figures, dates → Mono with tabular-nums
  • All labels above tables/charts → Caption (uppercase, +0.08em tracking)
  • Display fonts only for landing screens, marketing surfaces, and major section headers — never for in-app body content
  • Numbers in display sizes should use Newsreader (not Mono) — for emotional weight
  • Body copy stays at 15px; never inflate

5. SPACING (4px base, restrained scale)

0.5x   2px
1x     4px
2x     8px
3x    12px
4x    16px
5x    20px
6x    24px
8x    32px
10x   40px
12x   48px
16x   64px
20x   80px
24x   96px

Default screen padding mobile: 20px horizontal. Section vertical rhythm: 48px between blocks, 24px within. No values in between — discipline matters more than precision.

6. COMPONENT PRIMITIVES

Button — primary

  • 1px solid --accent border, transparent fill at rest
  • 0px border-radius (sharp corners)
  • Padding: 14px vertical, 24px horizontal
  • Type: IBM Plex Sans 500, 13px, +0.08em tracking, UPPERCASE
  • Hover: fills with --accent, text becomes --text-inverse
  • Active: fills with --accent-hover
  • No shadow. Ever.

Button — secondary

  • 1px solid --border-default border, transparent fill
  • Otherwise identical to primary
  • Hover: border becomes --border-strong

Card

  • Background: --bg-elevated
  • 1px solid --border-subtle
  • 4px border-radius (subtle, not pill)
  • No drop shadow. Use hairline borders for depth.
  • Internal padding: 20px

Input

  • Bottom border only, 1px solid --border-default
  • No box, no fill, no border-radius
  • Focus state: bottom border becomes --accent, 2px
  • Label sits above as Caption style
  • Type: Body, IBM Plex Sans 400, 15px

Table

  • Hairline rows (1px --border-subtle)
  • Tabular numbers right-aligned, IBM Plex Mono
  • Headers: Caption style, --text-tertiary
  • Row hover: --bg-interactive
  • No alternating row colors

Chart / data visualization

  • Use accent only for selected/focused data series
  • All other lines in --text-tertiary
  • Axis labels in Caption style
  • Grid lines in --border-subtle at 50% opacity
  • No animation on initial load — let the data sit

7. MOTION

  • Default ease: cubic-bezier(0.2, 0.8, 0.2, 1) — sharp, confident
  • Duration standard: 200ms
  • Duration emphasized: 400ms (page transitions, modal open)
  • Never: spring bounces, scale pulses, glow effects, gradient sweeps
  • Hover transitions on buttons: 150ms color change only
  • Numbers animating to new values: use counter rollup (200ms, linear ease for last 30%)

8. ICONOGRAPHY

  • System: Phosphor Icons (light weight, 1.5px stroke)
  • Size: 16px or 20px only
  • Color: Match adjacent text color
  • Never use Material Icons, Heroicons, Feather Icons (too common in SaaS / AI-default UIs)

9. ANTI-PATTERNS (explicitly do not generate)

Generic AI generators will default to several patterns. Each prompt should explicitly reject these:

  • Fonts: Inter, Roboto, Open Sans, system-ui, Lato, Fraunces, Playfair, Space Grotesk
  • Colors: Warm cream backgrounds (#F4F1EA family), terracotta accents, purple gradients, any gradient at all on backgrounds
  • Components: shadcn/ui Card defaults, Material Design ripples, iOS-style rounded squircles, glassmorphism, neumorphism
  • Layouts: Hero with centered "AI for [thing]" headline, three-column feature grid below, identical SaaS landing structure
  • Effects: Drop shadows, glow effects, animated gradients, parallax scrolling, scroll-jacked sections
  • Imagery: Stock photography of diverse smiling people, gradient mesh backgrounds, 3D blob illustrations, isometric character illustrations
  • Robinhood-isms: Bright neon green for gains, confetti animations, celebratory micro-interactions on financial events
  • Tropes: "Trusted by 10,000+ users" social proof bars, infinite-scrolling client logos, animated counters on landing pages

10. SCREEN-SPECIFIC GUIDANCE

Phase note (v2.1 pre-license): The MiFID suitability questionnaire, automatic allocation, and the Trade Execution Feed below describe the licensed (Phase C) product. Pre-license, Stratify is a research-publication reader — no suitability gate, no allocation, no execution. Treat those screens as design-ready specs that re-enter on FMA license grant.

Onboarding (4-question MiFID suitability — Phase C / licensed)

  • One question per screen. No progress bar (use Caption: "Question 2 of 4").
  • Question in Display M Newsreader.
  • Answers as large tap targets, 1px hairline border, transparent fill.
  • "Continue" button bottom-anchored, primary style.
  • Background: --bg-base. Pure.

Pilot Selection (single-Pilot at MVP)

  • Vertical card list, not grid.
  • Each Pilot card: Newsreader display name, Mono performance numbers, IBM Plex Sans bio.
  • One accent line under the active Pilot (1px, --accent, 40px wide).

Dashboard

  • Top: portfolio value in Display L Newsreader (not Mono — emotional weight matters here).
  • Below: tabular data in IBM Plex Mono.
  • Single sparkline using --accent for active series, --text-tertiary for benchmark.
  • No "good job!" reinforcement copy.

Trade Execution Feed (Phase C / licensed — pre-license shows a research-signal feed, no execution)

  • Tabular, timestamped, monospace.
  • Each row: timestamp · Pilot · action · symbol · proportional amount
  • Failure states in --data-negative, never red boxes — just inline text.

PROMPTING TEMPLATE FOR AI-ASSISTED UI GENERATION

When generating any Stratify UI screen with an LLM, prepend this to the prompt:

You are designing for Stratify, a curated investment-research publication
for European retail investors (pre-license phase — research only, no
execution). The visual language is "Editorial Dark Finance" — closer to a
Bloomberg terminal than a consumer fintech app. The product is dual-mode:
dark is the default, light follows the OS and can be toggled in-app.

Use exactly these tokens:
- Dark mode: #0F1117 bg base, #161B22 elevated; #ECEBE6 text primary,
  #9DA5B1 text secondary; #D43E2D accent (signal red, not amber)
- Light mode: #F5F4F1 bg base (warm stone, never pure white), #EDEDEB
  elevated; #131519 text primary, #4C5565 text secondary; #B83325 accent
  (darkened signal red for contrast on stone)
- Display font: Newsreader (weights 200, 300, 500)
- Body font: IBM Plex Sans (400, 500, 600)
- Mono: IBM Plex Mono with tabular-nums for all numerical data

Component rules:
- Buttons: 0px radius, 1px border, transparent fill, uppercase tracked text
- Cards: 4px radius, 1px hairline border, no shadow
- Inputs: bottom border only, no box
- All numbers in IBM Plex Mono with tabular-nums

Explicitly avoid:
- Inter, Roboto, Fraunces, Playfair, Space Grotesk fonts
- Warm cream backgrounds or terracotta accents
- Drop shadows, glassmorphism, gradient backgrounds
- Rounded corners >4px
- shadcn/ui defaults, Material Design patterns
- Robinhood-style bright greens or celebratory animations

The screen to design is: [describe specific screen here]

Match the aesthetic of Fey, Linear, and the Financial Times mobile app —
not Robinhood, not Trade Republic, not shadcn dashboards.

11. MOBILE (React Native / Expo)

The mobile app ships in parallel with web in v2. Same Editorial Dark Finance language. Single source of design tokens: packages/shared/src/tokens.ts. Tailwind (web) and the RN theme provider (mobile) both consume it — never duplicate values.

Font loading

  • Use @expo-google-fonts/newsreader, @expo-google-fonts/ibm-plex-sans,

@expo-google-fonts/ibm-plex-mono. Load in app/_layout.tsx with expo-font + expo-splash-screen so the splash stays up until fonts resolve. Never let system fonts flash through.

  • RN font family strings: Newsreader_300Light, Newsreader_500Medium,

IBMPlexSans_400Regular, IBMPlexSans_500Medium, IBMPlexMono_400Regular.

  • iOS does not synthesize bold/italic — load every weight you intend to use.

Theme provider

Decision: do not use Tamagui or NativeWind. Reason: the design system is small (a handful of components, all custom). Tamagui pulls a Babel plugin and new-arch alignment risks; NativeWind drags class-name parsing that does not play well with the design system's hairline-borders / tabular-mono detail. The RN surfaces import the shared theme object directly via apps/mobile/lib/theme.ts and use plain StyleSheet.create() or inline style objects keyed off it.

Spacing & touch targets

  • Base spacing scale identical to web (tokens.spacing, 4px base).
  • Minimum touch target 44 × 44 pt on iOS, 48 × 48 dp on Android. If a

control's visible area is smaller (e.g. a hairline-button row), wrap it in a Pressable that pads the hit-slop to spec via hitSlop.

  • Default screen padding 20pt horizontal (matches web).

Buttons & inputs

  • Same 0-radius, 1px hairline, uppercase-tracked label rules as web.
  • Pressable with android_ripple={{ color: tokens.colors.accent.muted, borderless: false }}

for a restrained Android pressed state. Never show Material ripples on iOS.

  • iOS pressed state: 150ms opacity transition to 0.8.
  • TextInput: bottom-border only, no box, accent color on focus. Use

selectionColor={tokens.colors.accent.DEFAULT}.

Tables / lists

  • FlatList with ItemSeparatorComponent as a 1px View in border.subtle.
  • Mono numerical columns: fontFamily: 'IBMPlexMono_400Regular',

fontVariant: ['tabular-nums'].

  • No alternating row colors. Pull-to-refresh tintColor = accent.DEFAULT.

Charts

  • react-native-svg for sparkline-grade charts. Same accent-only-for-selected

rule as web. No chart-library defaults (no Victory, no Recharts equivalent).

  • No animation on initial render — the chart sits before it moves.

Motion

  • Web uses cubic-bezier(0.2, 0.8, 0.2, 1). RN equivalent via

react-native-reanimated: Easing.bezier(0.2, 0.8, 0.2, 1).

  • Duration constants identical to web.
  • Never use spring animations with bounce > 0 in this product.

Push notifications & haptics

  • expo-notifications for push. Foreground presentation: banner + sound

(no badge counter — newsletter phase has no count semantics).

  • Notification accent color: #D43E2D (matches app.json plugin config).
  • Haptics: expo-haptics light impact on signal-tap navigation only.

No celebratory haptics on financial events (Robinhood anti-pattern).

Icons

  • phosphor-react-native, light weight, 16/20pt only — same as web.

Status bar & system UI (v2.1 — dual-mode)

  • StatusBar style is theme-driven: light in dark mode, dark in light

mode (<StatusBar style={isDark ? 'light' : 'dark'} />).

  • app.json userInterfaceStyle is "automatic" so the OS can drive the

default; the in-app toggle (System / Hell / Dunkel) overrides via ThemeProvider (persisted in SecureStore).

  • All screens consume colors through useTheme() — never the static theme

export inside a component. The static theme (dark) remains only for the pilot/admin compose tool, which is intentionally dark-only.

  • ~~userInterfaceStyle: "dark" — light mode not supported in v2.~~ Superseded

by dual-mode in v2.1 (see §3).

App Store specifics

  • "Reader app" pattern: subscriptions managed on web, mobile is a free reader.

Add the App Store-mandated External Purchase Link copy on the account screen *only if* requested in review.

  • Notification permission prompt timing: ask after the user opens their first

signal, not on app launch.


v1.0 — May 2026 — Editorial Dark Finance* *v1.1 — May 2026 — added §11 Mobile (RN/Expo)* *v2.1 — 2026-06-03 — dual-mode