Case Study

Portfolio design system

RoleUX/UI Designer
IndustryDesign
Date2026
OutcomeScalable design system

The 30-Second Version

A quick note before you keep scrolling

As the portfolio grew, inconsistencies started creeping into new components, sections, and pages. So I built a design system for it: a four layer architecture running from tokens to primitives to composite components to pages, with clear rules for when something earns full component status instead of staying a one-off. It's a living system, with a changelog tracking every update as the portfolio keeps evolving.

I Owned

  • Design tokens & typography scale
  • Component architecture (primitive to page)
  • Color, spacing & motion systems
  • Changelog & ongoing documentation

Solo project

  • Designed, built, and maintained end to end

While working on my portfolio, I noticed something I’ve experienced many times when working on projects that need to scale. As I kept building new components, sections, and pages, I started to notice inconsistencies creeping in. That’s when I realized I needed to create a design system for my own portfolio.

And this is it.

This is a living design system, so it’s continuously evolving as I make updates to the portfolio. At the bottom of the page, you’ll find a change log documenting the updates I make over time.

You can explore the full design system here and see how I approach building a system from scratch, while keeping it scalable and consistent as the product grows.

How this system is layered

The architecture follows a 4-layer hierarchy mapped directly to the codebase structure. Each layer builds upon the layer directly before it:

01
Design Tokens
02
Primitive Components
03
Composite Components
04
Pages

How I decide what gets added

As a solo designer and developer, I maintain a streamlined workflow. Elements qualify for full component status based on clear reuse criteria, keeping the library lean, functional, and highly effective.

1Check whether an existing token or component already solves it.
2If something close already exists, extend that pattern instead of starting fresh.
3Only create something new if neither of the above works.
4Document it and log the change in the changelog below.

Portfolio design system

H1
clamp(34px, 6cqw, 64px) · Weight 300
I craft digital stories through UX design
H2
clamp(24px, 4cqw, 32px) · Weight 300
Featured Case Studies
H3
clamp(20px, 3cqw, 24px) · Weight 300
Design Systems, Built to Scale
H4
clamp(16px, 2.5cqw, 18px) · Weight 300
ProtoPie Website Redesign
SUBHEADING
18px · Weight 400
Portfolio Design System — Foundations
SUB
16px · Weight 400
10+ years across AAA games and SaaS product design.
BODYS
14px · Weight 400
Bringing game-industry craft to complex digital experiences.
BODYXS
12px · Weight 400
Last updated August 2026
LABEL
14px · Weight 300
Case Study
EYEBROW
13px · Weight 600
Aa Visual Framework Spec
STAT
40px · Weight 300
200+
BUTTON
18px · Weight 300
View Case Studies
CAPTION
11px · Weight 400
Aa Visual Framework Spec
OVERLINE
11px · Weight 600
Aa Visual Framework Spec
MONOLABEL
13px · Weight 600
Aa Visual Framework Spec
MONOVALUE
12px · Weight 400
Aa Visual Framework Spec
CARDTITLE
14px · Weight 600
Aa Visual Framework Spec
EMPHASIS
16px · Weight 500
Aa Visual Framework Spec
EMPHASISLARGE
18px · Weight 500
Aa Visual Framework Spec

Brand & Semantics

Brand Primary Scale
AA
Primary 100
#EDF3EE
AA
Primary 200
#DCE7DE
AA
Primary 300
#C5D5C8
Fail
Primary 400
#A4BEA8
Fail
Primary 500
#7F9B80
AA
Primary 600
#5C7960
AA
Primary 700
#4E6B53
AA
Primary 800
#3E5644
AA
Primary 900
#2B3E31
Brand Accent Set
AA
Accent default
#6A4C7A
AA
Accent soft
#EDE7F0
AA
Accent strong
#4B3A63
AA
Accent subtle
#D8CEE0
AA
Accent faint
#F8F5F9
Semantic System Tones (Base)
AA
error Base
#C05050
AA
info Base
#56789A
AA
success Base
#47805F
AA
warning Base
#9C6A19
Primary Button Text
Used for Primary Button Fill
Preview Text Sample
4.82 : 1
WCAG AA Pass
Link Text
Used for Links / Nav
Preview Text Sample
4.64 : 1
WCAG AA Pass
Primary Text on Default Background
Used for Primary Body Copy
Preview Text Sample
8.41 : 1
WCAG AA Pass
Primary Text on Subtle Background
Used for Primary Body Copy
Preview Text Sample
7.95 : 1
WCAG AA Pass
Secondary Text on Default Background
Used for Secondary Body Copy
Preview Text Sample
16.78 : 1
WCAG AA Pass
Secondary Text on Subtle Background
Used for Secondary Body Copy
Preview Text Sample
15.86 : 1
WCAG AA Pass
Semantic Alert: Error
Used for Error Fill
Preview Text Sample
4.66 : 1
WCAG AA Pass
Semantic Alert: Info
Used for Info Fill
Preview Text Sample
4.62 : 1
WCAG AA Pass
Semantic Alert: Success
Used for Success Fill
Preview Text Sample
4.65 : 1
WCAG AA Pass
Semantic Alert: Warning
Used for Warning Fill
Preview Text Sample
4.68 : 1
WCAG AA Pass
default
hover
focus
pressed
disabled
loading
Primary
Contact
Contact
Contact
Contact
Contact
Contact
Secondary
Contact
Contact
Contact
Contact
Contact
Contact
Thanks for reaching out. Talk soon 🙂
Oops! Something didn't work as expected.
Success / error feedback replaces button content in place rather than opening a separate alert.
Passwordgate.tsx, MediaSlider.tsx, ProjectCard.tsx, Footer.tsx, VideoSlider.tsx, Navigation.tsx, and 4 more — the shared hover/interaction curve across the site.
User researchJourney mappingInformation architectureInteraction designAccessibility (WCAG)Usability testing
Status variants (semantic tint, no border)
BlockedIn reviewShippedAt risk
Brand-tint variants (two strengths — status badge vs. dense metadata row)
Fixed
Soft Sageprimary/100 → background/subtle → background/default
Sage Depthprimary/200 → primary/700
Duotone Accentprimary/600 → text/muted → accent/default
Deep Inkprimary/900 → background/inverse
Sage Depth (Subtle)primary/300 → primary/500
Accent Washaccent/faint → accent/strong
Whisperprimary/100 → background/default
Corner Driftprimary/100 → background/default
Dual Glowprimary/500 + accent/default, low opacity
Hushbackground/subtle → background/default
Accent Whisperaccent/soft → background/default
Four Cornersprimary/500 + accent/default, 4 corners, solid center
Drift (animated)primary/100 → background/default, drifts over 26s
Breathe (animated)primary/500 + accent/default, drift over 20s
Sage & Creamprimary/400, /600, /100 + grain
Sage & Accentprimary/500, accent/subtle, accent/default + grain
Single Glowprimary/400 + grain
Vertical Hushprimary/100 → background/subtle → primary/200
Vertical Hush — Accentaccent/faint → accent/soft → accent/subtle
GradientBackground.tsx — the film-grain texture layer paired with gradients.organic.*, toggled by its showGrainOverlay prop.
space-1 (xs)
4px
Aa
Tightest step — fine visual nudges and the smallest gaps between adjacent elements.
space-2 (sm)
8px
Aa
Compact inline gaps and tight padding.
space-3
12px
Aa
Compact padding and control-scale gaps — the step reached for most often across the site.
space-4 (md)
16px
Aa
Default spacing step — standard padding, paragraph gaps, and the most common vertical rhythm between stacked elements.
space-6
24px
Aa
Card-level padding and generous section-internal spacing.
space-8 (lg)
32px
Aa
Loose stack gaps and section-scale padding.
space-10
40px
Aa
Nav-bar and mid-size layout gaps.
space-12 (xl)
48px
Aa
Hero-scale inset padding.
space-16 (2xl)
64px
Aa
Large section-level gaps and big component padding (e.g. Footer's outer padding).
Card Shaperadius: 8px · elevation: card
Container Gridradius: 12px
Modal Viewradius: 12px · elevation: modal
Glowelevation: glow (dark surfaces only)
Compact Rowradius: 4px
Nav / Mediaradius: 16px
Status Pillradius: 12px
radius: 999px
Surface
surface.primary
#FAFBFA
surface.subtle
#F2F5F3
surface.inverse
#141414
Text
text.primary
#4B4B4B
text.secondary
#1A1A1A
text.muted
#6E6E6E
text.onDark
#FFFFFF
text.onSemantic
#FFFFFF
text.disabled
#A0A0A0
Border
border.default
#C2D2C5
border.active
#5C7960
border.strong
#4E6B53
Action
action.primary
#5C7960
action.primaryHover
#4E6B53
action.primaryPressed
#3E5644
action.accent
#6A4C7A
action.disabled
rgba(75, 75, 75, 0.12)
Primitive
brand.primary[600] (#5C7960)
#5C7960
→
Semantic
action.primary
→
Used in
Primary Button fill (this spec's Buttons section), EA_Play_Table.tsx header color
Primitive
text.secondary (#1A1A1A)
#1A1A1A
→
Semantic
text.secondary
→
Used in
Hero.tsx headline color
Primitive
border.default (#C2D2C5)
#C2D2C5
→
Semantic
border.default
→
Used in
EA_Play_Table.tsx divider color, shared --color-border-default
surface.primary#FAFBFA
surface.inverse#141414
text.secondary#1A1A1A
text.onSemantic#FFFFFF
border.active#5C7960
action.primaryHover#4E6B53
mobile
375px
tablet
768px
desktop
1024px
wide
1440px
sm · 16px
md · 20px
lg · 24px
Focusable element
focusRing: 2px solid #6A4C7A
focusOffset: 2px
inline
sm
8px
Horizontal gap between a small decorative element (an icon, a rule) and its adjacent text label — e.g. the eyebrow row above a page heading. Used in ProtoPie_Component_IA.tsx, Hero.tsx, CaseStudyHeader.tsx, IndexHeader.tsx, AboutMeHeader.tsx, ContactHeader.tsx, CaptionedGallery.tsx, SharedProcessCTA.tsx.
md
16px
Horizontal gap between adjacent interactive controls, like a header's paired CTA buttons. Used in Hero.tsx, AboutMeHeader.tsx, ContactHeader.tsx.
stack
default
16px
Vertical gap between related stacked text elements, like an eyebrow label and the heading directly below it. Used in Hero.tsx, CaseStudyHeader.tsx, IndexHeader.tsx, AboutMeHeader.tsx, ContactHeader.tsx, CaptionedGallery.tsx, FeaturedWorkSection.tsx.

Changelog

31 updates

Create a free website with Framer, the website builder loved by startups, designers and agencies.