All chapters Part III · Design
Chapter 12

UI Design and Design Systems

You do not need to be a designer to ship an interface that looks designed. You need a small set of constraints, applied without exception. Most amateur UI isn't ugly because of bad taste, it's inconsistent, and inconsistency is what reads as cheap.


The concept

Professional-looking UI comes from constraint, not talent. Pick a small number of values for each dimension and never use anything else:

DimensionAmateurProfessional
Spacing13px, 17px, 22px, whatever looked rightA scale: 4, 8, 12, 16, 24, 32, 48, 64
Type sizes9 arbitrary sizes5-7 steps on a ratio
Colours30 hexes8-12 semantic tokens
Corner radii4 different values2-3
ShadowsRandom blur values3 elevation levels
Fonts3-4 families1-2

The constraint is the craft. A limited palette applied consistently looks intentional; an unlimited one applied by feel looks accidental.

The four systems

1. Spacing, an 8-point scale. 4, 8, 12, 16, 24, 32, 48, 64. Every margin and padding comes from this list. It's the single highest-impact change most amateur UIs can make, because inconsistent spacing is what the eye reads as "unfinished."

Related items sit close; unrelated items sit far apart. Proximity communicates grouping more strongly than borders or boxes do, which is why heavy use of cards is often a symptom of bad spacing rather than good structure.

2. Type, a modular scale. Pick a ratio (1.25 or 1.333) and generate steps: 12, 14, 16, 20, 25, 31, 39. Body text at 16px minimum on mobile (below that iOS auto-zooms on inputs). Line height 1.5-1.7 for body. Line length 65-75 characters, longer and the eye loses its place.

Two fonts maximum, and pair on a contrast axis (serif + sans, geometric + humanist) rather than two similar sans-serifs, which reads as a mistake. A single family in multiple weights is often stronger than a timid pair.

3. Colour, semantic tokens, not raw hex. Define meaning, not appearance:

--bg          --surface       --border
--text        --text-muted    --text-inverse
--primary     --primary-hover
--success     --warning       --danger

Then only use tokens in components. This is what makes theming, dark mode, and rebrands possible instead of a search-and-replace nightmare.

Contrast is a hard floor, not a preference: 4.5:1 for body text, 3:1 for large text and meaningful UI elements. The most common failure in AI-generated and amateur designs is muted grey text on a tinted background, it looks elegant in isolation and is unreadable in use.

4. Elevation, three levels. Flat, raised (cards), floating (modals, menus). Each with one defined shadow. Don't invent a fourth.

Dark mode is not inversion

