Nzinga Design System

Web reproductions of the components the Nzinga phone app is built from, at their exact sizes, in the Nzinga Electric palette and the app's compact type scale. Every prototype page loads nzinga-ds.css, so what you review here is what gets built. Sources are in apps/mobile/components; the template a component came from is named where it is not Luna. Each block in the stylesheet carries the classes it reproduces.

Brand

components/brand/Cowrie.tsx · components/brand/Wordmark.tsx · generated from docs/logo (logo_page.py, parts.json)

  • Cowrie ellipse 56 × 80 (7:10); color is the shell, ground the slit
  • Slit eleven rounded bars from 32px up, seven below (small)
  • spectrum fills the shell Magenta → Flame → Volt, top to bottom
  • Wordmark Outfit ExtraBold outlines with hand-set kerning; never retyped as text
  • Short form "nzinga" (Home header 92, Welcome 104, Phone 112)
  • Two-line form nzinga over NETWORK (descriptor; About screen 168)

Welcome tints the mark per slide (spectrum, cyan, Volt); About sets the spectrum mark over the two-line wordmark; small sizes switch to the seven-bar slit.

Colour

utils/color-theme.ts (Tailwind classes) · app/contexts/ThemeColors.tsx (JS values) · change both together. Default theme is dark.

  • highlight Volt: action, money, accepted. Content on it is always black (text-black, colors.onHighlight)
  • accent coloured text: Volt in dark, a darkened Volt in light
  • magenta recording and live
  • cyan information, in review, pending
  • violet models and AI, licence share
  • flame warmth, warnings, withdraw, not accepted

The last row exists only in ThemeColors.tsx (no Tailwind class). brand in color-theme.ts holds the raw values for gradients: black #0B0B10, white #F7F7FA, magenta, flame, Volt, cyan, violet.

Typography

