← Ankit Kumar
GGoodScore

GoodScore Design System

A unified system of foundations and components for a fintech app with 100+ screens. Designed and built solo in three weeks.

  • 138 primitives
  • 61 semantic tokens
  • 21 text styles
  • 37 components
  • 454 variants
  • 132 icons
Foundations

Colour system

A flexible palette of primitive and semantic colours that keeps every screen consistent, accessible and recognisably GoodScore.

01

The challenge

Colour had grown screen by screen. Nobody could say which blue was “the” blue, or whether text on it was readable.

79 colours on 8 screensNear-duplicates everywhere, picked one screen at a time.
1.85 – 10.83 : 1Step 600 had a different strength in every hue. Swapping colours broke buttons.
Pay now3.3 : 1 · Fails
The brand blue failed WCAG#008EFE with white text reached only 3.3 : 1 on our main “Pay now” button.
02

Tests we ran

Every round had to survive four tests on real components and screens before moving on.

03

The system

Two layers. Primitives hold raw values; semantic tokens give each colour a job. Components only ever use the job.

How a colour reaches a button
PRIMITIVE
blue/600
#116DF1
SEMANTIC
bg/brand/default
“Primary action”
Pay now
COMPONENT
Button · Primary
Uses the job, never the hex
Brand blue pinned

#008EFE lives at blue/500, untouched. Buttons use 600, links 700.

One lightness curve

Every hue shares it, so any step means the same strength.

usage / role / type

text/neutral/primary · bg/danger/subtle · border/brand/default

One status, one meaning

Green done · yellow attention · orange warning · red risk. Blue is never a status.

04

Full scope

Everything that shipped: 9 ramps × 15 steps, 61 semantic tokens and a contrast check for every pairing.

Primitive colours
Semantic tokens · 61
Colour contrast check
Status colours
Foundations

Typography

Text styles as design tokens: one typeface, three weights and twenty-one styles, each named for the job it does.

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789  ₹1,95,000
400 · Reading600 · Interface700 · Emphasis
Body/LGPay before 5 Nov to keep your score healthy.
Body/MDPay before 5 Nov to keep your score healthy.
Body/SMPay before 5 Nov to keep your score healthy.
Label/MDPay before 5 Nov to keep your score healthy.
CaptionPay before 5 Nov to keep your score healthy.
Text styles
› Display3
› Heading5
› Body3
› Label4
› Overline1
› Caption1
› Number4
Total due₹78,876Number/XL · tabular figures
01

The challenge

The old styles had been converted, copied and renamed until nobody trusted them.

Display/Large43.2 px
Heading/X Large31.68 px
Heading/Medium21.12 px
Body/Medium20.58 px
13 styles, no rulesSizes like 31.68 and 21.12 px, converted from another unit and never rounded.
Lable/LargeLable/MediumLable/Small
Names like “Lable”Typos in style names made the right style hard to find and easy to misuse.
₹15,000Total due
No bold for moneyIn a credit app the amount is the hero, but nothing made numbers stand out.
02

Tests we ran

Five tests on real text and the live library. Two of them found things I still had to fix.

TEST 1

Line heights on the grid

FOUND13 of 13 old line heights were off the 4 px grid (43.2, 31.68, 20.58…).
CHANGEDRebuilt every line height as a multiple of 4.
RESULT0 of 21 off-grid
Before · 13 styles
After · 21 styles
Payment summary
Pay before 5 Nov to keep your score healthy.
₹12,550
Heading (28) · Body (20) · Number (36): every line lands on the grid.
TEST 2

Type × colour, every pair

FOUNDRan every text colour on every background: 81 pairs. Tertiary text dropped to 4.27–4.29 : 1 on tints.
CHANGEDTertiary text is now allowed on white surfaces only.
RESULT8 of 81 failed, all tertiary on tints. The other 73 pass.
Below 4.5 : 1 · computed from live token values
TEST 3

Tracking by size

FOUNDDisplay text felt loose; small caps felt cramped.
CHANGEDA tracking curve: +0.5% at 11 px to −2.2% at 32 px and above; Overline caps +6%.
RESULTTight headlines, open small text
₹8,96,835
0% tracking
₹8,96,835
−2.2% · the system
PAYMENT SUMMARYOverline: the only caps style, +6%
TEST 4

Library usage audit

FOUND493 text layers across the component library; 1 had no text style.
CHANGEDFlagged the unstyled layer for a fix. Kept unused display styles for screens and hero moments.
RESULT492 / 493 styled · 17 of 21 styles in use
TEST 5

47 real texts

FOUNDLabels do most of the work; some text had no obvious home.
CHANGEDAdjusted the scale until everything fit, without adding styles “just in case”.
RESULT47 texts · 12 styles · 0 without a home
47
texts from live screens
12
styles they needed
0
left without a style
03

The system

Kept Inter. Rebuilt everything around it.

Aa
400
Aa
600
Aa
700

Three weights, three jobs

400 for reading, 600 for interface, 700 for emphasis and amounts.

Payment summaryPay before 5 Nov to keep your score healthy.Pay before 5 Nov to keep your score healthy.Pay before 5 Nov to keep your score healthy.Pay before 5 Nov to keep your score healthy.

Every line on a 4 px grid

Line heights 16 · 20 · 24 · 28 · 32 · 36 · 40 · 48 · 56. Text lines up with spacing.

₹78,876
₹12,550
₹3,200

A Number group for money

Tabular figures, so amounts line up on every screen.

Lable/Large→Label/LG
Heading/X Large→Heading/XL
12/Auto→Body/SM

Named by job

Role/Size, readable in any Figma panel.

10 px11 px · smallest allowedPAYMENT SUMMARY

11 px minimum

10 px removed. Caps only for Overline, at +6% tracking.

04

Full scope

All 21 styles, each a design token with size, line height, weight and tracking.

Foundations

Spacing & sizing

One 4 px scale for every gap, and size tokens so a 48 px tap area is never a guess.

Spacing scale
Spacing on a real card
HDFC Credit Card
•••• 4321
₹XX,XXX
HDFC
EMI on 5 Nov
₹X,XXX
16 12
01

The challenge

Every gap looked fine on its own. Side by side, screens felt slightly off.

10, 12, 16 and 17 pxNear-identical gaps sitting next to each other on the same screen.
150gaps of 10 & 17 px
150 Figma defaultsEven our own new component file had 150 gaps of 10 and 17 px.
11 ad-hoc valuesIncluding 6, 10 and 98 px. No scale, no rule.
02

Tests we ran

Measured on the live library, not eyeballed.

TEST 1

Adoption audit

FOUND2,480 gaps and paddings in the library. 522 not linked to a token; 86 off the scale.
CHANGEDSnapped 150 Figma-default gaps (10 and 17 px) to the scale. The rest are queued.
RESULT79% linked · 97% on the 4 px scale
Every gap and padding value, counted
On the scale Off the scale
Linked to a spacing token
1,958 linked · 522 typed by hand
TEST 2

Breaking the margin

FOUNDEdge-to-edge content kept creeping in.
CHANGEDOnly 3 cases may break the 16 px margin.
RESULT16 px from the edge, everywhere else
TEST 3

Touch targets

FOUNDMeasured every tappable component: 8 of 10 draw smaller than 48 px.
CHANGEDThe glyph can be small; the tap area is padded to 48 × 48.
RESULTEvery target 48 × 48 · Android’s 48, not Apple’s 44
Drawn height vs the 48 px target
48 px or taller Drawn smaller 48 px tap area
03

The system

Closer means related. Distance shows structure.

Proximity
Payment summary
4
Due on 5 Nov
16
₹12,550
32
Next section
Layout rules
360 px screen
16 · 328 · 16
04

Full scope

12 spacing tokens and the size tokens components share.

Foundations

Corner radius

Six sizes that grow with the element: crisp small controls, soft large containers.

01

The challenge

19 different corner radii, from 1 px to 678 px, chosen screen by screen.

02

Tests we ran

TEST 1

Token adoption

FOUND793 rounded layers; 85 still had a typed-in radius.
CHANGEDLinked radius to tokens across components.
RESULT708 / 793 on tokens (89%)
Every corner radius in the library
708 linked to a radius token · 85 typed (mostly 54 px lender-logo artwork)
TEST 2

Nested corners

FOUND42 nested pairs broke inner = outer − padding. All were the Focus states, built from a separate ring layer (outer 12 around an inner 16).
CHANGEDRebuilt Focus as a focus-ring effect style that follows the button’s own radius. Re-ran the check.
RESULT0 breaks after the fix (was 42)
TEST 3

Component fit

FOUNDOne radius looked wrong on both tiny tags and big sheets.
CHANGEDRadius grows with the element’s size.
RESULT4 on tags → 24 on sheets
Paid
radius 4
24 px tall
Cards
radius 8
32 px
Pay now
radius 12
44 px
HDFC Card
₹12,550
radius 16
cards
Session expired

Log in again to continue.

Log in
radius 24
sheets, modals
smallerlarger → rounder
03

The system

Bigger thing, rounder corner. Inner radius is outer minus padding.

04

Full scope

Six tokens, and the components that use each one.

Foundations

Elevation

Shadows that show what sits on top of what. Six levels, nine roles, one shadow colour.

Level 1
Level 2
Level 3
Level 4
01

The challenge

Shadows were picked by eye, in pure black, and tested on white.

Invisible on whiteFirst shadows looked like nothing on the white canvas.
Black looked dirtyPure black at low opacity greyed out the colours underneath.
{shadow-color.key}→ unresolved
Links to nothingAfter import, shadow tokens pointed at references that didn’t exist.
02

Tests we ran

Every shadow tested where it will actually live: on real colours and real components.

TEST 1

Token import

