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: #9DA5B1Light 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: #F5F4F1Border
--border-subtle: #DEDBD6
--border-default: #D1CEC9
--border-strong: #B8B4AE
--border-accent: #B83325Accent — 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: #7E8899Why 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, withfont-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-numsType 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 96pxDefault 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
--accentborder, 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-defaultborder, 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-subtleat 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
--accentfor active series,--text-tertiaryfor 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.
Pressablewithandroid_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
FlatListwithItemSeparatorComponentas a 1pxViewinborder.subtle.- Mono numerical columns:
fontFamily: 'IBMPlexMono_400Regular',
fontVariant: ['tabular-nums'].
- No alternating row colors. Pull-to-refresh
tintColor=accent.DEFAULT.
Charts
react-native-svgfor 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-notificationsfor push. Foreground presentation: banner + sound
(no badge counter — newsletter phase has no count semantics).
- Notification accent color:
#D43E2D(matchesapp.jsonplugin config). - Haptics:
expo-hapticslight 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)
StatusBarstyle is theme-driven:lightin dark mode,darkin light
mode (<StatusBar style={isDark ? 'light' : 'dark'} />).
app.jsonuserInterfaceStyleis"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 statictheme
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