Design System & Foundations
Foundations ← Gallery
Twende Design System
Foundations for the Passenger, Driver, and Admin surfaces β€” color tokens, typography, components, microcopy, accessibility rules, and target viewports. Use this page as the canonical reference when implementing in Flutter (mobile) or React (admin).
🎨 Color Tokens
Semantic naming β€” never use raw hex in code. Tokens map 1:1 to Flutter ColorScheme and Tailwind config.
primary / green-60
#0FA958
CTAs, online state, success, brand
primary-pressed / green-70
#0a7a3f
Button :active, hover
primary-soft / green-10
#e6f7ee
Selected chip background, success badge bg
danger / red-60
#DC2626
SOS, fraud alert, destructive actions
warning / amber-50
#F59E0B
Demand hints, weak network, surge, KYC pending
info / blue-60
#2563EB
Driver app accent, informational links
accent / purple-60
#7c3aed
Admin/Ops badges, secondary tags
text / dark
#0F172A
Body text, headings, top bars
text-muted / muted
#64748B
Secondary text, captions, helper
surface
#FFFFFF
Cards, sheets, inputs
surface-alt / gray-10
#f8fafc
App background, subtle dividers
border / gray-20
#e2e8f0
Card borders, input borders, dividers
πŸ”€ Typography
Inter (primary) with system fallback to Roboto / Segoe UI for low-end Android. Weights: 400 / 500 / 600 / 700 / 800.
H1 Β· Twende ride you can trust
Inter 24/32 Β· Semibold
H2 Β· Confirm your ride
Inter 20/28 Β· Semibold
Body Β· Your driver Kato is 4 minutes away. Plate UBD 123X.
Inter 16/24 Β· Regular
Body strong Β· Fare estimate UGX 9,200
Inter 14/20 Β· Semibold
Caption Β· Includes UGX 700 fuel adjustment
Inter 12/16 Β· Regular Β· Muted
πŸ“ Spacing Scale
8pt grid. Use the named scale tokens β€” never hard-code px values. All padding, margins, gaps follow this scale.
space-1
4 px Β· tight inline
space-2
8 px Β· base unit
space-3
12 px Β· icon-to-text
space-4
16 px Β· default padding
space-6
24 px Β· section padding
space-8
32 px Β· between sections
space-12
48 px Β· hero spacing
🧩 Core Component Library
Variants used across all three apps. Implement once in shared package, consume per-surface.
PrimaryButton
Green-60 fill, 10px radius, 700 weight. Min height 44px (a11y tap target).
SecondaryButton
Surface bg, gray-20 border. For non-primary actions.
DangerButton
Red-60 fill. Used for destructive actions only β€” always paired with confirmation.
ServiceChip
🏍️ Boda πŸš— Car πŸ“¦ Delivery πŸ‘₯ Share
Service type filter. Active state uses green-60 border + green-10 background.
SignalStateBanner
Online β€” full features
Weak network β€” map simplified
Offline β€” request queued
Three states. Auto-shows based on connectivity_plus signal.
SOSFloatingButton
SOS
Persistent during in-trip. Bottom-right, 60Γ—60. 5-second cancel window before alert sent.
DriverIdentityCard
K
Kato Moses βœ“ Verified
⭐ 4.8 · UBD 123X · Red Bajaj
Photo/initial avatar, name, KYC badge, rating, plate, vehicle. Always shown before pickup.
FareBreakdownCard
BaseUGX 3,000
Distance Β· 4.2 kmUGX 4,500
β›½ Fuel adjUGX 700
TotalUGX 8,200
Always show every line item β€” radical fare transparency is a core trust principle.
Status Badges
βœ“ Verified ⚠ Pending βœ• Frozen
Status badges used across KYC, driver verification, account state.
TextLink / Tertiary
No background, blue-60 text. For low-emphasis fallback actions.
πŸ“± Target Viewports
Always validate critical flows on the smallest target (Android low-end 360Γ—640) before larger devices.
360 Γ— 640
Low-end Android
Itel A56, Tecno Pop Β· ~70% UG market
414 Γ— 736
Standard mid-range
Tecno Camon, Samsung A series
768 Γ— 1024+
Admin desktop
Min web target 1024Γ—768
β™Ώ Accessibility Standards
Non-negotiable for low-end Android, low-literacy users, and ageing fleet drivers.
Tap targets β‰₯ 44 Γ— 44 px
All interactive elements meet Android Material accessibility guideline. Smaller icons get padding to satisfy.
Contrast β‰₯ 4.5:1
All body text passes WCAG AA on white surface. Muted text used for secondary content only.
Body β‰₯ 14 px
Minimum readable size on low-end screens. Critical info (fares, ETA, plate) always β‰₯ 16 px.
Color is never the only signal
Status uses color + icon + text. Risk zones use color + dot + label.
SOS button discoverability β‰₯ 95%
User testing target. Always bottom-right, always visible during in-trip.
No reliance on hover
Mobile-first means every action must work on tap alone. Tooltips replaced by inline help.
πŸ’¬ Microcopy Reference
Plain, direct, trust-building. Avoid jargon, dark patterns, and passive voice. Localize to Luganda where shown.
ContextEnglishLuganda (when localized)
Offline queued ride
No internet. We saved your request and will send it when you reconnect.
Tewali yintaneeti. Twetegese okusaba kwo, tujja kuwereza nga oddamu okufuna.
Fuel adjustment shown
Fare increased by UGX 700 due to fuel adjustment.
β€”
In-trip safety nudge
For your safety, keep all calls and payments in-app.
Olw'obukuumi bwo, kuuma essimu n'ensimbi mu app.
Surge active
High demand in your area Β· prices may be slightly higher.
β€”
SOS confirmed
SOS sent. Help is on the way. Stay where you are if safe to do so.
SOS eweerezeddwa. Obuyambi bujja. Sigala awo bw'oba mu bukuumi.
Driver no-show
Driver hasn't arrived. We're finding you another driver β€” no charge.
β€”
Permission rationale
We need your location to find drivers near you. We never share it with anyone else.
β€”
MoMo timeout
No response from MoMo. Try again, or pay with a different method.
β€”
Cancel fee notice
A small fee of UGX 500 applies because your driver is already on the way.
β€”
Driver tier unlock
You're in Gold tier β€” you'll see priority offers and a lower commission rate.
β€”
⚑ Pricing Intelligence β€” Twende's Niche
Most ride apps publish a static rate card and leave drivers and passengers fighting over fairness. Twende's pricing is a live, transparent, explainable engine: it ingests fuel, FX, traffic, demand, weather, and time-of-day signals every few minutes, blends them through a clamped formula, and publishes per-zone decisions that any operator, driver, or passenger can audit on demand.
β›½ Fuel Index
NITA-U weekly API + retail scrape fallback (Total / Shell / Stabex). Diff vs 30-day moving average β†’ fuel_adj bounded Β±15% of base.
πŸ’± FX Rate
Bank of Uganda mid-rate API every 5 min. fx_adj only applied when 7-day drift > 5% β€” avoids reactive fare thrash from daily noise.
🚦 Traffic
Google Distance Matrix API per H3 zone β€” actual_time / free_flow_time ratio drives the traffic component.
πŸ“ˆ Demand
Rolling 5-min quote density per zone, fed into Prophet 30-min-ahead forecast β€” produces the demand score.
🌧 Weather
OpenWeatherMap city-level. Rain / storms increment demand weight (passengers reluctant to walk).
πŸ“… Time-of-day
Workday vs weekend, rush windows, late-night premium. Calendar overlay for known events (election, Independence Day).
fare = ( base + dist Γ— k_d + time Γ— k_t ) Γ— surge + fuel_adj βˆ’ promo where surge = clamp( 1 + Ξ±Γ—demand + Ξ²Γ—traffic + Ξ³Γ—weather, 1.00, 1.80 ) fuel_adj = f( fuel_index_delta_pct ) Β· bounded Β±15% of base fx_adj = applied only when 7-day USD/UGX drift > 5% // always-enforced floors and shock guards driver_payout / km β‰₯ UGX 800 // minimum earnings floor new_fare / avg_recent ≀ 1.20 // EMA smoothing if exceeded surge ≀ 1.80 // Uganda regulatory cap
Explainable, not black-box
Every component visible to admin, driver, and passenger. Same data, same formula, three audiences.
Capped, not extractive
Surge ≀ 1.8Γ— by Uganda regulation. Driver minimum floor enforced even when formula would go below.
Smoothed, not whiplash
EMA smoothing if a quote would jump > 20% vs recent average. Builds long-term passenger trust.
Multi-currency-ready
Same engine, swappable feeds: KES + Kenya central bank for Nairobi, RWF for Kigali, etc.
Kill-switch ready
SuperAdmin can pause auto-pricing in < 5 s; system reverts to last known-good manual config.
Audited end-to-end
Every signal update, override, and decision stored immutably for 5 years (regulatory + ML retraining).
πŸ—ΊοΈ Google Maps Integration
Maps are the backbone of every flow. Choice of SDK affects accuracy, cost, and offline behaviour. Twende uses Google Maps as the primary, with thoughtful fallbacks for low-bandwidth and cost control.
SurfaceSDKWhat it powersFallback
Passenger app Google Maps SDK for Flutter (google_maps_flutter) Pickup pin drop, landmark search overlay, driver-arriving live position, in-trip route polyline Cached map tiles (7-day) for last viewport Β· simplified vector mode on weak network
Driver app Google Maps SDK + deep-link to Google Maps app for turn-by-turn navigation Trip route preview, driver position broadcast, demand heatmap overlay (custom layer) Google Maps app fallback to web URL if not installed Β· cached tiles when offline
Admin web Leaflet.js + Google Tile Provider Live ops map (~500 concurrent markers via MarkerCluster), vehicle compliance plate-photo locations Open-source tile provider (Mapbox / OSM) if Google bills spike β€” keeps ops console alive
Pricing Service Google Distance Matrix API (server-side) Traffic ratio for surge calculation (actual_time / free_flow_time per H3 zone) Cached values up to 5 min Β· degraded confidence flag if API down
Landmark suggester Google Places API + Twende editorial DB on PostGIS Autocomplete pickup search (Acacia Mall, Wandegeya, etc.) β€” Uganda landmarks not always well-covered Editorial DB primary in Uganda β€” Places API used to fill gaps and suggest new ones
Cost note: Google Maps APIs are billed per request. Twende keeps spend predictable via: server-side Distance Matrix calls (one per quote, not per render); aggressive client-side caching of map tiles; per-driver position updates batched to one event per 2s rather than firehose; and admin Live Map clustering to keep the marker count bounded. Estimated cost at 10k trips/day: ~USD 350/month.
Offline-lite: Last viewport tiles cached 7 days locally via flutter_cache_manager. Driver positions cached for 30 s during WebSocket reconnects. SOS works without map (text-only fallback).
🏍️ Vehicle Identity Trust β€” 5-Layer Defense
A real-world failure mode in ride-hailing: driver registered with Vehicle A but shows up driving Vehicle B (accident, sale, expiry, fraud, account-jumping). Twende addresses this end-to-end with five reinforcing layers across the three apps.
LayerWhat happensWhereFailure mode it prevents
1 Β· Onboarding
Prevention
Driver photographs vehicle from 4 angles + plate close-up. Logbook owner cross-checked vs KYC name. Vehicle locked to driver_id as a UNIQUE constraint. Driver app Β· KYC + Vehicle Registration screens Fake / borrowed vehicle at signup; account-jumping
2 Β· Pre-shift
Deterrence
Once every 24 h (or after > 12 h offline), driver photographs current plate. Client + server OCR validate against registered. Mismatch β†’ cannot go online. Driver app Β· Pre-shift Vehicle Check Opportunistic vehicle swap; using cousin's bike for the day
3 Β· In-trip
Detection
When driver marks "Arrived", passenger sees driver photo, plate, make/model/color in a "Pickup Verification" screen. "Wrong vehicle" β†’ instant trip cancel (no fee), driver auto-suspended pending review. Passenger app Β· Pickup Verification Wrong driver picks up; driver swapped vehicles between trips
4 Β· Continuous
Monitoring
5% of online sessions sampled for re-verification. Insurance / logbook expiry auto-suspends driver. Anomaly detection on driving patterns (acceleration profile change suggests different vehicle). Driver app Β· Background; Admin Β· Vehicle Compliance dashboard Long-running undetected swap; lapsed insurance; expired permits
5 Β· Change mgmt
Recovery
Legitimate vehicle change has its own flow: select reason (new / accident / expiry / borrowed), upload new docs and 4 photos. Driver cannot accept rides during 24–48 h review. Driver app Β· Change Vehicle Request Honest cases get a clear path; doesn't push drivers to bypass
Why five layers? Each layer alone is bypassable β€” onboarding photos can be staged, OCR can fail, passengers may not look closely. Defense in depth means a determined attacker has to defeat ALL of them. Failure rate of any single layer is acceptable; the combined miss rate must be near-zero. Target: passenger reports of vehicle mismatch ≀ 0.8 per 1,000 trips (industry-leading).
πŸ“Ά Connectivity States β€” Cross-App
How each surface degrades as connection deteriorates. Critical for Uganda's variable 3G/4G coverage.
StatePassenger appDriver appAdmin web
● Online Full real-time map, live fare, in-app calling Real-time offers, live nav, instant payouts Live KPIs, full WebSocket feed, all features
● Weak Simplified map, reduced refresh, queue non-critical events Reduced position frequency, batched updates WebSocket fallback to polling at 10 s interval
● Offline Local request queue, USSD fallback shown, cached trip view Trip state cached locally, sync on reconnect, SMS-based SOS Read-only mode from last cached snapshot, action queue with confirmation