FOUNDAll tokens pasted into one set; every reference pointed at a path that no longer existed.
CHANGEDRebuilt as one self-contained set with short reference paths.
RESULTEvery reference resolves
BEFORE · ONE PASTED SETglobalprimitivesshadow-colorsemanticelevation/1{primitives.shadow-color.key} ✕elevation/2{primitives.shadow-color.ambient} ✕
→
AFTER · SELF-CONTAINED SETgoodscore-shadowsshadow-color.key ✓shadow-color.ambient ✓elevation/1{shadow-color.key} ✓ resolveselevation/2{shadow-color.ambient} ✓ resolves
TEST 2

Real page colour

FOUNDSoft shadows looked invisible on the white test canvas.
CHANGEDEvery shadow now judged on the app’s real page colour.
RESULTNever judge a shadow on white
TEST 3

Tinted vs black

FOUNDPure black at 35% greyed out coloured surfaces and looked like dirt.
CHANGEDShadow colour tinted slate, rgb(16, 24, 40), in two soft layers.
RESULTShadows feel part of the palette
TEST 4

Effect-style audit

FOUND28 layers with a shadow across the library.
CHANGEDEvery shadow applied through a named effect style; raw shadows replaced.
RESULT28 / 28 via effect styles
03

The system

Two layers per level: a tight key shadow for the edge, a wide ambient one for lift. Components use roles, never levels.

Key + ambient
Key
Ambient
Together
Bottom UI casts upward
Total due
₹78,876
Pay now

Sticky CTA bar · bottom nav · bottom sheet

04

Full scope

Six levels and the nine roles that point at them.

Foundations

132 icons

Material Symbols, the set Android users already know. Four sizes, colour from text tokens.

credit_score
01

The challenge

Icons were imported one by one, at random sizes and colours.

arrow_backarrow_backcheck_boxcheck_box
Duplicatesarrow-back and check-box existed twice in the library.
chevron_leftchevron_leftchevron_left
Hand-scaled sizes26, 30, 22 px icons side by side; strokes went soft.
arrow_downwardarrow_downwardarrow_downward
Colour as decorationPurple and teal icons with no meaning.
02

Tests we ran

03

The system

Familiar over clever. Icons support words. Colour comes from the text beside it.

Four sizes
credit_score16 px
credit_score20 px
credit_score24 px
credit_score32 px
Tap area ≠ glyph
credit_score

24 px glyph · 48 px target

Colour from text
arrow_forward Primary · next to titlesarrow_forward Secondary · rowsarrow_forward Brand · linksarrow_forward Success · status onlyarrow_forward Danger · status only
04

Full scope

Every icon in the library, named to match Material Symbols so developers find the same one in code.

Components

Simple components

The building blocks: buttons, inputs, badges, chips and toggles. Small, strict, and reused everywhere.

Buttons

Six types, three sizes, five states. One component, 90 variants. Blue only ever means “do something”.

arrow_backPay nowarrow_forward
Button ▾
LabelPay now
Show leading icon
Show trailing icon
TypePrimary ⌄
SizeSM ⌄
StateDefault ⌄

States & variants

Every type in every state. Pressed and Focus were added after review.

Inputs

Text, amount and OTP fields. Money gets its own field: ₹ prefix, Indian grouping and an optional period suffix.

₹ 85,000/ month
After tax
Amount field ▾ more_horiz
LabelMonthly income
Prefix₹
HelperWe’ll send an
Show helper
Value85,000
Suffix/ month
Show suffix
Show prefix
StateDefault ⌄

Badges

Six statuses, each with exactly one meaning. Subtle for lists and cards; Expressive for the one status that matters most.

And the rest

Small parts, built in full: every variant, state and property, with one rule each.

Components

Complex components

Rows, cards, app bars, sheets and modals. Built from the simple parts, so a fix in one place reaches every screen.

01

The challenge

The audit found the same jobs drawn many different ways, each one made for a single screen.

27 BILL-CARD VARIANTS27 bill-card variants from the audit
→
1 CONFIGURABLE CARD
~20–27 LIST-ITEM DESIGNSAbout 20 to 27 list-item designs from the audit
→
3 COMPONENTS
LIST ROW
HDFC Credit Card
•••• 4321
₹XX,550
scheduleDue in 3 days
DETAIL ROW
Total bill₹45,000 ₹44,550
TIMELINE ITEM
check_circle
Payment received
3 Oct, 7:19 PM
02

Tests we ran

A component was done only when it rebuilt a real screen from the audit, with no detached layers and no raw values.

03

The system

Flexibility from properties and slots, not from more components.

One List row · six trailing options
List row ▾ more_horiz
Subtitle•••• 4321
TitleHDFC Credit Ca
Value₹12,550
Show subtitle
Show avatar
Show value
Show chevron
Show tag
Show icon
TrailingValue ⌄
ContextScreen ⌄
Card anatomy

1Top strip · optional status

2Content slot · any rows

3Bottom strip · optional note

Cards

One Card shell with two slots, four appearances and optional status strips. Every product card is built on it, so they all share the same edges, padding and elevation.

8components
40variants
4appearances
Four appearances
04

Full scope

Every component set in the library, with its variant count.

Outcome

Shared infrastructure

From isolated decisions to one language for design and engineering.

Designers

Reusing components in new work

Engineering

Building the library and tokens into code

Thank you.