Systems & Platforms. Make every decision once, then respect the ground you build on.

A design system is how one person ships like a careful team: you decide the color of an error, the height of a button and the way a dialog behaves once, write it down in code, and never think about it again. Platforms are the other half of the same idea. iOS, macOS, Android, Windows and the web each come with a system of their own, and your product feels right when it speaks their language.

Reading timeAbout 4 hours, in 5 sittings
Practice time2 weeks
Before thisVisual Design, Structure & Interaction
TeachersBrad Frost, Nathan Curtis, Jina Anne, Jakob Nielsen, Ethan Marcotte, the Apple, Google and GOV.UK system teams

By the end you will be able to

  • Explain why a one-person product needs a small design system, and what the minimum viable system contains (understand)
  • Structure design tokens in three layers (primitive, semantic, component) and name them so that themes such as dark and high contrast become a swap, not a rewrite (apply)
  • Specify a component completely: anatomy, variants versus props, every state, sizes and content rules, on one page future-you can trust (create)
  • Spot where an app ignores the conventions of iOS, macOS, Android, Windows or the web, and say what the native answer is (analyse)
  • Adapt one feature across iPhone, Mac, Android and the web without copying the same screen four times (create)
  • Audit your product for inconsistency and design debt, and keep a system healthy for years with versioning and a regular inventory (evaluate)

Warm-up: how systematic is your product today?

Before any theory, a quick diagnostic. You already have a design system. Every product does. The only question is whether it is written down and deliberate, or scattered across a hundred files as accidents. Answer these honestly about the product you are building now (or the last one you shipped).

  1. Count your grays. Search your stylesheet or Swift code for color literals. How many distinct grays are there? Teams that have never audited usually find a dozen or more, and nobody chose most of them.
  2. Count your button styles. Open five screens. How many different paddings, radii and font weights do your buttons use?
  3. Try dark mode. If you flipped to dark mode today, how many files would you have to touch? One, or fifty?
  4. Ask the platform question. On your main platform, where does the back action live, where do settings live, and which side of a dialog does the confirm button sit on? If you hesitate, Part five is for you.
Diagnostic

You want to change your brand accent from blue to green. Which situation means you already have the most important part of a design system?

How to study this guide

Read Parts one to four with your code editor open. Each chapter ends with an exercise that changes your real product: you will finish with a token file, a dozen documented components and a consistency audit. Parts five and six are about platforms; read the chapter for your main platform closely and skim the others, then come back to them when you port.

Part one

A system for one

Why a solo creator, who has no team to align, still needs a design system; what the smallest useful one contains; the most famous model of how systems are built, and what to steal from the systems large companies have published for free.

1

Why a system, when you are one person

Taught by Nathan Curtis, with Brad Frost and Massimo Vignelli

A design system is a set of decisions you make once and reuse forever. For a team it is about alignment; for one person it is about speed, consistency and protecting your limited attention from a thousand tiny choices.

Most writing about design systems is written for companies with fifty designers and three hundred engineers. Their problem is coordination: how do you stop twelve teams from inventing twelve date pickers? You do not have that problem. You have a different one, and it is just as real: every time you build a screen, you face dozens of micro-decisions. How much padding here? Which gray for this caption? Is this button 36 or 40 pixels tall? Should the error be red text or a red border? Each decision costs a little attention, and attention is the scarcest thing a solo creator has.

Worse, you make each decision slightly differently each time, because you made the last one three weeks ago and do not remember. The result is drift: four grays that are nearly the same, buttons that are 38, 40 and 42 pixels tall, and dialogs that put the confirm button on different sides. No single screen looks wrong. Together they make the product feel subtly cheap, the way a room feels off when the picture frames are hung at slightly different heights. People rarely name it, but they feel it, and it lowers their trust.

Building one new settings screen Without a system about 30 choices: padding, grays, sizes, radii… With a system about 5 choices, all about the product: which settings exist, their order, their labels, defaults, grouping Illustrative counts: the system absorbs visual choices so your attention goes to content.
The system eats the small decisions. What remains are the decisions only you can make about the problem. That is where a solo creator's attention should go.

Three things a system buys you

