← Back to Blog

Building a Design System That Rivals Apple

8 min read
by Alex
Category:Design
design-systemCSSTypeScriptworkflowautomation

Building a Design System That Rivals Apple

When I started Studio89, I knew I wanted a professional, cohesive design system. What I didn't know was how deep the rabbit hole goes. Here's how I built a complete design system from scratch—and the non-obvious lessons I learned along the way.

The Problem

My portfolio had grown organically:

  • Colors chosen ad-hoc per project
  • Inconsistent button styles
  • Manual class string building
  • No validation or enforcement
  • 117+ violations scattered across the codebase

It looked okay, but it didn't look professional.

The Vision

I wanted a system that:

  • Rivals Apple, Vercel, and Stripe in quality
  • Maintains 100% consistency
  • Prevents violations automatically
  • Works for both global UI and project-specific contexts
  • Scales from mobile to desktop seamlessly

The 6-Color System

The Insight

Most design systems use one primary color. But I had a unique challenge: global UI (navigation, footer) needed to stay consistent, while project pages needed distinct category identities.

Solution: 6 colors total

  • 1 Primary: Electric Lime (#CCFF00) for global UI
  • 5 Categories: Purple (AI), Cyan (Code), Orange (Hardware), Red (Art), Pink (Design)

This separation prevents color confusion and maintains clear visual hierarchy.

Why OKLCH?

I integrated with an existing depth system that uses OKLCH color space. Benefits:

  • Perceptually uniform (unlike RGB/HEX)
  • Better for gradients and shadows
  • Future-proof color specification

All 6 colors available in both HEX and OKLCH.

Component Patterns

The Factory Pattern

Instead of manual class strings, I built a factory:

typescript
// ❌ Before: Easy to mess up
<button className="button button-category button-category-ai">

// ✅ After: Impossible to mess up
<button className={HouseStyle.button.category('AI')}>

The factory guarantees complete class combinations. No more incomplete patterns.

Tags vs Badges

Here's a subtle but important distinction I discovered:

Badges (for classification):

  • Pill-shaped, UPPERCASE
  • 15% opacity backgrounds
  • More prominent

Tags (for keywords):

  • Rounded corners, normal case
  • 10% opacity backgrounds
  • More subtle, hover glows

Different visual weight for different purposes. See them side-by-side on the tag test page.

Automation That Saves Time

Pre-Commit Validation

Built a validation script that runs on every commit:

bash
pnpm validate:house-style

It catches:

  • Incomplete class patterns
  • Inline style overrides
  • Manual class building
  • Deprecated functions

Result: 0 violations can slip through to production.

The Script

javascript
// Detects incomplete patterns
pattern: /className="([^"]*\bbadge\b(?!\s+badge-(primary|category))[^"]*)"/g;
message: "Incomplete badge class - missing pattern modifier";

Scans 115+ files in seconds. Blocks commits if violations found.

Non-Obvious Lessons

1. Audit Before Fixing

I started updating pages one-by-one. Then I found 117+ violations buried in the codebase.

Lesson: Stop. Audit EVERYTHING first. Fix systematically. Prevent rework.

2. Test Pages Are Reference Implementations

I created test pages (button-test, badge-test, etc.) early. They became the source of truth for correct implementation.

When production pages looked different, I knew immediately: test page is right, production page is wrong.

Lesson: Build reference implementations first. Compare everything against them.

3. Separate Archive from Worklist

My task file grew to 1,100+ lines with history, decisions, and rationale. Valuable, but hard to navigate.

Solution:

  • v1 (archive): Complete history, decisions, lessons learned
  • v2 (worklist): Clean 255-line list of remaining tasks

Both serve important purposes. Archive for context, worklist for execution.

4. Document Decisions

When someone asks "why did you do it this way?", having documented rationale saves hours of explanation.

Example: Why factory pattern over utility classes? Documented in v1 with full reasoning.

5. Non-Linear Execution Is Faster

My phases went: 1-5 → 6 (partial) → 10 (audit) → 10.5 (comparison) → back to 6.

Why jump around? Found violations during phase 6. Better to fix ALL issues once than discover them page-by-page.

Result: Less total work, higher quality.

The Numbers

Coverage:

  • 115 files scanned
  • 0 critical errors
  • 100% compliance

Performance:

  • < 15KB CSS overhead
  • 60fps animations
  • Zero console errors

Accessibility:

  • WCAG AA compliant
  • 13.7:1 contrast (AAA) for Electric Lime
  • Keyboard navigation throughout

Explore The System

Test Pages:

  • Buttons - Primary, secondary, category patterns
  • Cards - Base and category-accent cards
  • Badges - Classification labels
  • Tags - Keyword tags (NEW!)
  • Inputs - Form elements
  • Shadows - Depth and glow effects
  • Animations - Timing and easing

Project Page:

What's Next

The system is production-ready, but there's always room for improvement:

  • Additional patterns (modals, dropdowns, tooltips)
  • Storybook integration for component documentation
  • Visual regression testing
  • Component usage analytics
  • Expanded animation library

Takeaway

Building a design system is hard. But the payoff is worth it:

  • Professional, cohesive appearance
  • Faster development (factory pattern)
  • Zero violations (automation)
  • Clear documentation
  • Maintainable codebase

If you're building a portfolio or product, invest in your design system early. Your future self will thank you.


Want to learn more? Check out the full project page or explore the test pages to see every pattern in action.

Found this helpful? Share it with others!

Share: