GetirFinans Design System
- Role
- Design System Lead
- Timeline
- 2025 — Present
- Team
- Solo owner, with iOS/Android engineering and 20 domain designers
- Platforms
- Web, iOS, Android
GetirFinans started as a product inside the Getir super-app: light mode only, inheriting the parent shell's conventions. The color foundation was a flat variable list named by appearance (GrayPrimary, OffWhite, DarkPurple, PalePurple, FintechGreen). No primitives, no semantics, no mode awareness. Design lived across 20 independent domain Figma files with no component sync protocol. Then leadership asked for dark mode.
Most design system case studies show a system that was never load-bearing. This one was rebuilt underneath a running product, and the constraint that forced it is the same constraint that proved it worked.
What this case study covers, in four deliverables:
- 01A two-tier token architecture replacing the legacy color file
- 02Dual-mode support across web, iOS, and Android
- 03A documentation site the team actually uses
- 04A governance model and roadmap the org adopted
Where it started
The Legacy System
The starting color system was a flat list: Getir-Primary, GraySecondary, PalePurple, FintechGreen. Names described visual properties, not intent. No primitive/semantic separation. No mode awareness. It worked well enough for a single-mode app living inside a larger shell.
Hardcoding values and branching on isDark inside every layout file is how design-system debt compounds. Dark mode becomes an audit, styles duplicate, and a brand change touches hundreds of call sites.
// ✕ Legacy — the component owns theme logic
import { useColorScheme } from "@/hooks/useColorScheme";
export function ConfirmButton() {
const isDark = useColorScheme() === "dark";
return (
<button
style={{
backgroundColor: isDark ? "#2A2342" : "#5D3EBC",
color: "#FFFFFF",
borderColor: isDark ? "#5D3EBC" : "#EAEAEA",
}}
>
Onayla
</button>
);
}Governance debt. A single color change (say, replacing brand purple) means editing every conditional style across the codebase.
The foundation
System Architecture
The foundation is a two-tier token system: primitives carry values, semantics carry intent.
Primitives are raw palette scales: purple-light.50, neutral.900, cool-gray.500. They don't carry meaning, just values.
Semantics follow a {property}/{context}/{variant} pattern: bg/surface/default, text/content/primary, border/input/default. They describe what a color does, not what it looks like.
The token collections are primitives, semantics, and [legacy]. The legacy collection is kept deliberately during migration: it lets 20 domains migrate incrementally instead of in one synchronized big-bang refactor.
Modes (light and dark) are resolved at the semantic layer. A semantic token's identity is stable; only its primitive binding changes per mode. The system spans three platforms: web, iOS, and Android.
Combine a category, concept, and role to build a semantic token, then inspect how it resolves in each mode.
Namespace isolation
bg/ai/*, text/ai/*, and icon/ai/* form an isolated namespace for AI surfaces. The rationale is architectural, not aesthetic. AI surfaces were sharing semantic tokens with error and success states. In light mode the collision was invisible. In dark mode it broke.
The dimensional layer, honestly stated
Spacing (9 steps: 0/4/8/12/16/24/32/48/64), size (10 steps, including a 2-step touch target scale), radius (8 steps), and breakpoint (3 steps) primitives are built. Semantics exist for color only; the dimensional primitives haven't been promoted into the semantic collection yet.
This is a deliberate sequencing decision: color was the layer under production pressure and the layer where a mistake is visible to every user. Ship the semantic layer where it's load-bearing, build primitives beneath the rest, and promote them once the naming has been proven by the color layer. The color system is the template: same two-tier shape, same slash-delimited naming.
Three decisions define this architecture
Brand invariance: bg/action/primary resolves to #5D3EBC in both modes. Brand is a mode-independent anchor. Most fintech dark modes oversaturate the brand color into large fills; here purple lives at the stroke, accent, and border layer.
Brand as surface
Vadeli hesap
₺100.000
bg/action/primary as surface
The same token used as a large fill. Legible, and it makes every screen a brand screen.
Brand as anchor
Vadeli hesap
₺100.000
bg/action/primary as action + border
Neutral surface, purple reserved for the action and the border it owns.
Status colors range-switch, they do not invert. green.50 in light mode becomes green.900 in dark mode: same semantic role, different scale position.
Positional equivalence
What “give us dark mode equivalents” produces: the light scale positions, unchanged, on the dark canvas.
Range switch
Same semantic roles, resolved into the 900s. Contained instead of glowing.
Keeping the legacy collection alive: deleting it would have forced a synchronized migration across 20 domains. Keeping it made migration incremental and reversible.
Chapter one
Dark Mode as the Forcing Function
The brief from product and engineering was direct: "give us dark mode equivalents of our colors." That framing is wrong. Dark mode is not an inversion of light mode. A 1:1 hex mapping breaks status colors, collapses elevation hierarchy, and produces surfaces that are technically darker and perceptually wrong. Resolving the framing before designing anything was the first real design decision.
The dark palette: canvas #0E0E0E, near-black with no blue bias and not pure black. Surfaces at #1A1A1A and #262626 in tight steps: flat and dense rather than heavily elevated. Status colors sit in the 900 range, muted, with no neon-on-black. Reference points: Apple HIG as primary, Revolut and iOS system apps for comparison.
Engineering's initial resistance was correct on its own terms. Refactoring 20 domains is expensive and the brief asked for hex values. What changed it was showing them the token system from their side: fewer conditionals, one source of truth per semantic role, cleaner handoff. They became advocates.
Boundary conditions
Where the Token System Ran Out
These are boundary conditions of a token architecture, not failures.
FX line chart: the SVG stroke was hardcoded in markup with a Display P3 fallback. The fill gradient required a conditional SwiftUI render path (an isDark environment check, separate gradient values per mode), not a token swap. The root cause: the token system is single-value. Data visualization needs ordered palettes, contrast guarantees, and multi-series logic. Colors designed for UI surfaces were reused for data. Charts were never modeled as a semantic domain.
Three more edges, each outside what tokens can reach:
- Google Maps runs on a separate JSON style file the token system doesn't reach. Purple was tried on base tile geometry and explicitly removed; the final version is neutral geometry with purple reserved for overlays and markers.
- Lottie color management sits outside the token system entirely: parallel animation files or layer-level overrides. Android constraint: layers nested inside precomps don't support runtime color override.
- Liquid glass on the home action grid was evaluated and rejected: contrast failure in dark mode, and it doesn't satisfy HIG intent for static containers.
bg/ai/primary in dark mode is #2B2438, a desaturated dark purple, not an inversion of the light value. Dark AI surfaces needed to feel contained and premium, not just dark.
Yatırım Dünyasında Yeni Eğilimler
Yapay zekânın portföy yönetimindeki yeri ve geleceği.
What it exposed
The Governance Failure Dark Mode Exposed
Core UI icons were exported as PNGs per domain and uploaded to separate Directus CDN buckets. No versioning, no registry, no token injection possible at runtime. Dark mode required a full parallel set of PNGs: the same icon existed across 6+ CDN locations, each needing a manual re-export.
A Slack tracking list (name, domain, CDN link, type, request owner, status) was the operational workaround. It documented the absence of a system, and that documentation became the business case for building one.
Evidence, not claims
Documentation as an Artifact of the System
gf-design-system.vercel.app is a Next.js documentation and education site, live and in active use by the team. The color section alone is 7 pages: introduction, primitives, semantics, reality, dark mode, overlay, and gradients.
The "reality" page documents the gap between the system as designed and the system as implemented in production right now. Most design systems hide this. Publishing it is what made the migration tractable.
The repo split (design system source and documentation, previously one monorepo) was a governance decision, not a technical one. The docs site consumes published token packages, so it cannot describe a token that doesn't ship.
Beyond the site: facilitated token usage sessions across the design team, wrote implementation guidelines, and educated designers on the two-tier model.
Semantics
{property}/{context}/{variant}. bg/surface/default, text/content/primary, border/input/default — what a color does, not how it looks.
Built on the system
Proof
The new bottom sheet, designed after the token system shipped, is the clearest proof of what the system enables. Every color decision is a semantic token reference: no hardcoded values, no one-off overrides. Light and dark are one design file with a mode toggle. That is the difference between adapting a product to dark mode and building a product that understands it.
Transfer İşlemi Başarılı
₺2.500,00 Ulaş Alyeşil hesabına gönderildi.
GetirFinans'ı seviyor musun?
Dürüst görüşün bizi daha iyi yapar.
Counted
Results
The foundation was replaced underneath a running product without a freeze, a big-bang refactor, or a rollback.
- Full migration
- 2 months
- Every sub-product, with the core dev team, ahead of the standalone app release
- Domains migrated
- 20
- All of them, not a partial rollout
- Platforms
- 3
- Web, iOS, Android
- Modes
- 2
- Resolved at the semantic layer
- Token collections
- 3
- Primitives, semantics, legacy
- Dimensional primitives
- 30
- 9 spacing, 10 size, 8 radius, 3 breakpoint
Shipped
- The token system is live in production across the getirfinans app, on all three platforms.
- The legacy color file is fully replaced. Accounts, loans, FX and cards all resolve through the semantic layer.
- The migration landed ahead of the standalone app release, which was the deadline that mattered.
- gf-design-system.vercel.app is live and in active use by design and engineering.
Adoption
- Every sub-product is on the semantic layer. There is no domain still reading raw hex.
- The design team was trained on the two-tier model through facilitated token sessions.
- Engineering went from resisting the refactor to advocating for it, and now files token requests rather than hardcoding around them.
What it changed beyond the system
- getirfinans.com was redesigned end to end on the new visual language.
- A formal token governance process exists where there was none.
- The SVG icon migration was approved and prioritized, which is the fix for the CDN sprawl dark mode exposed.
- The monorepo split into a source repo and a documentation repo, so the docs cannot describe a token that does not ship.
- Design system deep dives now run after each sprint.
Roadmap
What's Next
- 01Semantic dimensional layer: promote spacing, radius, and size primitives into semantics using the existing names. Don't redesign the ramp. Publish.
- 02Component tokens: no component tier exists today. It's the layer above semantics and it isn't built yet.
- 03Icon migration: PNG to SVG, killing CDN bucket sprawl and unlocking runtime theming.
- 04Data visualization tokens: charts need a modeled semantic domain (ordered palettes, contrast guarantees, multi-series logic). The FX chart is the open ticket.
- 05Component library: downstream of all of the above. It doesn't start before the foundation carries weight.
Retrospective
Reflection
What worked
Sequencing color first: the layer under production pressure, the layer where errors are visible to every user, and the naming template for everything after it. Publishing the gap: the reality page and the Slack tracking list both documented the distance between design and implementation, and making the debt visible got it funded. Isolating the AI namespace early rather than redefining shared tokens under pressure. Bringing engineering in by showing them the system from their side.
What I'd do differently
Audit color usage in context before designing anything. Elevation problems surfaced during implementation instead of before it; a screen-by-screen audit of how colors were actually used, not how they were named, would have caught them. Model charts as a semantic domain from the start rather than reusing surface colors for data. Finish the dimensional semantic layer alongside color rather than leaving primitives promoted but unmapped. The gap is defensible. It was still avoidable.
I rebuilt the color foundation of a live fintech product, solo, while convincing the team that the problem was never dark mode. It was the system underneath.