If you support it, design it. Pure black (#000) causes smearing on OLED and harsh contrast; use a very dark neutral instead. Desaturate accent colours, a colour that pops on white will vibrate on dark. Check contrast independently in both themes; values that pass in light routinely fail in dark.

When to build a design system

Not on day one. The sequence that works:

  1. Build 3-4 real screens with hard-coded values
  2. Notice what repeats
  3. Extract tokens and components from what you actually built
  4. Refactor the screens to use them

Building a system before you know your patterns is speculative work that you'll throw away.

The AI-slop tells

Design generated without judgment has recognisable fingerprints. Avoid:

None are wrong once, deliberately. All are tells when they're the default.


📐 Best practice

Define tokens on day one, even if there are only twelve of them. Retrofitting is painful.

Use a component library and restyle it. shadcn/ui, Radix, Mantine, or the platform's native components. You get accessibility, keyboard handling, and edge cases for free, and those are the parts that take longest to get right.

Steal structure, not pixels. Study products you admire for hierarchy and density, then apply your own tokens.

Use SVG icons from one set. Lucide, Phosphor, Heroicons. One family, one stroke weight.

Test at the extremes: longest realistic string, zero items, 1,000 items, smallest screen, largest system font.

Check contrast with a tool, not your eyes. Your eyes are wrong, especially on dark backgrounds.

Reuse before you create. Every new component is maintenance forever.


💀 Common mistakes

Arbitrary spacing. The clearest amateur signal there is.

Too many fonts, too many weights.

Low-contrast "elegant" text. Fails accessibility and annoys everyone over 35.

Raw hex values in components. Kills theming permanently.

Pure black or pure white backgrounds. #000 smears on OLED; #fff is harsh. Use near-black and near-white.

Cards for everything. Cards are the lazy grouping answer. Nested cards are always wrong.

Inconsistent corner radii across components.

Designing only with perfect data. Short names, square images, three items.

Copying a whole aesthetic rather than adapting its structure.

Building a design system before you have patterns.


The professional workflow

 1. TOKENS FIRST — spacing scale, type scale, 8-12 colours,
    2-3 radii, 3 shadows. One file.
 2. PICK A COMPONENT LIBRARY. Restyle with your tokens.
 3. BUILD 3-4 REAL SCREENS with real content.
 4. EXTRACT what repeats into components.
 5. STATES for every component: default, hover, focus, active,
    disabled, loading, error.
 6. RESPONSIVE: check 375 / 768 / 1280 / 1440.
 7. DARK MODE (if supported): design it, don't invert it.
 8. ACCESSIBILITY: contrast both themes, focus rings, 44pt targets.
 9. EXTREME-CONTENT PASS: longest string, zero items, 1000 items.

Tools, websites & costs

NeedToolCost
DesignFigma, PenpotFree tiers
Components (web)shadcn/ui, Radix, MantineFree
Components (mobile)Native SDK, React Native Paper, TamaguiFree
IconsLucide, Phosphor, HeroiconsFree
FontsGoogle Fonts, FontshareFree
PalettesCoolors, Radix Colors, OKLCH pickerFree
ContrastWebAIM, APCAFree
InspirationMobbin, Godly, Land-book$0-15/mo
ReadingRefactoring UI, the single best resource for non-designers~$99

Realistic cost: $0-100. A component library plus a token file gets a non-designer 80% of the way.


Alternatives & trade-offs

Component library vs custom. Libraries give you accessibility and edge cases free, at the cost of a somewhat familiar look. Custom differentiates and costs enormous time on states, keyboard handling and ARIA. Use a library and restyle it unless visual identity is your product.

Tailwind vs CSS-in-JS vs plain CSS. Tailwind enforces a scale by default (its real benefit) and clutters markup. CSS-in-JS colocates and adds runtime cost. Plain CSS with custom properties is simplest and needs discipline. All three work; consistency matters more than the choice.

Off-the-shelf theme vs original identity. A bought theme gets you shipping fast and looks like every other product using it. Original identity is slower and can be a moat (Chapter 13). For consumer, identity matters more; for B2B tools, competence matters more.

Design in Figma vs design in code. Figma is faster to iterate and creates a translation gap. Coding directly removes the gap and is slower to explore. Solo founders often do better sketching roughly then designing in code.


Checklist


📓 Case Study: constraint doing the work of talent

Project: SOLIS. Solo founder, no design background, no budget.

The whole visual system fits in one block, and that's the point:

:root{
  --bg:#15100b; --bg2:#1e1710; --card:#241b12; --card2:#2e2316;
  --gold:#d4a24e; --gold2:#e8c279; --bronze:#a3793a; --crimson:#9a4631;
  --text:#efe3c8; --muted:#a8946f; --ok:#b7c08a;
  --radius:18px;
  --serif:'Cormorant Garamond',Georgia,serif;
  --body:'EB Garamond',Georgia,serif;
}

Eleven colours, one radius, two fonts. Defined on day one, used everywhere.

Three deliberate choices inside it:

⚠️ Deviation: hand-drawn decoration was attempted before real imagery, and it looked hand-drawn.

Several screens began as programmatically generated SVG "scenes", mountains, sunbursts, decorative flourishes. The verdict:

"dont try to make the design yourself, its pretty bad using svg, lets just use an image that is darkened, to match the vibe."

The commits read like a confession: "results, darkened background images instead of SVG-drawn scenes", "time-loss screen, painting with a grey→colour wipe."

📐 The generalisable rule: if you don't have design skill, don't generate decoration, use photography or art. A darkened photograph with good typography looks premium. A hand-coded "cinematic mountain range" looks like a hand-coded mountain range. This applies equally to AI-generated SVG, CSS illustrations, and elaborate gradient scenery.

⚠️ Deviation: a colour token failed contrast and nobody noticed for weeks.

A muted token used for small captions (11-14px) measured 3.72:1 against the background, below the 4.5:1 floor for body text. It looked fine; it was tested by eye, not by tool. It was caught only during a later audit and corrected to 5.36:1.

This is the most common contrast failure there is: muted text that looks elegant and fails the standard. The fix is a tool, not an opinion.

Where the constraint paid off. When the same identity was later applied to a completely different product surface, a documentation site, the palette, type pairing and radius transferred directly. A token system built for one product became a brand system for free, which is the compounding benefit of defining tokens early.


Lessons

  1. Constraint is the craft. Small palettes applied without exception read as intentional.
  2. The spacing scale is the highest-impact fix for amateur UI.
  3. Semantic tokens on day one. Raw hex in components kills theming permanently.
  4. Check contrast with a tool, in both themes. Elegant muted grey is the most common failure.
  5. Never pure white or pure black.
  6. Don't generate decoration you can't draw. Real imagery plus good type beats hand-made SVG scenery.
  7. Refusing the category colour is a strategy. Fitting in is invisibility.
  8. Use a component library and restyle it, you're buying accessibility and edge cases.
  9. Extract a design system after 3-4 screens, not before.

Next: Chapter 13: Branding and Visual Identity →

Useful? Share this chapter