Consistency. When the same thing always looks and behaves the same way, people learn it once. A red text link that deletes in one place and a red filled button that deletes in another make people re-learn. Consistency is a usability feature first and an aesthetic one second (it is one of Jakob Nielsen's ten usability heuristics: "consistency and standards").

Speed. Once you have a Button, a TextField, a Sheet and a ListRow that you trust, a new screen is assembly, not design. Many solo creators report that their second year of building is several times faster than the first for exactly this reason.

Fewer decisions. Decision fatigue is real enough at the end of a long building day. A system turns "what padding?" into "space-4", which is not a decision at all. You will make better product decisions with the attention you save.

The minimum viable system

The danger for a solo creator is the opposite of the team's: building a system for its own sake. A beautiful Figma library with 140 components and seven documentation pages is a hobby, not a tool, if your product has nine screens. The minimum you need is small and fits on a page or two of code:

Voice 10 writing rules Patterns forms, empty states, lists, errors, confirm About a dozen components button, field, list row, sheet, toast, menu… Tokens color, type, space, radius, shadow, motion
Build from the bottom. Tokens give the most value per hour, and every layer above depends on them. Voice sits on top because it governs every label in every component (see Words).
  1. Tokens. Named values for color, type, spacing, radius, shadow and motion. Perhaps 40 to 80 of them. Part two is about these.
  2. About a dozen components. Button, icon button, text field, select or picker, checkbox and switch, list row, card or section, sheet or dialog, menu, toast, empty state, navigation bar. That covers most of the screens of most small products.
  3. A handful of patterns. How you do forms, confirmation, undo, errors, loading, empty states and search. A pattern is a recipe made of components; see Structure & Interaction for the patterns themselves.
  4. Voice. Ten rules for how your product speaks: sentence case, verbs on buttons, no blame in errors. Covered in Words.
Don't
Save Save changes SUBMIT Update 4 screens, 4 "save" buttons: 28–40 px, 4 radii, 3 colors, 3 labels for one action
Drift. Each was a reasonable choice on its day. Together they say nobody is in charge.
Do
Save Save Save Save One Button, one size token (36), one radius token, one verb. Learned once.
One decision, reused. People stop noticing the button and notice the work.
A design system isn't a project. It's a product, serving products.Nathan Curtis, title of an essay on EightShapes' Medium publication, 2016

Curtis wrote that for organizations, but it lands differently for you. Your system is a product whose only customer is you, six months from now, tired, at 11 pm, trying to add a feature quickly. Design it for that person: obvious names, few options, no cleverness.

Myth

"A design system will make my product look generic." A system decides how consistently you apply your choices, not which choices you make. iA Writer, Things and Linear are among the most distinctive products of their eras, and each is extremely systematic. Distinctiveness comes from a few strong decisions applied everywhere, which is exactly what a system enforces.

For the solo creator

Do not stop building to "make a design system". Extract it. Build two or three screens, notice what repeats, and pull the repeated values into tokens and the repeated pieces into components. A system extracted from real screens fits your product; a system designed up front fits an imaginary one. Budget one afternoon a month for this, forever.

Do

  • Treat the system as code your product imports, not a separate document.
  • Start with tokens, then the five components you use on every screen.
  • Extract from screens you have already built.
  • Keep the whole system small enough to hold in your head.

Don't

  • Spend a month on a component library before the product has users.
  • Copy a big company's full system; you would maintain 200 things you never use.
  • Add a component for something that appears once.
  • Confuse the Figma file with the system. The shipped code is the system.

In the wild

  • NYC Transit Authority Graphics Standards ManualUnimark (Vignelli, Noorda), 1970
    A ring binder that specified every sign in the subway: typeface, arrow shapes, color discs for lines, module sizes. Decades before software, it is a design system in the full sense: a few rules, applied relentlessly. A system is decisions plus documentation. The medium does not matter.
  • iA WriterInformation Architects, 2010–today
    Very few typefaces, one accent color, a tightly limited set of controls across Mac, iOS, Windows and Android. The product feels like one object on every device. A small system, held tightly, is a brand.
  • LinearLinear, 2019–today
    The interface is extremely consistent: the same command menu, list rows, status icons and keyboard grammar everywhere. The consistency is a big part of why it feels fast. Consistency is perceived as quality and as speed.

Common mistakes

Building the system before the product

It feels like productive, grown-up work, and it postpones the scary part (finding out if anyone wants the thing).

Build three real screens first. Extract tokens and components from them in one afternoon. Grow the system only when something repeats a third time.

Treating the design file as the source of truth

Designers are taught that Figma is where design lives. For a solo builder, the design file and the code drift apart within weeks.

Make the code the source of truth. If you keep a design file, generate or update it from the code's token file, not the other way round.

"One-off" exceptions that never get folded back

A screen needs something slightly different, you hard-code it "for now", and now is forever.

Allow exceptions, but mark them with a comment like // SYSTEM-EXCEPTION: so a monthly search finds them. Either promote them to the system or remove them.

Check yourself

You are three weeks into a new note-taking app with four screens. Which is the best next move for your system?

Exercise 1.1 45 minutes · on your own product

The decision census

  1. Search your codebase for literal values: hex colors, pixel or point sizes, font sizes, corner radii, durations. In CSS a regex like #[0-9a-fA-F]{3,6}|\d+px gets you most of the way; in Swift search for Color(, .padding( and .font(.system(size:.
  2. Paste each unique value into a spreadsheet with a count of how often it appears.
  3. Group near-duplicates (#6B6B6B and #6E6E6E are the same decision made twice).
  4. For each group, write the one value you would keep and a name for it.
You're done when: you have a one-page list showing "found N grays, keeping 4; found N spacing values, keeping 7", with a proposed name for each kept value. This becomes the input to Exercise 4.1.
BeginnerCompetentStrong
Listed colors only.Colors, spacing and type sizes counted and grouped, with names.All six categories counted, duplicates merged, and each kept value justified by where it is used.
2

Atomic design, and where it breaks

Taught by Brad Frost, with critiques from the practice

Atomic design is a mental model: interfaces are made of small parts that combine into larger ones. It is a superb way to think about reuse, and a poor filing system if you take the chemistry literally.

In 2013 Brad Frost published a blog post, and in 2016 a book, both called Atomic Design. Borrowing from chemistry, he described five stages. Atoms are the smallest useful parts: a label, an input, a button, a color. Molecules are small groups that do one job together: a search form made of a label, an input and a button. Organisms are larger sections made of molecules: a header containing a logo, navigation and the search form. Templates place organisms into a page layout with placeholder content, showing the structure. Pages are templates filled with real content, which is where you discover whether the system actually holds up when a title is 80 characters long or a list is empty.

Atoms Label Search tokens live here too Molecule Search notes Go search form Organism header Template structure, placeholder content Page A very long real title that wraps to two… empty real content, real edge cases
Atoms to pages. The left three stages are your component library. The right two are where you test it. Notice the page stage: a long title and an empty column are exactly the cases templates hide.

What atomic design gets right

The model's lasting gift is a habit of mind: look at any screen and see it as parts and wholes at the same time. When you design a settings screen, you are also designing a list row, a switch and a section header that every future screen will use. Frost was clear that the stages are not a step-by-step process:

Atomic design is not a linear process, but rather a mental model to help us think of our user interfaces as both a cohesive whole and a collection of parts at the same time.Brad Frost, Atomic Design, chapter 2, 2016

The second gift is the page stage. Many systems stop at components and never stress-test them with real, ugly content. The page stage says: put the 80-character title, the user with no avatar, the list with 2,000 items and the list with zero items into the layout, and see what breaks.

Where it breaks

Practitioners have criticized the literal taxonomy for years, and the criticisms are worth knowing because you will feel them yourself:

  • The boundaries are arbitrary. Is a card a molecule or an organism? Is a list row with an avatar, two labels and a switch a molecule? Teams lose hours to these debates, and the answer never changes what anyone builds.
  • The chemistry names mean nothing to anyone else. "Organism" does not tell future-you where the date picker lives. "Forms", "Navigation" and "Feedback" do.
  • Real systems are graphs, not ladders. A button appears inside a dialog, a toolbar, a card and a form. A dialog can contain a form that contains a list. Parts compose in every direction.
  • It says nothing about behavior. States, keyboard handling, accessibility and motion are where most component work actually goes, and the atomic model is silent about them.
The ladder in theory Organism Molecule Atom The graph in practice Button Dialog Toolbar Card Form contains
Composition goes in every direction. A Dialog can contain a Form, and both contain Buttons. File components by what they do, not by how big they are.

What to keep, what to drop

Keep the habit of thinking in parts and wholes, and keep the page stage as your stress test. Drop the five folders. Organize your components by job, the way most published systems now do: Actions (button, icon button, link), Inputs (field, select, checkbox, switch), Feedback (toast, banner, progress, empty state), Navigation (tab bar, sidebar, breadcrumb), Containers (card, sheet, dialog, popover). Future-you will look for "the thing that shows a message after save" under Feedback, never under Molecules.

Do

  • See every screen as parts you are also designing for later reuse.
  • Test components inside real pages with worst-case content.
  • Group components by purpose: actions, inputs, feedback, navigation, containers.

Don't

  • Debate whether something is a molecule or an organism.
  • Name folders after chemistry.
  • Assume a component is done because it looks right with placeholder text.

In the wild

  • Pattern LabBrad Frost and Dave Olsen, 2013
    An open-source tool built to make atomic design concrete: you build atoms, molecules and organisms as templates and see them assembled into pages with real data. The page stage needs tooling that renders real content, not just a gallery.
  • Storybookopen source, 2016–today
    The most common way web teams view components in isolation, one "story" per state. It is organized by whatever folders you choose, and most teams choose purpose-based ones. Isolation is good for building; real pages are still needed for testing.
  • GOV.UK Design SystemGovernment Digital Service, 2018–today
    Splits the library into Styles, Components and Patterns, with patterns named after user tasks ("Ask users for addresses", "Check answers"). Name things by the job people come to do.

Common mistakes

Taxonomy over function

Classification feels like rigor.

Pick purpose-based folders once and move on. If a component could live in two, pick the one you would search first.

Never reaching the page stage

Components look perfect in isolation with "Lorem ipsum" and one-word labels.

Keep a "torture page" in your app (debug builds only) that renders every component with the longest, shortest, emptiest and most international content you can find.

Check yourself

Your reading app's article card looks perfect in your component gallery. In the real feed it breaks when a German headline has a 30-letter compound word. Which stage of atomic design would have caught this?

Exercise 2.1 40 minutes · on your own product

Build a torture page

  1. Create one screen, visible only in debug builds, that renders your five most-used components.
  2. Feed each one: the shortest real content, the longest real content, empty content, a right-to-left string, and a value at your largest accessibility text size.
  3. Screenshot it in light and dark mode. Circle every break.
You're done when: the torture page exists in your codebase and you have a list of at least three breaks to fix.
BeginnerCompetentStrong
Rendered components with long text only.All five content cases, both themes, breaks listed.The page is in the build, runs on every platform you ship, and you check it before each release.
3

Famous systems, and what to steal from each

Taught by the Apple, Google, GitHub, Shopify, IBM and GOV.UK system teams, with Radix and shadcn

Large companies publish their systems for free. Each one has solved a problem you will meet. Study them for their reasoning, not their visuals, and steal one idea from each.

Reading a mature design system is like reading the code of a well-run open-source project: you see thousands of hours of decisions, and the documentation often explains why. You do not need to adopt any of them. You need to know which one has thought hardest about the thing you are stuck on. Here is how they differ, and the one idea most worth taking from each.

Strong visual opinion Behavior first, style is yours Platform guidance Product kit Apple HIG Material 3 Carbon Polaris Primer GOV.UK shadcn/ui Radix My reading, not an official classification. Use it to choose what to study.
Different systems answer different questions. Platform guidelines tell you how to fit in on someone else's operating system. Product kits show how a company keeps hundreds of screens consistent. Behavior libraries give you accessible mechanics and leave the look to you.
SystemWhat it isSteal this
Apple Human Interface GuidelinesApple's guidance for every Apple platform: principles, patterns, components, inputs, accessibility.The habit of explaining when not to use a component, and the platform-by-platform notes. Read the pages on alerts, sheets and navigation before you build those things.
Material Design 3Google's system (2021–), behind Android and many Google products, with tokens, dynamic color and adaptive layouts.Color roles (primary, on-primary, primary-container, surface…) and window size classes. The cleanest public model of semantic color and adaptive layout.
PrimerGitHub's system for a dense, information-heavy product used all day by developers.How to design for density and long sessions, and the clear separation between primitive and functional tokens.
PolarisShopify's system for merchants running businesses in the admin.Content guidelines as a first-class part of the system: voice, actionable language, naming. Words are components too.
CarbonIBM's open-source system for enterprise software.Themes as named token sets (White, Gray 10, Gray 90, Gray 100), and serious data-table and form guidance.
GOV.UK Design SystemThe UK government's system for public services used by everyone, including people under stress and with low digital skills.Patterns named after tasks, and "research on this component" sections that record what was tested and what failed. Radical plainness.
RadixUnstyled, accessible primitives (dialog, popover, menu…) plus a separate set of color scales.Radix Colors: 12-step scales where each step has a job (app background, component background, borders, solid fill, text). A ready-made answer to "which shade where".
shadcn/uiA collection of components built on Radix and Tailwind that you copy into your project and own, rather than install as a dependency.The ownership model: the code is yours to edit, so your system can start from good defaults and diverge without fighting a library.

How to read a system in one hour

  1. Read the principles page, then ignore it. Every system has one; they are mostly similar. Note any principle that forces a trade-off (GOV.UK's "do less" is a real constraint; "be delightful" is not).
  2. Open the button page. It shows you their whole approach in miniature: hierarchy, states, sizes, content rules and accessibility notes.
  3. Open the color or token page. How many layers? How are tokens named? How do they theme?
  4. Find one pattern you need now (confirmation, empty state, form validation) and read it closely, including the "don't" examples.
  5. Write down one idea to steal and one thing you would deliberately not copy.
Don't
Notes + FAB on iPhone
Copying the surface. Material's look dropped onto iOS makes the app feel foreign to iPhone users.
Do
Notes Steal ideas,not looks
Copying the reasoning. The color-role idea travels anywhere; the floating button does not.
For the solo creator

If you build for the web with React, shadcn/ui on top of Radix is a very sensible starting point for a solo product in 2026: accessible behavior you could not afford to build yourself, and code you own and restyle with your own tokens. If you build native Apple apps, SwiftUI's built-in controls already are your component library; your system is mostly tokens, a few custom styles and the HIG.

Do

  • Study systems for their reasoning: why this state, why this rule.
  • Read the "don't" examples; they are the most expensive lessons.
  • Borrow accessible behavior (focus handling, keyboard) from a proven library.

Don't

  • Copy a system's visual identity; your product becomes theirs.
  • Import a platform's look onto a different platform.
  • Adopt a huge system for a ten-screen product.

In the wild

  • GOV.UK "Start now" buttonGOV.UK Design System
    The start button is documented as its own variant, with an arrow, meant only for the beginning of a service. It is not a general "big button". A variant should exist for one clearly described job, or it should not exist.
  • Material dynamic colorAndroid 12, 2021
    Material You derives a full color scheme from the user's wallpaper. It only works because every component asks for a role ("primary container") rather than a color ("blue 200"). Semantic tokens make radical theming possible.
  • shadcn/ui2023
    Became very popular quickly by inverting the usual model: you run a command that copies the component's source into your repository. Updates are yours to merge. For a soloist, owning the code can beat depending on a package.

Common mistakes

Using Material everywhere because it is complete

It is the most thoroughly documented system, so it is easy to copy wholesale.

On Android, follow it closely. Elsewhere, take its ideas (roles, size classes) and leave its look.

Reading systems as galleries

The pictures are the fastest part to absorb.

Read the usage guidance and the "when not to use" sections. That text is the part you cannot see in a screenshot.

Check yourself

You are designing a long, multi-step form for freelancers filing tax details. Which system's documentation is most likely to help with the flow itself?

Connect the ideas · Part one

You extracted twelve components from your app and named their folders Atoms, Molecules and Organisms. Six months later you cannot find your toast. Combining Chapters 1 and 2, what is the best fix?

Exercise 3.1 60 minutes · reading

Steal three ideas

  1. Pick the system closest to your platform (HIG or Material), one product kit (Primer, Polaris or Carbon) and GOV.UK.
  2. In each, read the button page and one pattern you need right now.
  3. For each system write: one idea to steal, where in your product it goes, and one thing you will not copy, with a reason.
You're done when: you have a short note with three stolen ideas, each tied to a specific screen of your product.
BeginnerCompetentStrong
Three visual details you liked.Three reasoned ideas with a target screen each.Three ideas already applied in code, and a "will not copy" list that shows judgment about your platform and audience.
Part two

Design tokens

Tokens are the atoms that are not even components: the named values every pixel of your product is made of. Get their layers and names right and theming, dark mode, high contrast and a future rebrand become an edit to one file.

4

Tokens in three layers

Taught by Nathan Curtis, with Jina Anne and the W3C Design Tokens Community Group

A design token is a named design decision. The power comes from layering: primitives say what values exist, semantic tokens say what each value is for, and component tokens say where it is used. Code only ever reads the upper layers.

The term "design tokens" was popularized at Salesforce around 2014, where Jina Anne and Jon Levine used it for the named values that fed the Lightning Design System across web and native platforms. The idea is older than the name: any time you wrote $brand-blue in Sass you were using a token. What changed is the discipline around them, and that discipline is mostly about layers.

1 · Primitive what values exist 2 · Semantic what a value is for 3 · Component where it is used (optional) gray-900#1C1D1F gray-600#5F6267 gray-50#F1F1EF amber-500#E9A800 red-600#B03D2B color-text color-text-secondary color-bg color-accent color-danger button-primary-bg button-primary-label field-border-focus button-danger-label Arrows read "refers to". Your screens read from layer 2 or 3, never from layer 1. A theme swaps the arrows between layers 2 and 1.
Three layers, one direction. Notice that button-primary-label points to color-bg: the label on a filled button is the page background color. That relationship survives dark mode automatically.

Layer 1: primitives (also called global, base or core tokens)

Primitives are your palette and your scales: every gray, every hue step, every spacing step, every font size. Names describe the value, not the use: gray-600, blue-500, space-4, radius-2. They are the paint on your palette. Nothing in your interface should reference them directly, because the moment a screen says "gray-600" it has made a decision that a theme cannot change.

Layer 2: semantic tokens (alias or system tokens)

Semantic tokens name a purpose: color-text, color-text-secondary, color-bg, color-surface, color-border, color-accent, color-danger, space-inset-card, radius-control. Each points to a primitive. This is the layer your product mostly reads, and the layer that changes between themes: in light mode color-text points to gray-900; in dark mode it points to gray-50. Not a single component changes.

Layer 3: component tokens (optional for you)

Component tokens name a specific slot in a specific component: button-primary-bg, field-border-focus. Large systems need them so that one team can tweak the button without touching everything that uses color-accent. A solo creator usually does not. Add a component token only when a component genuinely needs to differ from the semantic default, for example when your primary button uses a slightly darker accent than links for contrast. Otherwise, components read semantic tokens directly and you have two layers, which is plenty.

/* tokens.css: the whole idea in 20 lines */
:root {
  /* 1 · primitives: values, named by what they are */
  --gray-900: #1C1D1F;  --gray-600: #5F6267;
  --gray-200: #D6D6D2;  --gray-50:  #F1F1EF;
  --amber-500: #E9A800; --red-600: #B03D2B;
  --space-2: 8px; --space-3: 12px; --space-4: 16px;

  /* 2 · semantic: purposes, named by what they are for */
  --color-text:           var(--gray-900);
  --color-text-secondary: var(--gray-600);
  --color-bg:             var(--gray-50);
  --color-border:         var(--gray-200);
  --color-accent:         var(--amber-500);
  --color-danger:         var(--red-600);
  --space-inset-card:     var(--space-4);

  /* 3 · component: only where a component must differ */
  --button-primary-bg:    var(--color-text);
  --button-primary-label: var(--color-bg);
}

Naming: the part that lasts

Values change; names stay for years, and every rename touches every file. Nathan Curtis's essay "Naming Tokens in Design Systems" (EightShapes, 2020) breaks a token name into ordered parts. Simplified for a one-person system, a name reads from general to specific:

Read a token name from general to specific color text secondary categorypropertyvariant button primary bg hover componentvariantpropertystate Semantic token read by many components Component token read by one component Optional prefixes come first (a namespace like "rd-"); modifiers like state or mode come last.
Ordered parts make names guessable. If you know the order, you can guess a token you have never seen: the hover background of the secondary button must be button-secondary-bg-hover.
Don't
--blue
--blue-dark-2
--gray-text
--big-padding
--header-color-new
--modal-bg-FINAL
--color-red-for-errors
Names that rot. "blue" breaks when the accent becomes green. "new" and "FINAL" mean nothing in a month. "big" has no scale. Purposes and values are mixed in one layer.
Do
--blue-500          /* primitive */
--gray-600          /* primitive */
--space-6           /* primitive */
--color-accent      /* semantic */
--color-text-secondary
--color-danger
--space-inset-dialog
Names that survive. Primitives name values on a scale; semantic tokens name jobs. Changing the brand hue edits one line and no names.

A format you can share between platforms

If you ship on more than one platform, keep tokens in one neutral file (usually JSON) and generate CSS variables, Swift constants and Android resources from it. The W3C Design Tokens Community Group has worked for several years on a standard JSON format for exactly this, and tools such as Style Dictionary (originally from Amazon) transform one token file into each platform's format. Chapter 11 shows the pipeline. If you ship on one platform, skip the JSON: a single tokens.css or Tokens.swift is your source of truth.

For the solo creator

Two layers are enough to start: primitives and semantic. Aim for about 12 to 20 semantic color tokens, not 200. A good starter set: bg, surface, surface-raised, border, border-strong, text, text-secondary, text-tertiary, accent, accent-text (text on accent), accent-soft, danger, success, warning, focus-ring, overlay. If you cannot say in one sentence when to use a token, delete it.

Do

  • Name primitives by value on a scale, semantic tokens by purpose.
  • Make components read semantic tokens, never primitives.
  • Order name parts consistently, general to specific.
  • Keep the semantic set small enough to memorize.

Don't

  • Put a hue in a semantic name (--color-blue-button).
  • Make a token for a value used once.
  • Create component tokens for every component "just in case".
  • Rename tokens casually; every rename is a migration.

In the wild

  • Apple semantic colorsUIKit / SwiftUI, iOS 13, 2019
    With dark mode, Apple asked developers to use colors like label, secondaryLabel and systemBackground instead of fixed colors. These are semantic tokens that resolve differently in light, dark and increased-contrast settings. The platform itself works in semantic tokens; your custom colors should too.
  • Material 3 color rolesGoogle, 2021
    Roles such as primary, onPrimary, primaryContainer, onPrimaryContainer, surface and onSurface. The "on" pairing guarantees a readable foreground for every background. Name tokens in pairs: a surface and the content that sits on it.
  • Primer primitivesGitHub
    GitHub publishes base scales separately from functional tokens such as foreground, background and border variants, which components consume. Even very large systems keep the same two core layers.

Common mistakes

Semantic names that are really primitives

--color-primary-blue feels semantic because it says "primary".

Remove every hue word from semantic names. If the token would be wrong after a rebrand, it is a primitive wearing a costume.

Token explosion

Each new screen adds "just one more" token: --settings-row-gap, --profile-row-gap.

Before adding, ask whether an existing semantic token already describes the purpose. Most "new" tokens are an existing one with a new name.

Skipping the semantic layer

Primitives feel sufficient until dark mode arrives.

Introduce semantic tokens now, even if each points to one primitive. The indirection costs nothing today and saves the dark-mode rewrite later.

Check yourself

Your app's success toast uses --green-600 directly. You are adding a colorblind-friendly theme where success should be blue. What is the cleanest change?

Exercise 4.1 90 minutes · on your own product

Write your token file

  1. Take the census from Exercise 1.1. Turn each kept value into a primitive with a value-based name.
  2. Write 12 to 20 semantic color tokens, plus semantic spacing and radius tokens if useful, each pointing to a primitive.
  3. Replace the literals in your two most important screens with semantic tokens.
  4. Next to each semantic token, write a one-line comment: when to use it.
You're done when: a tokens.css, Tokens.swift or equivalent exists, two screens contain no literal colors or spacing, and every semantic token has a usage comment.
BeginnerCompetentStrong
A list of colors as variables, used in some places.Two layers, purposeful names, two screens fully migrated.The whole app migrated, a lint rule or search that flags new literals, and no hue words in semantic names.
5

Scales: space, type, radius, shadow, motion

Taught by the 8-point grid tradition, with Material, Apple and Radix

Primitives are not a bag of values; they are scales. A scale with a clear rule (each step twice the last, or a fixed ratio) makes good choices easy and bad ones impossible.

Ask someone to choose "the right padding" and they will deliberate. Give them a scale of eight steps and they will pick in a second, and their picks will relate to each other visually. That is the whole trick. Scales turn a continuous space of options into a small menu where every item looks good next to every other item.

Spacing: a 4-point base

Most modern systems use a base unit of 4 or 8 points. Both divide evenly into common screen densities (1×, 2×, 3×), so edges land on whole pixels. A 4-point base with steps that grow faster as they get bigger works for almost everything: small steps for tight relationships inside a control, large steps for separating sections. The jump between steps should be visible; if you cannot see the difference between two steps, you have one too many.

space-14icon to label space-28inside a control space-312label to field space-416card padding, screen margin space-524between groups space-632between sections space-748page header gap space-864hero, landing sections Steps grow by about 1.5× at the top: small distances need precision, large ones need contrast.
Eight steps cover a whole product. The rule of proximity from Visual Design becomes a rule of tokens: related things get small steps, unrelated things get big ones.

Type: a modular scale, few steps

Font sizes are a scale too. A ratio of about 1.2 (a "minor third") to 1.25 (a "major third") gives sizes that feel related: 13, 15, 17, 20, 24, 30. Most products need five or six text styles, each a token bundling size, line height, weight and letter spacing: text-caption, text-body, text-body-strong, text-title, text-headline, text-display. On Apple platforms, use the built-in text styles (.body, .headline, .title2…) as your tokens, because they scale with Dynamic Type for free. The details of choosing the scale are in Typography.

Radius: few values, and the nesting rule

Three or four radii are enough: radius-small (about 4) for chips and small controls, radius-control (about 8) for buttons and fields, radius-container (about 12 to 16) for cards and sheets, and radius-full for pills and avatars. When one rounded shape sits inside another, the inner radius should equal the outer radius minus the padding between them. Otherwise the gap between the curves is uneven, and the corners look pinched.

Same radius inside: pinched outer 20, inner 20 The corner gap is wider than the side gap. Inner = outer − padding outer 20, inner 10 The gap is even all the way round.
Concentric corners. Apple's hardware and recent software follow this rule closely. It is a two-line fix in code: calc(var(--radius-container) - var(--space-2)).

Shadow and elevation: two or three levels

Shadows say "this is above that". A product needs very few heights: flat (most things), raised (cards, if you use shadows at all), overlay (menus, popovers) and modal (dialogs, sheets). Make each a token bundling offset, blur and opacity. In dark mode shadows almost disappear against dark backgrounds, so dark themes usually express elevation by making higher surfaces slightly lighter instead. That is one reason color-surface-raised is a separate semantic token.

flatraisedoverlaymodal lists, pagecardsmenus, popoversdialogs, sheets
Elevation is information. If everything floats, nothing does. Keep most of the interface flat, as calm products like Things and iA Writer do.

Motion: durations and curves

Motion gets tokens too: three or four durations (for example duration-fast about 120 ms for hovers and toggles, duration-base about 200 to 250 ms for most transitions, duration-slow about 350 to 400 ms for large surfaces like sheets) and two or three easing curves (standard, enter, exit). Tokenizing motion also gives you one place to honor reduced-motion settings. The craft of timing is in Motion & Feel.

Lab: change a token, watch everything follow

Reading list

Three saved articles, synced 2 minutes ago.

  • The shape of design12 min
  • Calm technology8 min
  • Systems for one20 min
Every gap, padding, corner and text size in the card is computed from these four values. Drag the space unit to 2 and then 14: the layout stays coherent at every setting because relationships, not numbers, are encoded. Try a radius of 22 and notice the buttons and field follow while the card stays proportional. That is what one source of truth feels like.

Do

  • Define every primitive category as a scale with a rule.
  • Bundle text tokens (size, line height, weight) as styles.
  • Use the nesting rule for concentric corners.
  • Keep elevation to three or four levels.

Don't

  • Use "nearly the same" values (14 and 15 px padding).
  • Add a step to the scale for one screen.
  • Rely on shadows alone for elevation in dark mode.
  • Hard-code durations inside components.

In the wild

  • Material spacingGoogle
    Material layouts are built on a 4 dp base and 8 dp grid for components and layout, so elements align across apps. A shared base unit makes unrelated screens feel like one product.
  • Apple text stylesiOS 7, 2013–today
    Large Title, Title 1–3, Headline, Body, Callout, Subheadline, Footnote, Caption 1–2. Eleven tokens that every app shares and that resize together with the user's text-size setting. Type tokens are an accessibility feature.
  • Tailwind CSS spacingTailwind Labs
    Classes like p-4 and gap-6 map to a 0.25 rem step scale, so a whole team picks from the same menu. A scale with a short name is easier to obey than a free number.

Common mistakes

Too many steps

A designer wants 14 for one case and 18 for another, so both join the scale.

If two steps are within about 25% of each other, merge them. Fewer, more distinct steps produce clearer hierarchy.

Optical exceptions without a rule

Icons and text sometimes need nudging to look aligned, and the nudges accumulate as magic numbers.

Keep optical adjustments inside the component (the icon button handles its own centering) so the scale stays clean everywhere else.

Check yourself

A card has a 16 px radius and 12 px padding. Inside it sits an image. What radius should the image have so the corners look right?

Exercise 5.1 45 minutes · on your own product

Five scales on one page

  1. Write your spacing scale (6 to 8 steps), type styles (5 to 7), radii (3 to 4), elevations (3 to 4) and durations (3) as primitives.
  2. For each step, write one typical use in a comment.
  3. Find the three places in your app that use a value not on any scale, and snap them to the nearest step.
You're done when: your token file contains all five scales with usage comments, and a screenshot before and after snapping shows no visible regression (often an improvement).
BeginnerCompetentStrong
Spacing and type scales only.All five scales, used by your main screens.All five scales, reduced-motion handled through motion tokens, and nested radii computed rather than typed.
6

Theming: light, dark, high contrast

Taught by Apple, Material and Radix Colors, with the WCAG authors

A theme is a different mapping from semantic tokens to primitives. If your components only read semantic tokens, a new theme is a new mapping table, and nothing else in the product changes.

Dark mode used to be a rewrite. With semantic tokens it is an afternoon, and most of that afternoon is judgment, not plumbing. The plumbing is simple: the same names, different arrows.

color-text-secondary Light → gray-600 secondary text onnear-white Dark → gray-300 lighter, so it staysreadable on dark High contrast → gray-800 darker, closer toprimary text
Same name, three arrows. Components ask for "secondary text" and never learn which theme is active.
Lab: swap the token set

Delete this highlight?

It will be removed from your library on all devices.

  • “The best interface is no interface…”Ch. 3
  • Synced2 devices
loading…
The mini interface never changes; only the table of semantic tokens on the right does. Notice three judgments that are not plumbing: dark mode uses a softer accent and lighter raised surfaces, high contrast thickens borders and removes subtle tints, and the sepia theme is a reading choice, not an accessibility one. The "New brand" set shows a whole rebrand as eight changed values.

Dark mode is not inversion

Inverting light mode produces glaring white text on pure black, accents that vibrate, and shadows that vanish. A good dark theme follows a few rules that come up again and again in platform guidelines:

  • Dark gray, not black, for most surfaces. Very dark gray lets you show elevation with lighter surfaces and reduces the halo around bright text. (Pure black can be right for OLED-focused media apps, but it is a choice, not a default.)
  • Off-white text, and less contrast for secondary text than you think. Body text around 87 to 92% white is common.
  • Desaturate and lighten accents. Saturated colors on dark backgrounds vibrate; a lighter, slightly less saturated accent reads as the same brand.
  • Elevation by lightness. Raised surfaces get a lighter gray rather than a stronger shadow.
  • Check every pair again. Contrast ratios change; WCAG asks for at least 4.5:1 for body text and 3:1 for large text and UI component boundaries. See Accessible & Inclusive.
Don't
Library forgot this card #555 text on #111: hard to read Save
Literals leak. Every hard-coded value is a place dark mode forgets. Here: a white card, captions that are nearly invisible on purpose, and a saturated accent that vibrates against the dark.
Do
Library Raised: a lighter gray Secondary text, still readable Save
Tokens remember. Surfaces, text and accent all came from the dark mapping, including the label on the button.

(These two mockups use your course theme's colors, so in dark mode they flip too. Look at the structure, not the absolute colors.)

High contrast and forced colors

Operating systems let people ask for more contrast: "Increase Contrast" on Apple platforms, contrast themes on Windows, and on the web the media queries prefers-contrast: more and forced-colors: active. Apple's semantic system colors already include higher-contrast variants. For your custom tokens, a high-contrast mapping typically pushes secondary text toward primary text, makes borders darker and thicker, removes translucent tints, and never relies on color alone for state. Under Windows forced colors, the browser replaces your colors with the user's palette entirely, so borders and outlines (not background tints) must carry structure.

:root { --color-text: var(--gray-900); --color-bg: var(--gray-50); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-text: var(--gray-50); --color-bg: var(--gray-950);
  }
}
:root[data-theme="dark"] { /* same mapping, for a manual toggle */ }

@media (prefers-contrast: more) {
  :root { --color-text-secondary: var(--color-text);
          --border-width: 2px; }
}

The :not([data-theme="light"]) guard lets a manual choice override the system setting. This course's own stylesheet uses exactly this structure.

Watch out

Do not ship a theme you do not use. If you design in light mode all day, switch your own devices to dark mode for one week every few months. Dark-mode bugs survive because their author never sees them.

For the solo creator

Ship light and dark from day one (it is cheap with semantic tokens and expected on every platform), and honor the system's increased-contrast setting. A sepia or "paper" theme is worth it only for reading products, where it is a real feature. Custom user-made themes are almost never worth it for a soloist: each one multiplies your testing.

Do

  • Follow the system appearance by default, and offer an override.
  • Design dark mode with dark grays, lighter raised surfaces and softened accents.
  • Re-check contrast for every token pair in every theme.
  • Respect increased-contrast and forced-colors settings.

Don't

  • Invert colors and call it dark mode.
  • Use color alone to show state in high contrast.
  • Let components check which theme is active.
  • Offer five themes you never test.

In the wild

  • macOS dark modemacOS Mojave, 2018
    Apple introduced system-wide dark mode on the Mac and asked apps to use semantic and dynamic colors. Apps built on hard-coded colors looked broken in dark mode until their developers updated them. The platform changes under you; semantic tokens make you ready.
  • Kindle and iA Writer reading themesAmazon; Information Architects
    Reading apps commonly offer light, sepia and dark (and sometimes a dimmer "night") as first-class themes, because reading comfort varies with ambient light. Themes are a feature when they serve the core activity.
  • Linear's theme generationLinear, 2024 redesign
    Linear has described generating its themes from a few inputs (a base color, an accent and a contrast level) rather than hand-picking every value. With semantic tokens, a theme can be computed, not drawn.

Common mistakes

Images and illustrations that ignore the theme

Tokens cover CSS colors, but PNG illustrations with white backgrounds glow in dark mode.

Draw illustrations as SVG using currentColor or token classes, or ship light and dark variants.

Shadows doing all the elevation work

Light mode taught you that cards float because of shadows.

Add color-surface-raised and use it for raised surfaces in both themes.

Forgetting the browser's own UI

Scrollbars and form controls stay light in a dark page.

Set color-scheme: light dark on the root so native controls follow.

Check yourself

In your new dark theme, cards are indistinguishable from the background even though they have the same shadow token as in light mode. What is the most robust fix?

Connect the ideas · Part two

You rebrand from amber to teal. Which part of your token system should change, if Chapters 4 to 6 were followed?

Exercise 6.1 2 hours · on your own product

Ship a real dark theme, and a contrast pass

  1. Write the dark mapping for every semantic color token. Use dark grays, a lighter raised surface and a softened accent.
  2. Check contrast for these pairs in both themes: text on bg, secondary text on bg, text on surface-raised, accent-text on accent, danger on bg, border on bg.
  3. Add a high-contrast mapping (or the prefers-contrast query) for secondary text and borders.
  4. Use your app in dark mode for one full day and log every bug.
You're done when: your app runs in light, dark and high contrast with no literal colors, and a table lists each token pair's contrast ratio per theme.
BeginnerCompetentStrong
Dark mode works on main screens; some leaks remain.All screens themed, contrast table complete, all pairs pass.Also handles forced colors, illustrations and native controls, and you used it for a day.
Part three

Components

A component is a promise: it will look and behave the same everywhere, in every state, at every size, for every user. This part is about keeping that promise: anatomy, variants and props, the full state matrix, a deep dive into the humble button, and documenting each component so future-you can trust it.

7

Component anatomy, variants and props

Taught by Nathan Curtis, with the Figma and SwiftUI API designers

Every component has named parts (its anatomy) and a small set of choices (its API). Design the API like a function signature: few parameters, clear names, no impossible combinations.

When you design a component you are designing two things at once. The first is what people see: a container, a label, maybe an icon and a focus ring. The second is what future-you sees: the options you can pass when you use it. The second is easier to get wrong, and harder to fix later, because every screen that uses the component depends on it.

Anatomy: name the parts

Draw the component and label every part. This sounds pedantic until you need to say "the leading icon in the text field gets the secondary text color, except in the error state". Named parts make specifications, tokens and bug reports precise.

Email address rohit@example.com × We'll only use this to send your receipt. 1 2 3 4 5 6 7 1 Label2 Container3 Leading icon4 Input text 5 Trailing action6 Helper / error text7 Focus ring
Seven named parts. Each part gets its own tokens and its own rules. "Helper text becomes error text and keeps its position" is a rule you can only write once the part has a name.

Variants versus props

Figma calls them variants and properties; code calls them props, parameters or modifiers. Either way, a component's options come in two kinds:

  • Variants change what the component is: a primary versus a secondary versus a destructive button. Different jobs, different appearance. Keep them few and named by purpose.
  • Props configure an instance without changing its identity: the label text, an optional icon, the size, whether it is full-width, whether it is loading.

The trap is multiplying them. Four kinds × three sizes × seven states × with-or-without icon = 168 combinations. In Figma, teams sometimes draw all 168 as variants. In code, nobody draws them; they fall out of the logic. That is one of the strongest arguments for designing components in code as a solo creator (more in Chapter 11): code handles combinations by composition, while a drawing tool needs each one drawn.

Drawn as variants …and 88 more 4 kinds × 3 sizes × 7 states × 2 icon = 168 Written as props Button( kind: .primary, size: .regular, icon: nil, isLoading: false) 4 props; code handles state
Combinations are code's job. Draw only what you need to decide: each kind once, each state once, each size once. Let code combine them.

Rules for a good component API

  1. Few options, named by purpose. kind: .destructive, not color: .red. A purpose can be restyled; a color is a promise you cannot keep.
  2. Make impossible states impossible. If a button cannot be both loading and disabled-with-reason, use one state enum instead of two booleans.
  3. Sensible defaults. The most common use should need the fewest arguments: Button("Save").
  4. Content is a prop, not a variant. "Button with icon" is not a different component; the icon is optional content.
  5. No escape hatches for style. A customColor prop is the start of drift. If you need a new look, add a new purpose-named kind.

Composition over configuration

When a component grows its fifteenth prop, it is usually trying to be several components. A Card with props for header, subtitle, image, badge, two buttons, a menu and a footer is a monster nobody can predict. Prefer small pieces that nest: Card contains a CardHeader, any content, and an optional CardActions that contains Buttons. SwiftUI and React both reward this style: views made of views.

Don't
<Card title subtitle image
  imagePosition badge badgeColor
  primaryAction primaryLabel
  secondaryAction secondaryLabel
  showMenu menuItems footer
  footerAlign compact elevated
  customPadding customColor />
Configuration monster. 17 props, most combinations untested, and every new screen adds one more.
Do
<Card>
  <Card.Header title subtitle />
  <ArticlePreview article />
  <Card.Actions>
    <Button kind="secondary">Later</Button>
    <Button>Read</Button>
  </Card.Actions>
</Card>
Composed pieces. Each piece is simple and tested on its own. New layouts need no new props.

Sizes

Most components need at most three sizes, and many need one. A common set: small for dense toolbars and tables (desktop only, pointer input), regular for almost everything, large for one primary action on a mobile screen or a landing page. Remember that on touch screens the hit area must stay at least 44 × 44 points (Apple) or 48 × 48 dp (Material) even when the visible control is smaller. Size is a prop, and the hit area is a rule the component enforces on its own.

For the solo creator

Write the component's call signature before you style it. If you cannot write Button(...) in one readable line for the five most common uses, the API is wrong, and no amount of visual polish will save it. Then build exactly the variants your screens use today.

Do

  • Draw and label the anatomy of each core component.
  • Name variants by purpose; keep them to three or four.
  • Use enums for mutually exclusive states.
  • Compose small pieces instead of adding props.

Don't

  • Draw every combination of every option.
  • Expose raw style props such as color or padding.
  • Create a new component for "button with icon".
  • Let one component grow past about eight props.

In the wild

  • SwiftUI Button and ButtonStyleApple, 2019–today
    A Button holds an action and a label; how it looks is a separate ButtonStyle you apply, and roles like .destructive and .cancel describe purpose. Separate what a component does from how it looks.
  • Radix DialogWorkOS, Radix Primitives
    A dialog is built from parts: Root, Trigger, Portal, Overlay, Content, Title, Description, Close. You compose exactly what you need. Composition makes complex components flexible without props soup.
  • Figma component propertiesFigma, 2022
    Figma added boolean, text and instance-swap properties alongside variants, largely to stop teams from drawing every combination. Even the drawing tool moved toward props.

Common mistakes

Booleans that fight

isLoading, isDisabled and hasError are added one at a time.

Replace them with one state value. Now "loading and error" cannot exist by accident.

Variants named by look

"Blue button", "outline button" describe what you see while designing.

Name by job: primary, secondary, quiet, destructive. The look can change; the job cannot.

Check yourself

Your ListRow has props showChevron, showSwitch, showCheckmark and showBadge. You find a screen where a row shows a switch and a chevron, which makes no sense. What is the best redesign?

Exercise 7.1 60 minutes · on your own product

Anatomy and API of your three core components

  1. Pick your three most used components (often button, text field, list row).
  2. For each, draw the anatomy with numbered parts (paper is fine).
  3. Write the API: each prop with type, default and one-line purpose. Mark variants versus props.
  4. Find one impossible state your current API allows and design it away.
You're done when: three anatomy sketches and three API tables exist, and one impossible state has been removed from your code.
BeginnerCompetentStrong
Anatomy drawn, API listed.APIs have purpose-named variants, defaults, and no style escape hatches.APIs refactored in code, call sites simplified, and at least one component now composes instead of configuring.
8

The state matrix

Taught by every platform's interaction guidelines, with WCAG

A component is not finished until every state is designed: default, hover, focus, pressed, disabled, loading and error, in every variant. Missing states are where products feel broken.

Designers draw the default state, because that is what appears in mockups. Users live in all the others. They hover with a mouse before clicking, tab with a keyboard, press and hold on a phone, wait while something loads, and hit errors. Each of those moments needs a deliberate answer, and the answer should be the same everywhere in your product.

Default (rest)
The component waiting for input. Everything else is a change from this.
Hover
A pointer is over it. Pointer devices only; touch screens have no hover, so hover must never be the only way to discover something.
Focus
It will receive keyboard input. Must be clearly visible: WCAG 2.2 adds requirements for visible focus that is not hidden by other content. Show the ring for keyboard focus (:focus-visible), not necessarily for mouse clicks.
Pressed (active)
Being clicked or touched right now. On touch screens this is the main feedback that a tap registered, so it must appear within about 100 ms.
Disabled
Exists but cannot be used now. Lower contrast is allowed (WCAG exempts inactive controls from contrast rules), but people should be able to find out why.
Loading
The action is in progress. Keep the component's size, show progress, prevent double submission.
Error
Something is wrong with this component's value or action. Use more than color: an icon, a message, a thicker border.
DefaultHoverFocusPressedDisabledLoadingError PrimarySecondaryDestructiveText field SaveSaveSaveSaveSaveRetry CancelCancelCancelCancelCancelRetry DeleteDeleteDeleteDeleteDeleteFailed name@…name@…rohit|rohitlockedchecking! invalid 28 cells. Each one is a decision you either make deliberately, or make by accident in code.
Draw the grid once per component. Reading a row tells you if the component is consistent across states; reading a column tells you if a state is consistent across components (is focus the same ring everywhere?).
State matrix: pick a state
Publish
Post title
Systems for one
Shown on your public page.
state: default
Default: the resting state. Everything else is a deliberate change from it.

Rules that make states consistent

  • One recipe per state across components. Hover always mixes about 10 to 20% of the text color into the background. Focus is always the same 2 px ring in color-focus with a 2 px offset. Pressed always darkens a little more than hover.
  • State never changes size. A loading button keeps its width; an error field does not jump down the page. Layout shifts during interaction feel broken and make people misclick.
  • Never color alone. Error adds an icon and a message; selected adds a checkmark or weight change. This is required for people with color vision deficiencies and helpful for everyone.
  • Disabled is a last resort. A disabled button tells people "no" but not "why". Often it is better to keep the button enabled and explain on press what is missing (see the next chapter).
  • Focus order follows reading order. The state matrix says how focus looks; your layout decides where it goes next.
Don't
NameRohit Email Continue ?
Silent "no". The button is gray, and nothing says why. On a long form, people scroll up and down hunting for the reason.
Do
NameRohit Email ! Enter your email to get the receipt Continue
Explain on press. The button works; pressing it moves focus to the problem and says what to do.
For the solo creator

You will not draw 28 cells in Figma for every component, and you should not. Instead build a "states" row into your torture page from Chapter 2: every component rendered in every state, side by side, in both themes. On the web, CSS can force states for review (a class like .is-hover applied alongside :hover); in SwiftUI, previews can render each state. Review the page before every release.

Do

  • Design all seven states for each core component.
  • Use one recipe per state across all components.
  • Keep size constant across states.
  • Show focus for keyboard users, always.

Don't

  • Remove focus outlines because they look "ugly".
  • Hide information behind hover on touch platforms.
  • Signal errors with red alone.
  • Disable buttons without saying why.

In the wild

  • GOV.UK focus stateGOV.UK Design System
    A very visible yellow focus highlight with a thick black underline or outline, used consistently across links, buttons and inputs. Focus can be bold and on-brand at the same time.
  • Material state layersMaterial 3
    Hover, focus, pressed and dragged are shown by an overlay of the content color at fixed opacities over the container. One recipe, every component. Encode a state as a formula, not a list of colors.
  • iOS button highlightApple
    System buttons dim or highlight immediately on touch-down and restore on release or drag-off. There is no hover on iPhone, so pressed feedback carries everything. On touch, the pressed state is your only chance to say "got it".

Common mistakes

outline: none

The default browser focus ring looks out of place, so it gets removed globally.

Replace, never remove: one :focus-visible rule with your focus token, applied to every interactive element.

Loading that changes width

The label is swapped for "Saving…" or a spinner, and the button shrinks or grows.

Keep the label in place but invisible and overlay the spinner, or set a minimum width.

Hover-only discovery

On a laptop, hover-to-reveal buttons keep rows clean.

On touch, always show the actions (or offer swipe or long-press). Test with @media (hover: none).

Check yourself

You notice your app's pressed state on buttons is a darker background, but on list rows it is a scale-down animation, and on cards it is nothing. What should you do first?

Exercise 8.1 75 minutes · on your own product

Fill in your matrix

  1. Make a grid: your core components as rows, the seven states as columns.
  2. Render or screenshot each cell from your real app. Mark empty cells (state not designed) and inconsistent cells.
  3. Write one recipe per column as a rule using tokens. Implement it.
  4. Test with keyboard only, then on a touch device.
You're done when: every cell of the grid is filled and a reviewer could read each column as one consistent rule.
BeginnerCompetentStrong
Default, hover and disabled designed.All seven states, consistent per column, keyboard tested.States encoded as formulas on tokens, shown on the torture page, tested on touch and with increased contrast.
9

Deep dive: the button

Taught by Apple HIG, Material and GOV.UK, with Polaris content guidelines

The button is the smallest complete design problem in software: hierarchy, size, label, states, placement and accessibility in one rectangle. Get it right and the rest of your system inherits the care.

Hierarchy: one primary per view

Buttons come in a ladder of emphasis. The primary button is the action most people want next, filled with your strongest color. There should be at most one per view (a screen, a dialog, a section of a long page). Secondary buttons are alternatives: outlined or tinted. Tertiary or quiet buttons are text-only, for low-stakes options like "Skip" or "Learn more". Destructive is not a level of emphasis but a purpose: it uses the danger color, and is usually not primary, because the most likely action should rarely be destroying something.

Publish Preview Save draft Delete PrimarySecondaryTertiaryDestructive the next stepan alternativelow stakesloses something max 1 per viewa fewany numberrarely primary Emphasis should match probability: the action most people take next gets the most weight. If two buttons look equally important, people stop and think.
A ladder of emphasis. Squint at any screen: the primary action should be the first thing you see, and only one thing should win.

Size and hit area

Visible size and touchable size are different numbers. Apple's guidance is a minimum hit target of 44 × 44 points; Material's is 48 × 48 dp. WCAG 2.2's minimum target size criterion (2.5.8, level AA) asks for 24 × 24 CSS pixels or enough spacing around smaller targets. On desktop with a mouse, a compact 28 to 32 px button is fine in toolbars; on touch, use 44 or more. A small icon button can have a visually small glyph inside an invisible, larger hit area.

44 pt hit area around a 20 pt glyph Too close a fingertip coversboth targets Spaced hit areas do notoverlap
Design the invisible rectangle. The dashed boxes are what your component must guarantee, whatever the visible size.

Labels: verbs that predict the result

A button label should say what will happen, as a verb, in sentence case: "Save note", "Send invite", "Delete 3 photos". Avoid "OK", "Yes", "Submit" and "Confirm", which make people re-read the question to know what they are agreeing to. In a dialog, the confirming button repeats the verb from the title: "Delete draft?" → "Delete". Polaris and GOV.UK both document this; the full craft is in Words.

Don't

Are you sure?

This can't be undone.

NoYes
Labels that hide the action. Sure about what? "Yes" makes people scroll back to understand what they are saying yes to.
Do

Delete “Trip notes”?

It will be removed from all your devices.

CancelDelete
Labels that predict. Title names the object; the button repeats the verb. Someone who reads only the button still knows what happens.

Loading without jumping

Don't
before Save changes Cancel loading Cancel Cancel jumped
Layout shift. The Cancel button slid under the pointer; the next click hits the wrong thing.
Do
before Save changes Cancel loading Saving… Cancel
Width is locked. The spinner appears in place, presses are ignored until done, and nothing moves.

Placement

Where buttons go is a platform convention more than a personal choice (Part five covers this in detail). Some rules hold everywhere: put the primary action where the eye ends its journey through the content (usually the bottom or bottom-right of a form or dialog); keep the destructive action away from the most-pressed spot; on phones, consider a full-width primary button within thumb reach at the bottom of a long form; and keep button order identical across your whole product.

Checklist: the button specification

Hierarchy and content
Size and input
States
Placement

Do

  • One primary action per view.
  • Verb labels that name the object when useful.
  • Lock width during loading.
  • Give icon-only buttons an accessible label and a tooltip on desktop.

Don't

  • Make two buttons equally loud.
  • Use "OK", "Yes" or "Submit".
  • Style links as buttons or buttons as links: links navigate, buttons act.
  • Shrink touch targets to fit a design.

In the wild

  • Stripe CheckoutStripe
    The pay button states the amount ("Pay ₹2,499" or "Pay $20.00") rather than "Submit". The label predicts the exact consequence. The best label is a promise people can check.
  • GitHub "Danger zone"GitHub repository settings
    Destructive repository actions sit in a separate, red-bordered section at the bottom of settings, and deleting asks you to type the repository's name. Distance and friction should scale with the damage.
  • iOS action sheetsApple
    Destructive options appear in red, and Cancel sits apart at the bottom of the sheet. Color and position together mark danger and escape.

Common mistakes

Every action is primary

Each action feels important to the person who built it.

Rank actions by how often people take them. Only the top one is filled.

Icon-only buttons with no name

Icons save space and look clean.

Add an accessible label (aria-label, .accessibilityLabel), and on desktop a tooltip. If the icon is not universally understood, add a text label.

Check yourself

A sheet in your reading app offers: Archive, Share, Add to collection, Delete. Archive is used most often. How should you style them?

Exercise 9.1 60 minutes · on your own product

Write your button spec

  1. Run the checklist above against your current button. Note every failure.
  2. Rewrite every button label in the app as a verb (count how many "OK"s you remove).
  3. Fix loading width and hit areas in the component itself, not on call sites.
You're done when: the checklist is fully ticked, and a list shows each label you changed, before and after.
BeginnerCompetentStrong
Labels rewritten.Checklist passes; component enforces hit area and loading width.Also audited every screen for one-primary-per-view and fixed button order across the product.
10

Patterns, and documentation for future-you

Taught by GOV.UK, with Nathan Curtis and Massimo Vignelli

Components are nouns; patterns are sentences made of them. Document both briefly, in the codebase, for the one reader who matters: you, months from now, having forgotten why.

From components to patterns

A pattern is a recurring solution to a user task, assembled from components: "confirm a destructive action" uses a dialog, a title, a body, a destructive button and a cancel button, plus rules about when to use a dialog versus an undo toast. Your product needs a handful of these, and you will reuse them on every new feature. The patterns themselves (forms, validation, empty states, lists, search, undo, onboarding) are taught in Structure & Interaction. Here the point is simpler: write down your version of each, once.

Components Patterns Dialog Button Toast Text field List row Empty state Confirm or undo Form validation Searchable list First run
Many-to-many. The same Button appears in most patterns. That is why changing a component is risky and why patterns need their own short page.

The one-page component doc

Big systems have long documentation sites. You need one page per component, ideally a comment block at the top of the component file or a Markdown file beside it, so it is updated in the same commit as the code. Every page answers the same questions in the same order:

Button v1.3 · updated Oct 2026 1 PurposeTriggers an action in place. Links navigate. 2 Use / don't useUse for actions. Not for navigation or toggles. 3 Anatomycontainer · label · optional icon · focus ring 4 APIkind, size, icon, state 5 Stateslink to torture page row 6 Contentverb, sentence case, max 3 words 7 Accessibility44 pt target, label for icon-only, Enter/Space 8 Decisions2026-03 removed "outline" kind: confused with disabled in user tests fits on one screen
Eight short sections. The decisions log (section 8) is the most valuable and the most often skipped: it is the reason future-you will not undo a choice you already tested.
/**
 * Button
 * Purpose: triggers an action in place. Use Link to navigate.
 * Use when: one clear action. Don't: toggles (use Switch), navigation (use Link).
 * API: kind = primary | secondary | quiet | destructive (default primary)
 *      size = small (desktop toolbars only) | regular | large
 *      icon? leading icon; label required even when visually hidden
 *      state = idle | loading  (disabled only with a visible reason)
 * Content: verb first, sentence case, ≤ 3 words ("Save note").
 * A11y: hit area ≥ 44pt; Enter/Space; icon-only needs a label.
 * Decisions:
 *   2026-03 dropped "outline" kind: testers read it as disabled.
 *   2026-07 loading keeps width (layout shift caused misclicks).
 */
Key idea

The 1970 New York subway standards manual is still readable half a century later because each page shows the rule, the measurements and the example together. Aim for that: rule, numbers, example, reason.

For the solo creator

Documentation that lives in a separate wiki will be out of date within a month. Keep it in the same file as the component, or a Markdown file next to it, and update it in the same commit. When you work with an AI coding assistant, these comment blocks double as instructions it can read, so it uses your components the way you intended (see Designing with AI).

Do

  • Document each component in eight short sections, beside the code.
  • Record decisions with a date and a reason.
  • Write each pattern as a short recipe: when, which components, which rules.
  • Link to the torture page for states.

Don't

  • Build a documentation website for a product with one maintainer.
  • Document what the code already says (prop types) at length.
  • Skip "when not to use"; it prevents most misuse.
  • Leave decisions in your head or in chat history.

In the wild

  • GOV.UK component pagesGOV.UK Design System
    Each component page has "When to use", "When not to use", "How it works", and a section on research findings and known issues. "When not to use" and recorded research are what make a system trustworthy.
  • Architecture decision recordsSoftware practice, from Michael Nygard's 2011 post
    Engineers record decisions as short dated documents with context, decision and consequences. The same format works for design decisions. A decision without a reason will be relitigated.

Common mistakes

Writing docs for an audience that doesn't exist

You copy the structure of a big company's docs site, with intro pages and contribution guidelines.

Write for one reader (future-you) and one question: "can I use this here, and how?"

No "when not to use"

Usage feels obvious while you are building.

Write one line per component: what it is often mistaken for, and what to use instead.

Check yourself

Eight months ago you removed swipe-to-delete from list rows after people deleted items by accident. Today it seems like a nice addition. What would have stopped you from re-adding it?

Connect the ideas · Part three

You are adding a "Restore from trash" button. Using Chapters 7 to 10, which option is most consistent with a healthy system?

Exercise 10.1 90 minutes · on your own product

Document five components and three patterns

  1. Write the eight-section doc block for your five most used components, inside their files.
  2. Write three pattern recipes you use repeatedly (for example confirm-or-undo, form validation, empty state): when to use, which components, the rules.
  3. For each, add at least one dated decision, even if it is "2026-11: chose X over Y because Z".
You're done when: five doc blocks and three pattern recipes exist in the repository and each has at least one dated decision.
BeginnerCompetentStrong
Purpose and API written.All eight sections, including when-not-to-use and decisions.Patterns link to the components and to the torture page; an AI assistant given only these docs builds a new screen correctly.
Part four

Code is the system

For a team, the design file and the codebase are kept in sync by people whose job that is. For you, keeping two sources of truth is a tax you pay on every change. This part is about choosing where the system lives.

11

Designing in code versus Figma

Taught by the practice of designer-engineers, with Figma, Tailwind and SwiftUI

A solo creator should keep one source of truth, and it should usually be code. Use a drawing tool to explore, then promote what you decide into tokens and components in code, where it can no longer drift.

This is not an argument against Figma or sketching. Drawing tools are faster than code for divergent thinking: trying ten layouts in ten minutes, laying screens side by side, making a flow you can see at once. Code is better for convergent work: real data, real states, real text sizes, real devices, and above all, a system that is impossible to violate because the product is built from it. The question is not which tool, but where each decision finally lives.

ActivityDrawing tool (Figma, Sketch, paper)Code (browser, SwiftUI previews)
Exploring many directionsFast; ten variants side by sideSlower; each variant is real work
Seeing a whole flowExcellent; screens on one canvasHard without extra tooling
Real content and edge casesManual; easy to fake the happy pathNatural; real data, empty and long cases
States, motion, responsivenessApproximated, drawn one at a timeExact and free once built
Accessibility (text size, screen readers)Mostly invisibleTestable directly
Staying in sync with the productDrifts unless maintainedIs the product

The concepts map one to one

The good news is that modern drawing tools have converged on the same model as CSS and SwiftUI. If you learn the concepts once, you can move between them without translation loss.

FigmaCSSSwiftUI Auto layout, horizontaldisplay:flex; flex-direction:rowHStack { … } Gap between itemsgap: var(--space-3)HStack(spacing: Space.s3) Paddingpadding: var(--space-4).padding(Space.s4) Hug contentswidth: fit-content.fixedSize() Fill containerflex: 1.frame(maxWidth: .infinity) Variables (with modes)--color-text (+ media query)Color("Text") (+ appearance) Component + propertiescomponent + propsView + init parameters
Same ideas, three spellings. Figma's auto layout behaves much like flexbox, and Figma variables (introduced in 2023) work like CSS custom properties with modes for themes. Designing in Figma with auto layout and variables is already half-designing in code.

Your system in each stack

/* tokens.css is the system; components read only semantic tokens */
.button {
  min-height: 44px;
  padding: 0 var(--space-4);
  border-radius: var(--radius-control);
  font: 600 var(--text-body-size)/1 var(--font-ui);
  background: var(--button-primary-bg);
  color: var(--button-primary-label);
  transition: background-color var(--duration-fast) var(--ease-standard);
}
.button:hover  { background: color-mix(in srgb, var(--button-primary-bg) 85%, var(--color-bg)); }
.button:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

Plain CSS custom properties need no build step and theme at runtime. A strong default for small web products.

One source of truth across platforms

If you ship to more than one platform, put tokens in one neutral file and generate the rest. Tools like Style Dictionary read a token JSON file and output CSS variables, Swift, Kotlin or XML resources. Figma can import variables too, either through plugins or its API, so even your design file becomes an output. The arrow always points one way: from the token file outward.

tokens.json the only source build step tokens.css Tokens.swift Colors.kt Figma variables Never edit an output by hand. If Figma and code disagree, code wins and Figma is regenerated.
Arrows point one way. Most solo creators on one platform do not need this pipeline; a single token file in their main language is enough. Add the build step on the day you add a second platform.

A workflow that works for one person

  1. Sketch to diverge. Paper or Figma, fast and ugly, using your real tokens if your tool has them. Ten options, not one.
  2. Pick one, build it in code. Use existing components only. If something new is needed, write it as a local, unstyled piece first.
  3. Review on the device. Real data, both themes, largest text size, keyboard, touch.
  4. Promote. If the new piece appears a second or third time, move it into the system: tokens, component, doc block.
  5. Regenerate the design file only if you still use one for exploration.
For the solo creator

The real cost of a design tool for a soloist is not the subscription; it is the second source of truth. If you love Figma, use it for exploration and for flows, with variables mirroring your token names, and accept that the file is a sketchbook, not a specification. If a mockup and the shipped product disagree, the shipped product is right by definition, and you update the sketchbook only when you need it again.

Myth

"Real designers design in Figma first." Many respected product designers who build their own products work largely in code or move to code early, because the material (the browser, the device) is the truth. Bret Victor's argument, covered in the earlier field guide, applies: the closer you are to the real thing, the faster you see what is wrong.

Do

  • Explore in a drawing tool; decide in code.
  • Name Figma variables exactly like your code tokens.
  • Use auto layout so your drawings behave like flexbox.
  • Keep one token file and generate everything else from it.

Don't

  • Maintain a pixel-perfect mirror of your app in Figma.
  • Edit generated token files by hand.
  • Approve a design you have only seen in a drawing.
  • Let tool habits (absolute positioning) leak into your thinking.

In the wild

  • SwiftUI previews and Xcode canvasApple, 2019–today
    Live previews render views in multiple configurations (dark mode, large text, devices) next to the code. For Apple platforms, the code editor is now a reasonable design tool. Design where the states are real.
  • Figma Dev Mode and variablesFigma, 2023
    Figma added variables with modes and a developer-focused inspection mode, explicitly to close the gap between design files and code. The industry is converging on tokens as the shared language.
  • Style Dictionaryoriginally Amazon, open source
    A widely used build tool that turns one token source into platform files. The pipeline is solved; you only need to choose to use it.

Common mistakes

Two sources of truth

You designed it in Figma, then tweaked it in code, then forgot which is current.

Declare code the source. Treat design files as disposable sketches, dated and archived.

Designing the happy path only

Drawing tools make perfect content effortless and edge cases tedious.

Promote to code early and review on the torture page from Chapter 2.

Fighting the platform's components

You draw a custom control in Figma, then spend days rebuilding what SwiftUI or the browser already gives you.

Start from native controls and style them through tokens; customize only where your product's identity needs it.

Check yourself

Your Figma file says the card radius is 14; your code says 12. You are unsure which is current. What does the principle of one source of truth tell you?

Connect the ideas · Part four

You add an Android app to your iOS app. Combining Parts two and four, what is the cleanest way to share your design system?

Exercise 11.1 60 minutes · on your own product

Collapse to one source of truth

  1. List every place a design value currently lives: code, design files, notes, screenshots.
  2. Choose the source (almost certainly your token file in code).
  3. Rename Figma variables or styles (if any) to match code token names exactly.
  4. Write a one-paragraph "how a design decision flows" note in your repository's README.
You're done when: there is exactly one file where a color, space or radius value can be changed, and the README says so.
BeginnerCompetentStrong
Source chosen, names partly aligned.One source, names aligned, README written.Outputs generated automatically (or there is only one platform and one file), and design files are dated sketches.
Part five

Platforms

Your design system lives inside a bigger one. Every operating system has conventions its users learned from thousands of hours in other apps: where back is, how dialogs behave, where settings live. Respecting them is the cheapest usability you will ever get.

12

Conventions, and Jakob's law

Taught by Jakob Nielsen, with Jon Yablonski and Don Norman

People spend most of their time in other products, so they expect yours to work like those. Every convention you follow is something they already know; every convention you break is something they must learn.

Users spend most of their time on other sites.Jakob Nielsen, on what is now called Jakob's Law of the Internet User Experience, 2000

Nielsen was writing about websites, but the idea generalizes to every platform. Jon Yablonski's Laws of UX made it widely known as "Jakob's law": users prefer your product to work the same way as all the others they already know. Your app is one of perhaps eighty on someone's phone. Their thumb has learned where back is, what a swipe does and which side of a dialog confirms, from all the others. Norman's term for this is a conceptual model people bring with them; conventions are shared conceptual models.

Where a person's expectations come from Every other app, plus the OS itself: messages, mail, maps, browser, settings… Your app A small slice of their day. Their habits are trained elsewhere, so the platform's conventions are your users' real mental model. (Illustrative.)
Your app is the exception, not the rule. Unless people use your product for hours a day, their expectations are set by the platform and its best-known apps.

What counts as a convention

A convention is any behavior people predict without being told: tapping a back chevron goes back; ⌘Z undoes; a red button destroys; a blue underlined phrase on the web is a link; swiping left on an iPhone list row reveals actions; settings on a Mac live under the app menu. Conventions live at every level: gestures, shortcuts, placement, iconography, words ("Settings" on most current platforms; older macOS used "Preferences" until 2022), and timing.

When to break one

Break a convention only when all three are true: the convention measurably hurts your core task, your alternative is clearly better for that task, and you can teach the new behavior in the moment it is needed. Loren Brichter's pull-to-refresh in Tweetie 2 (2009) is the classic case of a broken convention that became one; it is famous precisely because such successes are rare. For everything outside your core innovation, be boring. Spend your novelty budget where it matters.

The conventions table

Here are the conventions that most often trip up people who build for several platforms. Treat it as a starting checklist and verify details in each platform's current guidelines; they change across releases.

ConventioniOS / iPadOSmacOSAndroidWindowsWeb
Going backBack button top-left, labelled with the previous screen; swipe from the left edgeNo system-wide back; ⌘[ in browsers and Finder-like apps; close a window or sheetSystem back gesture from either screen edge (or back button); predictive back previews the destinationBack button in the app's title bar area where navigation is deep; Alt+LeftThe browser's back button. Your app must push real history entries
Dialog button orderTwo buttons side by side; Cancel on the left, the likely action on the rightDefault button on the right; Cancel to its left; Esc cancels, Return triggers defaultActions aligned right as text buttons; confirming action on the far rightPrimary button first (left), Close/Cancel last (right)No single rule; pick one order and use it everywhere
Destructive actionsRed "destructive" style in alerts and action sheets; swipe actions on rowsName the action ("Delete"); avoid making it the default buttonConfirm in a dialog, or act and offer Undo in a snackbarConfirm with a ContentDialog naming the actionPrefer undo; confirm big losses; GitHub-style "type the name" for the irreversible
SettingsA settings screen inside the app (often from a tab or profile); some apps also use the Settings appApp menu → Settings… (⌘,) opening a separate window; changes apply immediatelySettings screen, usually reached from a menu, the profile or navigationSettings at the bottom of the navigation pane, often a gearAccount or avatar menu; a /settings URL
Main navigationTab bar at the bottom (iPhone); sidebar on iPadSidebar plus toolbar; every command also in the menu barNavigation bar at the bottom (3–5 destinations); navigation rail or drawer on larger screensNavigation view (left pane or top)Header links or sidebar; every view has a URL
UndoUndo buttons or toasts; shake to undo exists but is little known⌘Z, with the Edit menu naming what will be undoneSnackbar with "Undo"Ctrl+ZToast with "Undo"; ⌘/Ctrl+Z inside editors
Text sizeDynamic Type: support it fullyRespect system text size settings where availableFont scale setting: use sp unitsText scaling settingBrowser zoom and default font size: use rem, never block zoom
One dialog, five platforms
iOS: a centered alert with a short title and message. Two buttons share a row: Cancel on the left, the action on the right in the red destructive style. Keep alerts rare; for most deletions, act immediately and offer undo, or use an action sheet from the button that triggered it.
Watch out

Platform details change across releases: Apple renamed macOS Preferences to Settings in macOS Ventura (2022), Android added predictive back, and Apple introduced a new visual material, Liquid Glass, across its platforms in 2025. Re-read the current guidelines for your main platform once a year, ideally right after the annual developer conferences (WWDC in June, Google I/O in May).

For the solo creator

The cheapest way to follow conventions is to use the platform's own components. A native SwiftUI .alert, a Material 3 AlertDialog or a browser <dialog> get the button order, keyboard handling, accessibility and next year's visual update for free. Custom components should be the exception you can justify.

Do

  • Learn your main platform's conventions before designing your first screen.
  • Use native components where they exist.
  • Break conventions only in your core innovation, and teach the break.
  • Keep your own conventions consistent where the platform has none (web).

Don't

  • Port one platform's conventions to another.
  • Reinvent back, undo, settings or share.
  • Assume conventions are permanent; check yearly.
  • Override system gestures (edge swipes) with your own.

In the wild

  • Tweetie pull-to-refreshLoren Brichter, Tweetie 2, 2009
    A new gesture for a core, repeated task, discoverable because the list visibly stretched. It became a convention across platforms. Break a convention only where you can do dramatically better, and make the new behavior self-explaining.
  • macOS Settings renamemacOS Ventura, 2022
    When System Preferences became System Settings, well-behaved Mac apps renamed their own "Preferences…" menu item to "Settings…". Following conventions includes following their changes.
  • Electron apps on the Macvarious
    Cross-platform desktop apps are often criticized by Mac users when they ignore the menu bar, standard shortcuts or native text behavior, and praised when they respect them. Users judge you by the platform's standard, not your framework's limits.

Common mistakes

Designing on one platform, shipping on all

You use an iPhone, so every platform gets iPhone conventions.

Spend one hour using the target platform's best built-in apps before designing for it.

Custom gestures that collide with system ones

Edge swipes look like free real estate.

Leave screen edges to the system (back, home, notifications). Put custom gestures inside content.

Check yourself

Your Mac app shows a dialog "Discard unsaved changes?" with buttons [Discard] [Keep editing], and Discard is the highlighted default. A user presses Return out of habit and loses work. What is the convention-respecting fix?

Exercise 12.1 45 minutes · on your own product

Convention audit

  1. For your main platform, copy the seven rows of the conventions table into a note.
  2. For each row, write what your app does today. Mark matches, mismatches and "not applicable".
  3. For each mismatch, decide: fix, or justify as a deliberate break tied to your core task.
You're done when: you have the filled table and a fix list ordered by how often users hit each mismatch.
BeginnerCompetentStrong
Table filled for one platform.Mismatches fixed or justified in writing.Also compared against two of the platform's best apps and adopted one convention you had not noticed.
13

iOS and iPadOS essentials

Taught by Apple's Human Interface Guidelines

iPhone design is built on a few strong structures: navigation stacks you can swipe back through, a tab bar for top-level places, sheets for focused tasks, and text and symbols that scale with the user's settings. Learn these and most screens design themselves.

Library ‹ Library Calm tech Navigation stack Push to go deeper. Back is top-left, labelled with where you came from, and a left-edge swipe always works.
Never break the swipe. Custom transitions and full-screen gestures that block the edge swipe are among the most common complaints about iOS apps.
Library ReadFindNotesYou Tab bar Top-level places, always visible, usually 3 to 5. Each tab keeps its own stack.
Places, not actions. Tabs are where you go, not what you do. A "+" tab that opens a composer is a common misuse.
CancelNoteDone medium large Sheet with detents A focused task above the current context. Swipe down to dismiss; confirm if data is lost.
Sheets keep context. A medium detent shows the screen beneath, so people remember what they were doing.
safe area Safe areas Backgrounds run edge to edge; text and controls stay inside the safe area.
Edge to edge, but safe. Use safe-area layout guides (SwiftUI does this by default; on the web, env(safe-area-inset-*) with viewport-fit=cover).

The essentials, briefly

Navigation bar and large titles
The top bar shows where you are and holds the back button and a few actions. Large titles that collapse as you scroll work well for top-level screens; detail screens usually use inline titles.
Tab bar
Top-level destinations at the bottom, always visible on iPhone. Each tab remembers its own navigation stack. Do not hide the tab bar on drill-down unless the task is immersive.
Sheets and full-screen covers
Sheets present a focused task (compose, edit, filter) with Cancel and Done; detents let a sheet sit at medium height. Full-screen covers are for immersive tasks (a camera, a reader).
Swipe back
A left-edge swipe pops the stack, interactively. People use it constantly; breaking it makes an app feel cheap.
SF Symbols
Apple's icon set of thousands of symbols, designed to align with San Francisco text and to scale and change weight with it. Using them keeps icons consistent with the system and with Dynamic Type.
Dynamic Type
People choose their text size, from smaller than the default (17 pt body) up to accessibility sizes around three times larger. Use text styles, let layouts reflow (stack horizontally arranged items vertically at large sizes), and test at the largest size.
Swipe actions and context menus
Rows can reveal actions on swipe; long-press shows a context menu with a preview. Both are shortcuts, so the same actions must also be reachable another way.
Haptics
System controls give subtle haptic feedback (toggles, pickers). Add your own sparingly, for confirmations that matter (see Motion & Feel).
Don't
Library 3 sectionshidden top-left iswhere Backlives
Hidden places, contested corner. Out of sight is out of mind, and on iOS the top-left corner belongs to Back.
Do
Library all placesone tap,thumb zone
Visible places in the thumb zone. One tap to anywhere, and Back keeps its corner.

iPadOS: not a big iPhone

On iPad, the tab bar often becomes a sidebar, and split views show a list and its detail side by side (the classic master–detail layout). People use iPads with keyboards, trackpads and Apple Pencil, so hover effects, keyboard shortcuts and drag and drop matter. Apps also run in resizable windows next to other apps, so your layout must adapt to many widths, not just "iPad portrait" and "iPad landscape" (Chapter 18). Recent iPadOS versions moved further toward Mac-like windowing and added a menu bar, which makes the Mac conventions in the next chapter increasingly relevant on iPad too.

For the solo creator

SwiftUI's NavigationStack, TabView, NavigationSplitView, .sheet with .presentationDetents, SF Symbols and text styles give you nearly all of this for free, including next year's visual changes. Your design system on iOS is mostly tokens, a few ButtonStyles and ViewModifiers, and the discipline not to replace system structures with custom ones.

Do

  • Use a tab bar for 3 to 5 top-level places on iPhone.
  • Keep the left-edge swipe back working everywhere.
  • Use text styles and test at the largest Dynamic Type size.
  • Use SF Symbols for system-like actions.
  • Adapt to a sidebar and split view on iPad.

Don't

  • Hide top-level navigation behind a hamburger.
  • Put custom actions in the top-left corner.
  • Fix text sizes in points that ignore Dynamic Type.
  • Stretch the iPhone layout across an iPad screen.

In the wild

  • Apple Notes and MailApple
    Navigation stacks on iPhone, a split view with sidebar on iPad and Mac, swipe actions on rows, sheets for compose. Study the system's own apps first; they define the conventions.
  • Things 3Cultured Code, 2017
    A highly distinctive iPhone app (the "Magic Plus" button you drag to insert a task) that still respects the stack, swipe back, Dynamic Type and system share sheets. Innovate in your core interaction; follow the platform everywhere else.
  • HalideLux, 2017–today
    A camera app with a custom, gesture-rich interface that is immersive by necessity, yet uses system conventions for settings and the photo library. Immersive tasks earn custom UI; administrative tasks do not.

Common mistakes

Full-screen modal for everything

It looks bold and hides distractions.

Use sheets for short tasks so people keep context, and full-screen only for immersive ones.

Breaking Dynamic Type

Layouts are designed at the default size and break at larger ones, so text sizes get fixed.

Use text styles, allow wrapping, switch horizontal stacks to vertical at accessibility sizes.

Web-style navigation in an iOS app

Teams porting from the web bring headers, footers and hamburger menus.

Map web sections to tabs and web pages to stack screens.

Check yourself

Your reading app has Library, Search, Highlights and Settings. You also want a prominent way to add a new article by URL. On iPhone, where should "Add" go?

Exercise 13.1 45 minutes · on an iPhone

Five-structure check

  1. Open your app (or one you use daily) on an iPhone and test: tab bar or equivalent, swipe back on every pushed screen, sheet dismissal, largest Dynamic Type, safe areas in landscape.
  2. Record each failure with a screenshot.
  3. For each failure, write the native structure that should replace the custom one.
You're done when: you have five pass or fail results with screenshots and a fix for each fail.
BeginnerCompetentStrong
Checked navigation and swipe back.All five checked with fixes.Fixes shipped, and the iPad layout uses a sidebar or split view.
14

macOS: the native feel

Taught by Apple's Human Interface Guidelines, with the long tradition of Mac indie developers

A Mac app is a keyboard-and-pointer tool that lives in windows, with every command in the menu bar. "Native feel" is not a look; it is dozens of small behaviors people expect without thinking.

Mac users are famously particular. They notice when ⌘, does not open settings, when a text field does not support the standard emacs-style shortcuts, when a window does not remember its size, or when the menu bar is empty. Each is small. Together they decide whether your app feels like a guest or a resident.

●MarginFileEditViewWindowHelp Library All notes HighlightsArchive TagsDesignReading Search All notes InspectorWords: 1,204Tags: Design 1 2 3 4 5 6 1 Menu bar: every command2 Window controls3 Unified toolbar4 Sidebar5 Content list6 Inspector
Six regions people expect. Not every app needs all six, but each region has a conventional job. A Mac app that puts commands only in a hamburger menu or a floating button is ignoring the strongest convention on the platform.

The behaviors that make it feel native

  1. A complete menu bar. Every command your app has should appear in a menu, with its shortcut shown. The menu bar is how Mac users discover features and learn shortcuts, and it is searchable from Help.
  2. Standard shortcuts, untouched. ⌘N new, ⌘O open, ⌘W close window, ⌘Q quit, ⌘, settings, ⌘Z / ⇧⌘Z undo and redo, ⌘F find, ⌘C/⌘V copy and paste. Never reassign them.
  3. Settings in their own window. App menu → Settings…, opened with ⌘,. Usually a small window with a toolbar of tabs. Changes apply immediately; there is no Save button.
  4. Windows that behave. Multiple windows where documents warrant them, remembered size and position, full-screen support, and correct behavior when the last window closes.
  5. Native text. Text fields that support system spelling, standard editing shortcuts, services and the system font panel where relevant. Re-implementing text editing is where most cross-platform apps fail.
  6. Right-click everywhere. Context menus on every object, mirroring commands that also exist in the menu bar.
  7. Density for pointers. A mouse is precise, so controls can be smaller and denser than on touch: regular controls are typically compact compared with iOS.
  8. Drag and drop, in and out. Files, text and images should drag into your app and out to Finder and other apps.
Don't
Settings Save replaces your work
A web page in a Mac costume. Settings replace the document view, need an explicit Save, and are not reachable with ⌘,.
Do
GeneralLookSync Open at login Font sizeMedium ▾ ⌘, opens this window
The Mac way. A separate small window, tabs in the toolbar, changes apply at once, ⌘, opens it and ⌘W closes it.
For the solo creator

If you build with SwiftUI, a Settings scene gives you the settings window and the ⌘, menu item; .commands adds your menus; .keyboardShortcut wires shortcuts and shows them in menus. If you build with a web technology (Electron, Tauri), budget explicit time for a real menu bar, standard shortcuts, native context menus and window state; these are the details Mac reviewers mention first.

Do

  • Put every command in the menu bar with its shortcut.
  • Open settings with ⌘, in a separate window; apply changes immediately.
  • Support multiple windows when people work on several things.
  • Use native text controls and context menus.

Don't

  • Ship an iPad layout unchanged on the Mac.
  • Override standard shortcuts.
  • Put a Save button on settings.
  • Use touch-sized controls everywhere on a pointer platform.

In the wild

  • ReederSilvio Rizzi
    A three-pane reader (sources, list, article) with full keyboard navigation and a calm, native look. Mentioned in the earlier field guide for its restraint; it is also a model of Mac conventions. Calm and native are the same direction.
  • RaycastRaycast, 2020–today
    A launcher built around the keyboard: every action shows its shortcut, and a single action panel (⌘K) lists everything you can do with the selected item. On the Mac, discoverable shortcuts are a feature, not an extra.
  • Mac Catalyst appsApple, 2019–today
    iPad apps brought to the Mac with Catalyst feel native only when developers add menus, shortcuts, pointer-sized controls and Mac-style settings. Porting is not adapting.

Common mistakes

An empty menu bar

Commands live in toolbars and buttons, so menus feel redundant.

Mirror every command in the menu bar. It is the Mac's command palette and its documentation.

Single-window thinking

Mobile apps have one window, and the port inherits that.

Ask whether people would want two documents side by side. If yes, support multiple windows.

Check yourself

You port your note app from iPad to Mac. Testers say it "feels like an iPad app". Which change will most improve the native feel?

Exercise 14.1 30 minutes · on a Mac

The ten-shortcut test

  1. In your Mac app (or any app you use), try: ⌘N, ⌘W, ⌘Q, ⌘,, ⌘Z, ⇧⌘Z, ⌘F, ⌘A in a text field, right-click on an item, and dragging a file in.
  2. Open each menu and check every command has a shortcut or a reason not to.
  3. Close and reopen the app: did it remember window size and position?
You're done when: you have a ten-row pass or fail table and fixes for every fail.
BeginnerCompetentStrong
Shortcuts tested.Shortcuts, menus and window state tested with fixes.Fixes shipped; every command is in the menu bar and has a context-menu equivalent where relevant.
15

Android, Material 3, and Windows briefly

Taught by Material Design 3, with Microsoft Fluent 2

Android has its own grammar: a system back gesture, a bottom navigation bar, a floating action button for the main creative action, and colors that can come from the user's wallpaper. Respect it instead of shipping your iPhone app in disguise.

Library + LibrarySearchNotesYou Top app bar Back gesture fromeither edge, with apredictive preview FAB: the screen'smain creative action Navigation bar:3–5 destinations
Material's phone grammar. The selected destination gets a pill-shaped indicator. The FAB is optional and should hold the one action that defines the screen (compose, add), never navigation.

The essentials

System back
Android has a system-wide back action (a swipe from either edge in gesture navigation, or a button). Your app must handle it correctly at every level: closing a sheet, leaving a screen, or exiting at the root. Predictive back (introduced across Android 13 to 15) shows a preview of where back will go as the user swipes, so your navigation must be honest about its history.
Top app bar
Title, navigation icon (up arrow or menu) and a few actions, with an overflow menu for the rest. It can collapse on scroll.
Navigation bar, rail and drawer
Bottom navigation bar for 3 to 5 destinations on phones; a navigation rail on medium widths (tablets, unfolded foldables); a navigation drawer for many destinations or large screens.
Floating action button (FAB)
One prominent button for the primary creative action of a screen. Not every screen needs one.
Snackbars
Brief messages at the bottom, often with a single action such as "Undo". The Material way to confirm an action without interrupting.
Dynamic color
Since Android 12 (2021), Material You can generate the color scheme from the user's wallpaper. Apps that use Material color roles adapt automatically; you can opt in fully, partially, or keep your brand colors.
Edge to edge
Recent Android versions draw apps behind the status and navigation bars by default (enforced for apps targeting Android 15). Handle window insets as you would iOS safe areas.
wallpaper seed color tonal palettes primary onPrimary primaryContainer surface onSurfaceVariant roles your components read
Semantic tokens at platform scale. Dynamic color is only possible because components read roles. Your own token system works the same way, which is why Chapter 4 matters on every platform.

Windows and Fluent, briefly

If you ship on Windows, Microsoft's Fluent 2 design system and the WinUI guidance are your HIG. A few conventions matter most: Segoe UI Variable as the system typeface; materials like Mica that tint the window background with the desktop wallpaper; rounded corners (8 px for top-level windows, 4 px for controls in Windows 11); a navigation view with settings at the bottom of the pane; dialogs with the primary button first (left) and Close last; full keyboard access with Alt-key access keys; and respect for the user's contrast theme. Many solo creators reach Windows through a cross-platform framework; the same rule applies as on the Mac: budget time for the keyboard, the title bar and the system's dialog order.

Don't
‹ Library Article iOS backon Android system backskips steps
Two backs that disagree. The in-app "‹ Library" and the system gesture lead to different places.
Do
Article up arrowand backagree
One back, honest history. The up arrow and the system gesture go to the same place, so the predictive preview is truthful.
For the solo creator

With Jetpack Compose and Material 3 components, most conventions come built in: Scaffold, TopAppBar, NavigationBar, FloatingActionButton, SnackbarHost and the navigation library's back handling. Your job is to map your tokens onto the Material color scheme and type scale and decide how much of dynamic color to accept. A good compromise for a branded product: keep your accent, let surfaces follow the system.

Do

  • Handle system back correctly at every level.
  • Use a navigation bar on phones and a rail on medium widths.
  • Use snackbars with Undo for reversible actions.
  • Map your tokens onto Material roles.
  • On Windows, follow Fluent's dialog order and keyboard rules.

Don't

  • Put an iOS-style "‹ Previous" back button on Android.
  • Use a FAB for navigation or for minor actions.
  • Ignore insets in edge-to-edge layouts.
  • Ship the same screenshots to the Play Store as to the App Store.

In the wild

  • Google Keep and GmailGoogle
    FAB for compose, snackbars with Undo after archive or delete, top app bars with search. The reference implementations of Material on phones. Study the platform owner's apps before designing yours.
  • Pocket CastsAutomattic
    A podcast app with a shared visual identity on iOS and Android. Install both versions side by side and list which conventions each adapts (navigation, back, sheets, typography) and which it keeps identical. The best cross-platform apps share brand and adapt behavior.

Common mistakes

Broken back stacks

Custom navigation code does not tell the system what back should do.

Use the platform's navigation component and test back from every screen, including after deep links.

FAB for everything

It is eye-catching, so every screen gets one.

Use a FAB only where one creative action defines the screen. Elsewhere, use top bar actions.

Check yourself

A user opens your Android app from a notification straight into an article, then swipes back. What should happen?

Exercise 15.1 40 minutes · on an Android device or emulator

Back and color pass

  1. Test system back from every screen, from a deep link, and with a sheet open.
  2. Switch the wallpaper and dark theme; note what your app does with dynamic color.
  3. Decide and document your dynamic color policy (full, surfaces only, or none) and why.
You're done when: back works everywhere and your dynamic color policy is written in your token file's comments.
BeginnerCompetentStrong
Back tested on main screens.Back tested including deep links and sheets; color policy decided.Also verified predictive back previews, insets and the medium-width layout with a navigation rail.
16

The web, and the small screens: watch and widgets

Taught by Tim Berners-Lee's web, with Ethan Marcotte, Steven Champeon, Alex Russell and Frances Berriman

The web's conventions are older and deeper than any app platform's: URLs, links, the back button, text you can select and zoom. A web product that breaks them feels like a trap. One that honors them feels trustworthy.

URLs are part of the interface

On the web, every meaningful view should have its own URL. That single rule gives people back and forward, bookmarks, sharing, opening in a new tab, and restoring after a crash. Single-page apps often break it by changing the screen without changing the URL. Put the state people would want to return to in the URL: the screen in the path, filters and view modes in the query, a position in the hash.

margin.app /notes/42 ?view=split&q=calm #p-7 Pathwhich thing(screen, object) Queryhow it is shown(filters, mode) Hashwhere on the page(paragraph) If someone copies this URL to a friend, the friend should see the same thing. That is the test.
A URL is a saved state. Design it as deliberately as a screen: readable, stable, and meaningful when shared.
Don't
margin.app/app margin.app/app margin.app/app LibraryNote 42Settings Back leaves the app. Refresh loses your place. Nothing can be bookmarked or shared.
A web page pretending to be an app. Every web convention people rely on silently breaks.
Do
margin.app/library margin.app/notes/42 margin.app/settings Back, refresh, bookmarks, new tabs and sharing all work, for free.
The web's oldest feature, honored. Routing libraries make this the default; the work is only in deciding what state belongs in the URL.

The web's other conventions

  • Links navigate, buttons act. Use <a href> for anything that goes somewhere, so ⌘/Ctrl-click and middle-click open new tabs. Use <button> for actions. Do not make clickable divs.
  • Text is text. People select, copy, translate, search with ⌘/Ctrl-F, and zoom. Do not render text as images or disable selection on content.
  • Responsive by default. Ethan Marcotte's "Responsive Web Design" (A List Apart, 2010) established fluid grids, flexible images and media queries. Today container queries let components respond to their own space too (Chapter 18).
  • Progressive enhancement. A term coined by Steven Champeon in 2003: start with HTML that works, then layer CSS and JavaScript on top. A form that submits without JavaScript, links that work before the app loads. It makes products sturdier on bad networks and old devices.
  • Respect the browser's settings. Use rem units so the user's default font size applies; never block pinch zoom; honor prefers-color-scheme, prefers-reduced-motion and prefers-contrast.
  • Progressive web apps. The term was coined in 2015 by Alex Russell and Frances Berriman for web apps that can be installed, work offline (via service workers) and send notifications. With a manifest and a service worker, your web product can live on a home screen or dock; on iOS, home screen web apps gained web push in iOS 16.4 (2023). A PWA should still behave like the web inside: URLs and all.

The small screens: watch and widgets

Watch apps and widgets are not small versions of your app. They are glances: one piece of information or one action, available in a second or two. Apple's guidance for watchOS emphasizes brief interactions; widgets on iOS, iPadOS, macOS and Android show timely content and open the app (or, since iOS 17, run a simple action with a button or toggle) when tapped.

Today24minutes read small: one fact Continue reading Calm technology 60% · 8 min left medium: one task 24 watch complication Mark read one action More than a glance or one tap? It belongs in the app, not the widget.
Glances, not apps. Each size answers one question. A widget that tries to be a dashboard is a widget nobody can read.
For the solo creator

The web is the cheapest platform to ship and the easiest to get subtly wrong. Use a router that maps URLs to screens from day one, semantic HTML for links and buttons, and a native <dialog> or a proven accessible library for overlays. Only add a watch app or widgets when you can name the one glance they answer; a single excellent widget beats three mediocre ones.

Do

  • Give every meaningful view a URL; put view state in the query.
  • Use real links and buttons.
  • Build with progressive enhancement and rem units.
  • Design widgets and watch views as single glances.

Don't

  • Hijack the back button or scrolling.
  • Disable zoom or text selection on content.
  • Make the PWA behave like a closed native app.
  • Squeeze your whole app into a widget.

In the wild

  • Linear and GitHub URLsLinear; GitHub
    Issues have short, stable, readable URLs that people paste into chat and documents. The URL is a product feature. A good URL is a shareable unit of your product.
  • GOV.UKGovernment Digital Service
    Built with progressive enhancement: pages and forms work before or without JavaScript, which matters for people on old phones and poor connections. Sturdy beats clever when everyone must be able to use it.
  • Apple Fitness ringsApple Watch, 2015–today
    Three rings readable in under a second, the canonical example of a glanceable complication. The smaller the screen, the fewer the questions.

Common mistakes

Clickable divs

Frameworks make any element clickable, and a div is easy to style.

Use a or button. You get keyboard support, screen reader roles and open-in-new-tab for free.

State lost on refresh

Filters and tabs live only in memory.

Write view state to the URL query and read it on load.

Dashboard widgets

You want to show everything your app knows.

Ask what single question people would glance at your widget to answer. Show that.

Check yourself

In your web reading app, the list can be filtered by "unread" and sorted by "longest". A user wants to send this exact view to a friend. What should you build?

Connect the ideas · Part five

You are adding "Delete note" to your app on iPhone, Mac, Android and web. Which plan respects the platforms while keeping one system?

Exercise 16.1 45 minutes · on your web product (or one you use)

The web conventions test

  1. Click through five screens, then press back five times. Does it retrace your steps?
  2. Refresh on a filtered view. ⌘/Ctrl-click a list item. Copy a URL into a private window.
  3. Zoom to 200%. Tab through the page with the keyboard. Disable JavaScript and load the home page.
You're done when: you have a pass or fail list for all eight checks and a fix for each fail.
BeginnerCompetentStrong
Back and refresh tested.All eight checks with fixes planned.Fixes shipped; view state lives in URLs; the core page renders without JavaScript.
Part six

Across platforms, sizes and years

Your product will run on more than one platform, in windows of every size, with fingers, pointers, keyboards and pens, for years. This part is about adapting one product without losing its identity, and keeping its system healthy as it ages.

17

Cross-platform: adapt, don't copy

Taught by the practice of multi-platform indie developers, with Apple and Material guidance

Share what makes your product yours (its content, model, brand and voice). Adapt what makes each platform familiar (navigation, controls, gestures, dialogs, shortcuts). A feature should feel designed for each device, not shipped to it.

Shared and native layers

Shared everywhere Content and data model: notes, highlights, sync, what things are called Features and flows: what you can do, in what order Brand: tokens for color, type personality, icon, voice and words Adapted per platform Navigationtabs, sidebar, URLs Controlsnative components Inputgestures, shortcuts Systemdialogs, share, menus
The line between brand and behavior. If a decision is about who you are, share it. If it is about how this device works, adapt it.

What your framework means for design

Your choice of technology shapes how much adaptation is free and how much is work. This is a design decision as much as an engineering one.

ApproachHow it rendersDesign implication
Native per platform (SwiftUI, Jetpack Compose, WinUI)The platform's own componentsConventions and yearly updates come free. Cost: building each platform separately; sharing only tokens and logic.
React NativeJavaScript driving native platform viewsMany controls feel native by default; navigation and gestures need careful platform-specific choices.
FlutterDraws its own widgets with its own enginePixel-identical everywhere by default, which is the danger. Use its Material and Cupertino widgets deliberately per platform, and check text editing, scrolling physics and accessibility.
Kotlin / Compose MultiplatformShared Kotlin logic; shared or native UIShare the model and decide per screen whether the UI is shared or native.
Electron, TauriWeb views inside a desktop shellWeb conventions inside a desktop app. Budget for menus, shortcuts, window behavior and native dialogs.
Web and PWAThe browserOne codebase, the web's conventions. Adapt layouts responsively; respect each OS's keyboard and text habits.

Worked example: "Highlight and add a note" on four platforms

Imagine a reading app called Margin. The feature: while reading, select a passage, highlight it, and optionally add a note. Shared across platforms: the highlight colors (tokens), the word "Highlight", the data model (passage, color, note, date), and sync. Everything else adapts.

CopyHighlightShare CancelAdd noteSave iPhone System edit menu gets "Highlight"; note in a medium sheet; light haptic.
iPhone. Extend the system's selection menu rather than inventing a toolbar. The sheet keeps the passage visible above it.
Copy⌘C Highlight⇧⌘H Add Note…⌥⌘N Look Up Notes Mac Context menu and menu bar item with a shortcut; the note lives in an inspector so reading is never covered.
Mac. Same command, now in the Edit menu, the context menu and on the keyboard. No sheet: a sidebar uses the extra width.
CopyHighlight⋮ Add a note Save Highlight savedUndo Android Floating selection toolbar; bottom sheet; snackbar with Undo; system back closes sheet.
Android. Act first, offer Undo in a snackbar. Material's bottom sheet holds the optional note.
margin.app/read/88#h-3f2 NoteLink SavedUndo H Web Floating toolbar near the selection, a keyboard shortcut, a toast with Undo, and a URL anchor so the highlight is shareable.
Web. The web's superpower is the link: every highlight gets an address. The toolbar must also work by keyboard.
DecisioniPhoneMacAndroidWeb
TriggerSystem edit menuContext menu, Edit menu, ⇧⌘HSelection toolbarFloating toolbar, H key
Note entryMedium sheetInspector sidebarBottom sheetPopover under toolbar
ConfirmationVisual + light hapticVisual, appears in sidebarSnackbar with UndoToast with Undo
UndoUndo in toast or edit menu⌘ZSnackbarToast, ⌘/Ctrl+Z
SharedColors (tokens), the word "Highlight", the data model, sync, ordering of colors, the note's character limit
For the solo creator

You cannot launch on four platforms at once, and you should not. Ship on the platform your first users live on, design it natively, and keep the brand and model clean so the second platform is an adaptation rather than a rewrite. When you add a platform, spend the first day using its best apps, then write the adaptation table above for your three most important features before writing code.

Do

  • Share tokens, words, data model and features.
  • Adapt navigation, controls, input and system integration.
  • Write an adaptation table per key feature.
  • Use each platform's extra capabilities (Mac menus, web links, Android snackbars).

Don't

  • Ship the same screenshot four times.
  • Let a framework's defaults decide your conventions.
  • Rename features per platform.
  • Launch everywhere before you are good anywhere.

In the wild

  • iA WriterInformation Architects, Mac, iOS, Windows, Android
    A distinctive, shared identity (typography, focus mode, Markdown handling) delivered through apps that each follow their platform's file handling, menus and controls. Identity in the content area; conventions in the chrome.
  • Apple's own apps on iPhone, iPad and MacNotes, Mail, Reminders
    Same data and features, but a stack on iPhone, a split view on iPad, and windows with menus and an inspector on Mac. Even the platform owner adapts rather than copies.
  • Slack and other Electron appsvarious
    Largely identical across Windows, Mac and Linux, which makes them easy to learn once but often criticized for not feeling native on any. Uniformity is a trade-off; know when you are making it.

Common mistakes

Lowest common denominator

Features are limited to what works the same everywhere.

Use each platform's strength: keyboard shortcuts on Mac, links on the web, widgets on phones. Parity of capability, not of interface.

Brand in the chrome

You want every platform to look unmistakably like you, so you restyle the navigation bars and controls.

Put brand in content, color accents, typography and voice. Leave the platform's chrome mostly alone.

Check yourself

Your Flutter app looks identical on iPhone and Android, including a Material-style back arrow and ripple effects on iOS. iPhone reviewers say it "feels off". What is the best fix?

Exercise 17.1 90 minutes · on your own product

Adaptation table for your key feature

  1. Pick your most important feature and two platforms (the one you have and the one you want next).
  2. Fill the table: trigger, entry, confirmation, undo, and what is shared.
  3. Sketch the feature on the second platform (paper, four frames), using only that platform's conventions.
You're done when: you have the table and the sketches, and you can point to what is shared versus adapted in each frame.
BeginnerCompetentStrong
Table filled with guesses.Table checked against the platform's guidelines and its best apps.Sketches tested with someone who uses that platform daily, and corrected.
18

Responsive and adaptive: sizes and inputs

Taught by Ethan Marcotte, with Material window size classes and Apple size classes

Design for ranges, not devices: a layout that works from 320 to 1,600 points and reflows at a few meaningful widths. And design for inputs, not screens: a finger, a mouse, a keyboard and a pen ask for different things on the same screen size.

Breakpoints from content, size classes from platforms

A breakpoint is a width where the layout changes. Do not choose them from device lists, which change every year; choose them where your content breaks: when a line of text becomes too long (more than about 75 characters, see Typography), when two columns become cramped, when a sidebar crowds the content. Platforms give you named ranges to anchor on. Material defines window size classes: compact (under 600 dp wide, most phones in portrait), medium (600 to 839 dp, tablets in portrait and many unfolded foldables) and expanded (840 dp and up, tablets in landscape and desktops), with larger classes added for very wide windows. Apple uses size classes, compact or regular in each dimension.

06008401600 dp compactmediumexpanded one column, bottom navrail + listsidebar + list + detail
Three layouts, not thirty. Most products need a compact, a medium and an expanded arrangement; fluid sizing handles everything in between.
Lab: resize the window, change the pointer
Drag the width through 600 and 840 to watch the navigation move from a bottom bar to a rail to a sidebar, and the detail view appear beside the list. Then switch the input: with a fine pointer, rows get denser and a hover hint appears (hover over a row); with touch, rows grow to 44-point targets and nothing depends on hover. Width and input are independent: an iPad with a trackpad is wide and fine; a phone in landscape is wide-ish and coarse.

Input is a separate axis

Screen size does not tell you how someone is pointing. A 13-inch touchscreen laptop, an iPad with a Magic Keyboard and a phone with a Bluetooth mouse all break the "small means touch" assumption. On the web, query the input directly: @media (hover: hover) and (pointer: fine) for precise pointers, (pointer: coarse) for touch. On native platforms, use the system's controls, which already adapt (iPadOS shows pointer effects, macOS uses denser controls).

Don't
Calm technologySystems for oneReading on screens Actions appear on hover only.On touch they never appear at all.
Hover as the only door. Touch users never discover edit and delete.
Do
Calm technologySystems for oneReading on screens A visible "more" on every row, plus swipeor long-press; hover adds shortcuts only.
Hover enhances; it never gates. Every action has a touch path.

Layout shifts, desktop windows and foldables

  • Reserve space. Images without dimensions, late-loading banners and fonts that swap cause content to jump while people are reading or about to tap. Give media an aspect ratio, reserve space for async content, and keep component sizes stable across states (Chapter 8).
  • Desktop windows are any size. People tile windows side by side, so your Mac or web app may be 500 points wide on a 5K display. Treat window width, not screen size, as the input, and define a sensible minimum window size.
  • Container queries. Supported in all major browsers since 2023, they let a component respond to the width of its own container. A card can switch from horizontal to stacked whether it lives in a sidebar or a main column.
  • Foldables. A device can change size while your app is open: folded phone, unfolded tablet, half-open "tabletop" posture with a hinge across the middle. Preserve state through the change (the scroll position and the open note must survive), avoid putting critical content across the hinge, and use the medium and expanded layouts you already have.
folded: compact unfolded: list + detail tabletop: view up, controls down
The size can change mid-task. Treat a fold or unfold like a window resize that must preserve everything the person was doing.
For the solo creator

Write your three layouts (compact, medium, expanded) as the only breakpoints in your token file, and make components fluid inside them with container queries or flexible stacks. Test with real window resizing, not just device presets: drag the browser or Mac window slowly from narrowest to widest and watch for the moment anything looks wrong. That moment is a breakpoint or a bug.

Do

  • Pick breakpoints where your content breaks; anchor on platform size classes.
  • Treat input type as independent from screen size.
  • Reserve space to prevent layout shifts.
  • Preserve state across resizes and folds.

Don't

  • Design for device names.
  • Gate actions behind hover.
  • Assume desktop means a big window.
  • Stretch a phone layout to 1,400 points wide.

In the wild

  • Gmail on AndroidGoogle
    Switches from a single list with a bottom bar on phones to a navigation rail and list-detail layout on tablets and unfolded foldables. The same app, three canonical layouts.
  • The Boston Globe responsive site2011, with Ethan Marcotte and Filament Group
    One of the first major responsive news sites, often cited as proof that a single fluid design could serve phones to desktops. Responsive design was a design decision before it was a technique.

Common mistakes

Too many breakpoints

Each device gets a tweak, and the CSS becomes a maze.

Three layouts plus fluid sizing. Add a breakpoint only where content visibly breaks.

Width as a proxy for touch

"Mobile" and "touch" are treated as the same thing.

Query pointer and hover capabilities separately from width.

Check yourself

On a 1,024 dp wide iPad with a trackpad, your web app shows the phone layout with 44 px rows and no hover. What went wrong?

Exercise 18.1 60 minutes · on your own product

The slow drag

  1. Open your app in a resizable window. Drag slowly from the narrowest to the widest size. Screenshot every moment something looks wrong.
  2. Decide your three layouts and their breakpoints from those screenshots.
  3. Test with touch only, then mouse only, then keyboard only. Note anything that depends on hover.
You're done when: your breakpoints are written as tokens with a reason each, and every hover-only interaction has a touch path.
BeginnerCompetentStrong
Breakpoints chosen.Breakpoints from content, three layouts, hover issues fixed.Container queries for components, layout shift eliminated, state preserved across resizes.
19

Design debt, and keeping a system alive for years

Taught by Brad Frost (the interface inventory), with semantic versioning and the GOV.UK team

Every shortcut you take in the interface is a loan. Design debt compounds quietly until a simple change takes a week. Pay it down on a schedule: inventory, consolidate, version, and deprecate gently.

Engineers know technical debt: code that works but makes future changes expensive. Design debt is its twin: the third gray nobody chose, the dialog that puts the buttons on the other side, the screen that uses its own spacing, the component with a customColor prop. Each one was a reasonable choice under time pressure. The trouble is interest. Every inconsistency makes the next change slower (which version do I follow?), makes theming leakier, and teaches users that your product is unpredictable.

cost of a change months since launch no maintenance half a day a month Illustrative shape, not measured data.
Interest compounds. A small, regular payment keeps the curve flat. Waiting for "the big redesign" is how products end up rewriting everything.

The interface inventory

In 2013 Brad Frost described the interface inventory: screenshot every instance of every kind of UI element in your product and paste them into one document, grouped by type. All buttons together. All headings. All form fields. All dialogs. All icons. The power is in the side-by-side view. Inconsistencies that are invisible across fifty screens become embarrassing on one page, and the embarrassment is useful: it creates the motivation to consolidate.

Inventory: every button in one app

Primary-looking buttons, collected from 14 screens

SAVE Save changes Save Update Continue → Submit Done

Destructive buttons

Delete remove Delete forever
Before: ten buttons, seven heights from 30 to 44 px, six radii, five colors, four weights, and labels in three cases ("SAVE", "Save", "Submit"). Each looked fine on its own screen.

Running an inventory alone, in one afternoon

  1. Capture. Walk through every screen in both themes and screenshot each instance of: buttons, text styles, form fields, icons, dialogs and sheets, empty states, colors (use a color picker on screenshots).
  2. Group. Paste into one canvas or document, one row per type. Do not judge yet.
  3. Count. Write the count of distinct variants per row: "buttons: 10 styles".
  4. Decide. For each row, choose the keepers (usually the ones closest to your tokens) and map every other instance to one of them.
  5. Consolidate. Replace instances in code with the system component. Do the most-used screens first.
  6. Prevent. Add a search, lint rule or code review habit that catches new literals and one-off styles.

Versioning your system

Even for one person, a version number on your system is useful because it marks moments of change you can look back on, and it lets you migrate gradually. Use the familiar rules of semantic versioning (major.minor.patch, from semver.org): a patch fixes something without changing how you use it (a contrast tweak); a minor version adds something backward-compatible (a new component, a new token); a major version breaks something (a token renamed or removed, a component API changed).

2.4.1 majorminorpatch major: rename or remove a token,change a component's APIminor: add a component or tokenpatch: fix contrast, a radius, a bug Gentle deprecation --color-muted --color-text-secondary old name kept as an aliasfor one major version
Rename without breaking. Point the old token at the new one, mark it deprecated in a comment, migrate call sites over time, and delete it at the next major version.

Checklist: system health, run it monthly

Tokens
Components
Debt
Platforms
For the solo creator

Put "system day" in your calendar: half a day each month for the health checklist and the smallest consolidation, and one day each quarter for a full interface inventory. Pair it with something you enjoy (new music, a good café) so it survives busy months. It is the cheapest way to keep a product feeling crafted as it grows from ten screens to a hundred.

Do

  • Inventory the interface quarterly, side by side.
  • Consolidate the most-used screens first.
  • Version your system and keep a changelog.
  • Deprecate with aliases, then remove at a major version.

Don't

  • Wait for a big redesign to fix inconsistency.
  • Rename tokens without an alias.
  • Let exceptions live without a marker.
  • Treat the system as finished.

In the wild

  • GOV.UK FrontendGovernment Digital Service
    The code behind the GOV.UK Design System is released with version numbers and published release notes that explain breaking changes and how to migrate. Release notes are documentation for future-you.
  • Interface inventoryBrad Frost, 2013
    Frost's article walks through screenshotting a site's elements into categories and using the result to start a system and get buy-in. For a solo creator, the buy-in you need is your own. Seeing all variants at once is the motivation to fix them.

Common mistakes

The big-bang redesign

Debt piles up until a full redesign feels like the only answer.

Small, scheduled payments. A redesign should be a choice, not an escape from debt.

Breaking changes without a path

You rename a token everywhere in one commit and miss a few call sites.

Alias first, migrate, then remove. Search for the old name before deleting.

Auditing only one theme or platform

You inventory what you use daily.

Include dark mode, high contrast, the largest text size and every platform you ship.

Check yourself

You want to rename --color-muted to --color-text-secondary. It is used in 140 places. What is the safest plan?

Connect the ideas · Part six

Your quarterly inventory shows that on Android your dialogs have a different button order from your web app, and both differ from your iOS app. Is that design debt?

Exercise 19.1 3 hours · on your own product

Your first interface inventory

  1. Follow the six-step afternoon above for buttons, text styles, form fields and dialogs.
  2. Write the count per type, before and after your planned consolidation.
  3. Consolidate the single worst category in code this week.
  4. Tag the result as version 1.0.0 of your system and start a changelog.
You're done when: an inventory document exists with counts, one category is consolidated in code, and your repository has a CHANGELOG entry for system 1.0.0.
BeginnerCompetentStrong
Screenshots collected for one category.Four categories, counts, one consolidated.All categories in both themes and every platform, a prevention rule added, and the monthly health checklist scheduled.

Wrap-up

Ten things to remember

  1. A design system is decisions made once and encoded in code. For one person, its value is speed, consistency and saved attention.
  2. Start small: tokens, about a dozen components, a handful of patterns, and voice. Extract from real screens; never build for imaginary ones.
  3. Atomic design is a way of seeing parts and wholes. Keep the page stage as a stress test; organize components by purpose, not by chemistry.
  4. Tokens come in layers: primitives (values), semantic (purposes), and optionally component tokens. Screens read only purposes.
  5. Names outlive values. No hues in semantic names; order name parts from general to specific.
  6. A theme is a new mapping from semantic tokens to primitives. Dark mode is not inversion; high contrast is a real theme.
  7. A component is finished only when its anatomy, API, all seven states, sizes, content rules and accessibility are decided and written down beside the code.
  8. Keep one source of truth, almost always code. Drawing tools are for exploring, not for specifying.
  9. Follow each platform's conventions for behavior; share brand, words and the data model. Adapt, don't copy.
  10. Design for width ranges and input types, not devices, and pay down design debt a little every month.

Self-assessment: I can…

Practice assignment for the next two weeks

  1. Days 1–2: census and tokens. Exercises 1.1 and 4.1. End with a token file and two migrated screens.
  2. Days 3–4: scales and themes. Exercises 5.1 and 6.1. Ship dark mode and the contrast table.
  3. Days 5–6: components. Exercises 7.1 and 8.1. Build the torture page (2.1) with the state matrix on it.
  4. Day 7: rest and review. Use your app for a full day in dark mode with large text. Write down everything that annoys you.
  5. Days 8–9: button and docs. Exercises 9.1 and 10.1. Five documented components, three patterns.
  6. Day 10: one source of truth. Exercise 11.1.
  7. Days 11–12: platforms. Exercise 12.1 plus the chapter exercise for your main platform (13.1, 14.1, 15.1 or 16.1). Fix the top three mismatches.
  8. Day 13: sizes. Exercise 18.1, the slow drag.
  9. Day 14: inventory. Exercise 19.1. Tag system 1.0.0, write the changelog, and book the monthly system day in your calendar.

Reflection

At the end of the two weeks, write one paragraph: which decision did the system take off your plate that you used to agonize over? Which convention did you discover you had been breaking? What will you check first on system day next month?

Reading list

  • Atomic Design
    Brad Frost, 2016 (free to read online) · The model in full, plus the interface inventory and how to keep a pattern library alive. Read chapters 1, 2 and 4.
  • Design Systems
    Alla Kholmatova, Smashing Magazine, 2017 · The best book on the human side of systems: shared language, functional versus perceptual patterns, strict versus loose systems.
  • EightShapes essays on tokens and systems
    Nathan Curtis, Medium, 2016–today · Especially "Naming Tokens in Design Systems". The most precise thinking available on token architecture.
  • Expressive Design Systems
    Yesenia Perez-Cruz, A Book Apart, 2019 · How to keep a system from flattening your product's personality.
  • Refactoring UI
    Adam Wathan and Steve Schoger, 2018 · Practical, visual advice on scales for spacing, type, color and shadow, from the makers of Tailwind.
  • Apple Human Interface Guidelines
    Apple, continuously updated · Read the pages for navigation, sheets, alerts, menus, typography and the platform considerations for each device you ship.
  • Material Design 3
    Google, continuously updated · Read color roles, window size classes and canonical layouts, even if you never ship on Android.
  • GOV.UK Design System
    Government Digital Service · The model for documenting components and patterns with research and "when not to use".
  • Laws of UX
    Jon Yablonski, O'Reilly, 2020 (and the website) · Jakob's law and its relatives, with the psychology behind them.
  • Responsive Web Design
    Ethan Marcotte, A Book Apart, 2011 (from his 2010 A List Apart article) · The origin of designing for ranges instead of devices.
  • Resilient Web Design
    Jeremy Keith, 2016 (free online) · Progressive enhancement and the web's long history of sturdy conventions.
  • NYCTA Graphics Standards Manual
    Unimark International (Massimo Vignelli, Bob Noorda), 1970; facsimile reissued 2014 · A design system before software: rules, measurements and examples on every page.

Next: Designing with AI, where your documented system becomes the context an AI assistant needs to build screens the way you would.