tailwind.config.js fontSize, a compact scale (Tailwind's default reads oversized on a phone). ThemedText = Text with text-primary and maxFontSizeMultiplier 1.25. The platform font everywhere; Outfit only for font-outfit / font-outfit-bold. Text with no size class is 14px.

font-outfit-bold text-4xl
Ẹ káàbọ̀, Amara
font-outfit-bold text-3xl
Greetings and Introductions
font-outfit-bold text-2xl
What's Your Number?
text-4xl font-bold
Library
text-3xl font-bold
Sent For Review
text-2xl font-bold
Wallet
text-xl font-bold
Record now
text-lg font-bold
Consent and Privacy
text-base font-medium
Directions from the market
default 14 (Button label)
Send for review
text-sm text-subtext
Tuesday 3 November
text-xs text-subtext
Yoruba · 3:12 · Today
text-[11px] uppercase
Today's idea
text-[9px]
Tab bar label

Spacing and radius

tailwind.config.js spacing.global = 16px · Tailwind scale, 1 unit = 4px

  • px-global screen gutter, built into ThemedScroller, Header and ThemedFooter
  • !px-10 join screens and Profile (40px)
  • gap-2 8px between StatCards and chips
  • py-5 ListLink and Expandable rows; py-3.5 recording rows and ConsentOption
  • rounded-lg 8 (Button default, Input, CodeInput)
  • rounded-xl 12 (TipCard icon box, PromptUnit tile)
  • rounded-2xl 16 (ConsentOption, StatCard, info notes, SessionRow tile)
  • rounded-3xl 24 (TipCard, groups, SessionCard, bordered cards)
  • rounded-[25px] AudioPlayerBar and the recording controls
  • 40 ActionSheetThemed top corners; 20 ConfirmationModal

Shadows

utils/useShadow.ts shadowPresets: getShadow defaults #000 at 0.2, offset 0/2, radius 3.84

  • small 0 1 / 2.5 at .2
  • card 0 2 / 3.84 at .2 (AudioPlayerBar, recording controls)
  • medium 0 3 / 5 at .2 (Profile and About groups)
  • large 0 5 / 20.84, effective .04 (RecordButton)
  • shadow-sm 0 1 / 2 at .05 (Switch thumb)
small
0 1 / 2.5 · .2
card
0 2 / 3.84 · .2
medium
0 3 / 5 · .2
large
0 5 / 20.84 · .04
shadow-sm
0 1 / 2 · .05

Icon

components/Icon.tsx: Lucide, size 24, strokeWidth 2, colour colors.text · variant plain | bordered | contained · iconSize xs 32/16 · s 40/20 · m 48/24 · l 64/32 · xl 80/40 · xxl 96/48

plain 24 · contained xs / s / m · bordered l / xl · the Volt disc on Saved and Payout (h-20 w-20 bg-highlight, black icon).

Button

components/Button.tsx: variant primary | secondary | outline | ghost · size small py-2 | medium py-3 | large py-3.5 (48pt) · rounded lg default, full · iconStart / iconEnd · loading · disabled · href

  • Label Text font-medium at the 14px default; text-invert on primary, text-primary otherwise
  • Icons 16 / 18 / 20 by size, mr-2 / ml-2
  • Volt CTA !bg-highlight + !text-black, iconColor onHighlight (Record now, I Agree, Send for review, Save method)
  • Withdraw outline with !border-flame and !text-flame
  • Loading spinner in black on Volt, accent on outline, invert on primary

Welcome's pills: flex-1 rounded-full py-4, icon 20, label ml-2 text-sm font-medium; the invite pill is bg-text with !text-invert.

ActionButton

Walley components/ActionButton.tsx: icon · label (two lines with \n) · href / onPress · disabled · variant highlight | secondary

  • Circle h-16 w-16 rounded-full bg-highlight, Icon 24 in black
  • secondary border border-border bg-secondary, icon in the text colour
  • Label mt-2 text-center text-sm font-semibold
  • Wallet row flex-row justify-between px-global: Withdraw (disabled below the minimum), Statements, How you're paid, Payout method

Withdraw disabled until the available balance reaches the minimum payout.

Last one in the secondary look, for actions that move no money.

Input

components/forms/Input.tsx: variant animated (default) | classic | underlined · label · rightIcon · error · isPassword · containerClassName

  • Field h-14 (56), 14px text, border border-border
  • animated rounded-lg, the label floats from 16px placeholder to 12px on a background box
  • classic rounded-2xl, label above in mb-2 font-medium; search fields are !rounded-full !border-transparent !bg-secondary
  • underlined border-b-2, mb-6 (Review title, Edit Profile)
  • Error border-magenta and mt-1 text-xs text-magenta
Invite code
K7Q2-M8XA
Name
Name

Tell us what to call you.

Phone number
With the country code
Title
Directions from the market
Name on the account
Amara Okafor

CodeInput

components/CodeInput.tsx (Nzinga): six boxes over a hidden TextInput (number pad, one-time-code autofill) · onComplete at six digits · error · disabled

  • Box h-14 flex-1 rounded-lg border, gap-2
  • Current border-highlight with a blinking h-7 w-0.5 Volt caret
  • Digit text-2xl font-semibold
  • Error every box border-magenta, mt-2 text-xs text-magenta
482
482910

That code is not right. Check it and try again.

Chip

components/Chip.tsx: label · isSelected · size xs | sm | md (default) | lg | xl | xxl · icon · href / onPress

  • xs / sm text-xs · md px-3 py-1 text-sm · lg px-4 py-1.5 text-base (filters, setup, payout)
  • Unselected bg-secondary text-subtext · Selected bg-text text-invert
  • Icon -ml-1 mr-2 in colors.icon
AllRecordingsInvitesPayouts
YorubaNigerian PidginAdd a languageDemo data
xssmmdlgxlxxl

Switch

components/forms/Switch.tsx: label · description · icon · value · onChange

  • Track h-6 w-10; off bg-secondary, on bg-highlight
  • Thumb h-5 w-5 bg-white shadow-sm
  • Label text-base font-medium · Description text-sm text-subtext
Microphone · neededTo record voice notes when you tap record. Nothing records on its own. Allowed.
Pause all sharingNew recordings stay with you until you turn this off.

Section

components/layout/Section.tsx: title · subtitle · titleSize sm | md | lg | xl (default) | 2xl | 3xl | 4xl · link / linkText · icon

  • Title font-bold at the titleSize class
  • Subtitle text-subtext at the 14px default
  • Link text-primary underline (14px), right-aligned
  • Page titles 4xl on Library, Circle, Payout, Statements, All Activity, Languages, Notifications
Library
5 recordings · 3 accepted · 12 min
Recent activity See all
Record now
Today's idea, in Yoruba
Sessions See all
Short sets of ideas on one theme
Who can use new recordings

Expandable

components/Expandable.tsx: title · description · icon (24, mr-3) · defaultExpanded · onChange · content px-4 pb-4 pt-4, children "text-sm leading-6 text-subtext"

ThemeTabs

components/ThemeTabs.tsx: bar h-[48px] border-b · type fixed (tab flex-1) | scrollview (px-4) · label text-base, active text-accent · indicator h-[2px] bg-highlight · headerComponent scrolls away above the sticky bar

Tab bar

Feedy (tabs)/_layout.tsx + components/TabButton.tsx: five w-1/5 slots, Home, Library, the Record button, Circle, Wallet

  • TabButton pt-4, icon 24, opacity-40 unless focused (stroke 2.5)
  • Label text-[9px], shown only when focused (labelAnimated)
  • Badge h-3 w-3 bg-magenta
  • Record h-10 w-10 rounded-full bg-highlight, Mic 18 in black; pushes Record, not a tab

MultiStep

Feedy components/MultiStep.tsx, extended: Header with back (after the first step), close and a ghost "Skip" on optional steps · ProgressBar segments under it (mb-8 px-global, h 4, gap-1, rounded, bg-border, Volt fill; segmentTotal spans the whole setup) · footer px-global py-3 › Pressable rounded-full bg-highlight py-5 › text-base font-semibold text-black · nextDisabled dims it, error sits above in magenta

Setup, step 3 of 6 (About you, optional).

Could not save the request. Try again.

Payout, step 2 of 4 (Method), disabled until a method is saved.

Sheets

components/ActionSheetThemed.tsx: colors.sheet, top radius 40, bottom inset; screens add px-global pb-4 pt-6, a text-xl font-bold title and rounded-2xl bg-background item groups · components/ConfirmationModal.tsx: colors.bg, radius 20, p-8 pb-14, title text-xl font-bold, message text-subtext, Cancel bg-secondary and Confirm bg-flame with black semibold text

Sort by
Discard this recording?
It is deleted from your phone. Nothing was sent anywhere.

Screen shell

ThemedScroller (ThemeScroller.tsx) flex-1 bg-background px-global with a trailing h-20 spacer · ThemedFooter (ThemeFooter.tsx) px-global pt-global, paddingBottom insets.bottom, no border · every screen has a Header; sticky actions go in the footer

TipCard

components/TipCard.tsx, Luna's chat-home card: title · icon · label · note · tone default | highlight | cyan | violet | magenta | flame · size md | lg · rightIcon / onRightPress

  • Card rounded-3xl border border-border bg-background, min-h 44, mb-2; never changes colour
  • md p-3, icon box h-7 w-7 rounded-xl bg-secondary, Icon 15, title text-sm font-semibold
  • lg p-4, box h-10 w-10, Icon 20, title text-base font-semibold leading-6
  • Tone colours the icon and the label (text-[11px] uppercase tracking-wider)
  • Note mt-0.5 text-xs text-subtext · Right h-9 w-9, Icon 18 subtext

StatCard

components/StatCard.tsx, from Multia analytics.tsx: title · value · caption · tone default | accent | cyan | magenta | violet · icon · trend · href

  • Card rounded-2xl bg-secondary p-global
  • Title text-sm text-subtext · Value text-xl font-bold in the tone · Caption text-xs text-subtext
  • Icon h-8 w-8 rounded-full bg-background, Icon 16 subtext
  • Trend mt-10 pill, accent up / magenta down

FlipBalance

Walley components/FlipBalance.tsx: value (formatted) · visible · label · textClassName · each digit in a 48pt window slides to "•" when hidden, 280 ms, 30 ms stagger; symbols and separators stay

Available balance
+$1.80 this week
2h 11m accepted · in US dollars
Available balance
+$1.80 this week
2h 11m accepted · in US dollars

BalanceChart

Walley components/BalanceChart.tsx: data · labels · visible · height (Wallet 220) · formatYLabel · line and fill in colors.highlight (fill .25), dashed grid · Wallet reveals it left to right over 2 s each time the tab is focused

TransactionItem

Walley components/TransactionItem.tsx: title · time · amount · method · isIncome · pending · icon · href / onPress

  • Row py-3, minHeight 64 · circle h-14 w-14 bg-secondary, Icon 24
  • Title text-lg font-bold · Time text-sm text-subtext
  • Amount text-lg font-semibold: income accent, pending cyan, payouts primary · Method (status) text-sm text-subtext

Rows and details

Inline sub-components the screens repeat: RecentRow (Home) · RecordingRow (Library) · InviteRow (Circle) · DetailItem (Payout review, Transaction, from Walley) · Statement (Statements, Walley) · summary rows (How You're Paid, Agreement)

SessionCard

components/SessionCard.tsx, from Courso FeaturedCourse: session (lib/sessions.ts) · status · width (260; Sessions screen up to 320) · opens /screens/session

  • Panel min-h-[200px] rounded-3xl p-5 in the session's tone: cyan, violet, magenta or flame; content is black
  • Top icon tile h-11 w-11 rounded-2xl bg-black/10 and the status pill (New, 2 of 5 recorded, Complete)
  • Title mt-6 font-outfit-bold text-xl · Meta "5 prompts · 8 min · about $1.60" text-sm text-black/70
  • Progress mt-3, h 5, bg-black/15 with a black fill
  • Session header the same panel at full width with the title at font-outfit-bold text-3xl

SessionRow and PromptUnit

components/SessionRow.tsx, from Courso CourseList · PromptUnit in app/screens/session.tsx, Courso's unit row

  • SessionRow tone tile h-16 w-16 rounded-2xl, title text-base font-bold, meta text-xs subtext with dot separators, a thin progress line once started, h-11 w-11 rounded-xl chevron tile
  • PromptUnit py-4 border-b, h-12 w-12 rounded-xl tile (Volt with a check once recorded, else the number), state line Recorded / Up next / Not yet, Mic or RotateCcw tile

AudioPlayerBar and recording controls

components/AudioPlayerBar.tsx (Nzinga, in Luna's ChatInput box): uri · durationSeconds · unavailableText · tap the bars to seek · recording.tsx controls in the same box

  • Box rounded-[25px] border bg-background px-3 py-3 + shadow card
  • Play h-11 w-11 rounded-full bg-primary, Play/Pause 18 in invert
  • Bars 36, played in Volt, the rest subtext at .45 · Time text-xs tabular
  • Recording Pause and Trash2 in h-11 w-11 bg-secondary circles, Done in a Volt pill (min-w 120) with black text

RecordButton

components/RecordButton.tsx, on Luna's AiCircle: size (Record screen 200) · live · busy · icon · ring size × 0.56 on bg-background (shadow large), gradient ring − 12 in the brand spectrum Magenta → Flame → Volt, disc ring − 20, Mic at disc × 0.3, stroke 1.2 · live: the ring breathes to 1.06, the icon turns magenta and speaking.json plays over it at 0.2 · the recording screen's level meter draws magenta bars

Timelines

Multia order-detail TimelineStep (Recording Detail, Saved) · Walley send.tsx "Track your transfer" (Payout success)

  • Done Volt disc with a black check · Current cyan ring with a cyan dot · Failed flame disc with an X · To come bordered, title in subtext
  • Payout Check in accent when done, Circle in subtext to come, ml-3 h-10 connectors

ProgressBar

Feedy components/ProgressBar.tsx, extended: percentage · height (8) · backgroundColor (bg-secondary) · fillColor (bg-highlight) · segments · label · animates on focus · SetupProgress is the 6-piece setup line under the setup headers

Ready to withdrawminimum $5.00

$1.90 more to reach the minimum payout.

Wallet's minimum payout bar · the setup line at step 4 of 6 · a session row's 4pt line.

StatusBadge

components/StatusBadge.tsx, from Multia's order pill: label · tone · dot · rounded-full px-2 py-1, text-xs font-semibold in the tone, the fill a tint of the same colour (alpha 2E dark, 24 light) so it reads on both themes

highlight / green → accent · cyan / amber → cyan · flame / red → flame · magenta · violet · muted: bg-secondary with subtext.

Avatar

components/Avatar.tsx: src · name (initials) · size xxs 28 · xs 32 · sm 40 · md 48 · lg 64 · xl 80 · xxl 96 · border · link

AOAOAOAOAOAO

Initials are 14px font-medium at every size; Home uses xs, Circle rows sm, Profile xl, the setup photo xxl with a border.

Placeholder

components/Placeholder.tsx: icon · title · subtitle · button with href or onPress · circle h-20 w-20 bordered, Icon 30 · title text-xl font-bold · subtitle text-subtext · Button outline rounded full

Not enough to withdraw yet
The minimum payout is $5.00. You have $3.10 available.
Could not load your wallet
Check your connection and try again.

Cards

Bordered card (Luna profile.tsx: rounded-3xl border, no fill) · list group (rounded-3xl bg-secondary, shadow medium) · info note (rounded-2xl bg-secondary p-4, Icon 18, text-sm text-subtext) · components/Card.tsx is present but no screen uses it

AO
Amara Okafor
+234 803 412 0021
Yoruba · member since October
Nigerian PidginEnglish
Requested on this phone; Nzinga confirms by SMS before any money moves. Check the number and name: they must match the account.
Found by comparing scrambled phone numbers. Nzinga keeps none of them, and people who are not members are never revealed.