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.
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).
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.
Count your button styles. Open five screens. How many different paddings, radii and font weights do your buttons use?
Try dark mode. If you flipped to dark mode today, how many files would you have to touch? One, or fifty?
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?
A system is a set of decisions that the product actually obeys. A named variable that every screen reads from is a decision encoded once (c). The Figma page (a) and the PDF (d) describe intentions but nothing forces the code to follow them. Typing the same hex everywhere (b) is consistent today, but the change still costs fifty edits, and one of them will be missed.
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.
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:
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).
Tokens. Named values for color, type, spacing, radius, shadow and motion. Perhaps 40 to 80 of them. Part two is about these.
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.
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.
Voice. Ten rules for how your product speaks: sentence case, verbs on buttons, no blame in errors. Covered in Words.
Don't
Drift. Each was a reasonable choice on its day. Together they say nobody is in charge.
Do
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.
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?
Extraction (b) captures decisions you have already proven on real screens, costs an afternoon, and pays back immediately. (a) builds for imaginary screens. (c) lets drift compound until the cleanup costs weeks. (d) can be a fine starting point for components, but adopting everything unchanged means maintaining far more than you use and erasing your product's identity.
Exercise 1.1 45 minutes · on your own product
The decision census
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:.
Paste each unique value into a spreadsheet with a count of how often it appears.
Group near-duplicates (#6B6B6B and #6E6E6E are the same decision made twice).
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.
Beginner
Competent
Strong
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 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.
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?
Templates show structure with placeholder content; pages put real content in, and that is exactly where long words, missing images and empty states show up. The fix (hyphenation, overflow-wrap, a maximum of lines) belongs to the component, but the discovery happens at the page stage.
Exercise 2.1 40 minutes · on your own product
Build a torture page
Create one screen, visible only in debug builds, that renders your five most-used components.
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.
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.
Beginner
Competent
Strong
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.
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.
System
What it is
Steal this
Apple Human Interface Guidelines
Apple'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 3
Google'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.
Primer
GitHub'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.
Polaris
Shopify'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.
Carbon
IBM'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 System
The 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.
Radix
Unstyled, 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/ui
A 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
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).
Open the button page. It shows you their whole approach in miniature: hierarchy, states, sizes, content rules and accessibility notes.
Open the color or token page. How many layers? How are tokens named? How do they theme?
Find one pattern you need now (confirmation, empty state, form validation) and read it closely, including the "don't" examples.
Write down one idea to steal and one thing you would deliberately not copy.
Don't
Copying the surface. Material's look dropped onto iOS makes the app feel foreign to iPhone users.
Do
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?
GOV.UK's patterns ("Question pages", "Check answers", "Ask users for dates") are written for exactly this: long, high-stakes forms used by stressed people. Radix gives you accessible parts but no guidance on flow. Color and typography are not the hard part of a tax form.
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?
The system's customer is future-you (Chapter 1), and atomic levels describe size, not purpose (Chapter 2). Purpose-based groups match how you will search. Rebuilding (d) is exactly how duplicates and drift start.
Exercise 3.1 60 minutes · reading
Steal three ideas
Pick the system closest to your platform (HIG or Material), one product kit (Primer, Polaris or Carbon) and GOV.UK.
In each, read the button page and one pattern you need right now.
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.
Beginner
Competent
Strong
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.
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:
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.
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.
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?
A semantic token (c) names the purpose, so each theme can point it at a different primitive. Redefining --green-600 as blue (a) makes the primitive lie about itself and breaks everything else that wanted green. Theme checks inside components (b, d) spread theming logic across the codebase.
Exercise 4.1 90 minutes · on your own product
Write your token file
Take the census from Exercise 1.1. Turn each kept value into a primitive with a value-based name.
Write 12 to 20 semantic color tokens, plus semantic spacing and radius tokens if useful, each pointing to a primitive.
Replace the literals in your two most important screens with semantic tokens.
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.
Beginner
Competent
Strong
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.
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.
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.
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?
Concentric corners need inner = outer − padding, so 16 − 12 = 4 px. Using 16 makes the gap at the corner wider than along the sides, which reads as pinched.
Exercise 5.1 45 minutes · on your own product
Five scales on one page
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.
For each step, write one typical use in a comment.
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).
Beginner
Competent
Strong
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.
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
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
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.
The :not([data-theme="light"]) guard lets a manual choice override the system setting. This course's own stylesheet uses exactly this structure.
// In the asset catalog, give each color set
// "Any", "Dark" and "High Contrast" appearances.
extension Color {
static let textSecondary = Color("TextSecondary")
static let surfaceRaised = Color("SurfaceRaised")
}
// Prefer system semantic colors where they fit:
Text(note.title).foregroundStyle(.primary)
Text(note.date).foregroundStyle(.secondary)
Asset catalog colors resolve automatically for light, dark and increased contrast. Your views only ever say .textSecondary.
// Jetpack Compose with Material 3
val scheme = when {
dynamic && Build.VERSION.SDK_INT >= 31 ->
if (dark) dynamicDarkColorScheme(ctx) else dynamicLightColorScheme(ctx)
dark -> DarkColors
else -> LightColors
}
MaterialTheme(colorScheme = scheme) { App() }
// Components read roles: MaterialTheme.colorScheme.onSurfaceVariant
Material roles are semantic tokens; dynamic color generates the mapping from the user's wallpaper.
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?
Dark themes express elevation with lighter surfaces because shadows have little to darken. A heavy shadow (a) looks muddy; borders everywhere (b) add noise; pure black (c) makes the jump to text harsher and still leaves cards and background similar.
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?
Primitives hold values, semantic tokens hold purposes, components read purposes. A rebrand is new primitives plus new arrows, once per theme. If you had to rename semantic tokens (c), they contained a hue and were primitives in disguise.
Exercise 6.1 2 hours · on your own product
Ship a real dark theme, and a contrast pass
Write the dark mapping for every semantic color token. Use dark grays, a lighter raised surface and a softened accent.
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.
Add a high-contrast mapping (or the prefers-contrast query) for secondary text and borders.
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.
Beginner
Competent
Strong
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.
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.
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
Few options, named by purpose.kind: .destructive, not color: .red. A purpose can be restyled; a color is a promise you cannot keep.
Make impossible states impossible. If a button cannot be both loading and disabled-with-reason, use one state enum instead of two booleans.
Sensible defaults. The most common use should need the fewest arguments: Button("Save").
Content is a prop, not a variant. "Button with icon" is not a different component; the icon is optional content.
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.
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?
One enum makes the impossible combination unrepresentable, while keeping one component and one set of layout rules. Four components (c) duplicate the row's layout four times and will drift.
Exercise 7.1 60 minutes · on your own product
Anatomy and API of your three core components
Pick your three most used components (often button, text field, list row).
For each, draw the anatomy with numbered parts (paper is fine).
Write the API: each prop with type, default and one-line purpose. Mark variants versus props.
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.
Beginner
Competent
Strong
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.
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
Silent "no". The button is gray, and nothing says why. On a long form, people scroll up and down hunting for the reason.
Do
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?
Read the state matrix by column: a state should mean the same thing everywhere. Pick one recipe (for example "overlay of content color at 12%") and apply it consistently. Removing pressed feedback (a) hurts touch users, who rely on it to know a tap registered.
Exercise 8.1 75 minutes · on your own product
Fill in your matrix
Make a grid: your core components as rows, the seven states as columns.
Render or screenshot each cell from your real app. Mark empty cells (state not designed) and inconsistent cells.
Write one recipe per column as a rule using tokens. Implement it.
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.
Beginner
Competent
Strong
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.
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.
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
Layout shift. The Cancel button slid under the pointer; the next click hits the wrong thing.
Do
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?
Emphasis should follow probability, and danger should be marked by color and distance. Making Delete primary (b) invites accidents. Identical rows (c) can be fine in a menu, but Delete should still be red and separated.
Exercise 9.1 60 minutes · on your own product
Write your button spec
Run the checklist above against your current button. Note every failure.
Rewrite every button label in the app as a verb (count how many "OK"s you remove).
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.
Beginner
Competent
Strong
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.
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:
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?
The decisions log records the reason, which is exactly what fades from memory. A prop table (a) shows what exists, not why something does not. An old design (c) shows what it looked like, not what went wrong.
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?
Reuse the component, pick the kind by hierarchy (Chapter 9), rely on the shared state recipes (Chapter 8), avoid style escape hatches (Chapter 7), and record the pattern (Chapter 10). A link (d) navigates; restoring is an action.
Exercise 10.1 90 minutes · on your own product
Document five components and three patterns
Write the eight-section doc block for your five most used components, inside their files.
Write three pattern recipes you use repeatedly (for example confirm-or-undo, form validation, empty state): when to use, which components, the rules.
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.
Beginner
Competent
Strong
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.
Activity
Drawing tool (Figma, Sketch, paper)
Code (browser, SwiftUI previews)
Exploring many directions
Fast; ten variants side by side
Slower; each variant is real work
Seeing a whole flow
Excellent; screens on one canvas
Hard without extra tooling
Real content and edge cases
Manual; easy to fake the happy path
Natural; real data, empty and long cases
States, motion, responsiveness
Approximated, drawn one at a time
Exact and free once built
Accessibility (text size, screen readers)
Mostly invisible
Testable directly
Staying in sync with the product
Drifts unless maintained
Is 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.
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.
Plain CSS custom properties need no build step and theme at runtime. A strong default for small web products.
/* app.css: Tailwind v4 reads design tokens from @theme */
@import "tailwindcss";
@theme {
--color-ink: #1C1D1F;
--color-paper: #F1F1EF;
--color-accent: #E9A800;
--radius-control: 0.5rem;
--spacing: 0.25rem; /* the base of the spacing scale */
}
/* Now classes like bg-accent, text-ink, rounded-control exist,
and every token is also a CSS variable at runtime. */
Tailwind v4 (2025) moved configuration into CSS with @theme, so your tokens are both utility classes and CSS variables. Keep semantic names (accent, ink) rather than hues.
enum Space { static let s2: CGFloat = 8; static let s3: CGFloat = 12; static let s4: CGFloat = 16 }
enum Radius { static let control: CGFloat = 10 }
struct PrimaryButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.font(.body.weight(.semibold))
.padding(.horizontal, Space.s4)
.frame(minHeight: 44)
.background(Color.accent.opacity(configuration.isPressed ? 0.8 : 1))
.foregroundStyle(Color.onAccent)
.clipShape(RoundedRectangle(cornerRadius: Radius.control))
}
}
// A ViewModifier packages a recurring look:
struct CardSurface: ViewModifier {
func body(content: Content) -> some View {
content.padding(Space.s4)
.background(Color.surfaceRaised, in: .rect(cornerRadius: Radius.control + Space.s2))
}
}
extension View { func cardSurface() -> some View { modifier(CardSurface()) } }
In SwiftUI, ButtonStyles, ViewModifiers and asset-catalog colors are your component layer. Built-in controls already handle most states and accessibility.
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.
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
Sketch to diverge. Paper or Figma, fast and ugly, using your real tokens if your tool has them. Ten options, not one.
Pick one, build it in code. Use existing components only. If something new is needed, write it as a local, unstyled piece first.
Review on the device. Real data, both themes, largest text size, keyboard, touch.
Promote. If the new piece appears a second or third time, move it into the system: tokens, component, doc block.
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?
The shipped code is what users experience, so it is the truth. You can still decide the drawing had the better value, but you make that change at the source, and every output follows.
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?
Tokens are shared; components and conventions are per platform. That is the line between brand (shared) and behavior (native), which Part five and Chapter 17 develop.
Exercise 11.1 60 minutes · on your own product
Collapse to one source of truth
List every place a design value currently lives: code, design files, notes, screenshots.
Choose the source (almost certainly your token file in code).
Rename Figma variables or styles (if any) to match code token names exactly.
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.
Beginner
Competent
Strong
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.
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.
Convention
iOS / iPadOS
macOS
Android
Windows
Web
Going back
Back button top-left, labelled with the previous screen; swipe from the left edge
No system-wide back; ⌘[ in browsers and Finder-like apps; close a window or sheet
System back gesture from either screen edge (or back button); predictive back previews the destination
Back button in the app's title bar area where navigation is deep; Alt+Left
The browser's back button. Your app must push real history entries
Dialog button order
Two buttons side by side; Cancel on the left, the likely action on the right
Default button on the right; Cancel to its left; Esc cancels, Return triggers default
Actions aligned right as text buttons; confirming action on the far right
Primary button first (left), Close/Cancel last (right)
No single rule; pick one order and use it everywhere
Destructive actions
Red "destructive" style in alerts and action sheets; swipe actions on rows
Name the action ("Delete"); avoid making it the default button
Confirm in a dialog, or act and offer Undo in a snackbar
Confirm with a ContentDialog naming the action
Prefer undo; confirm big losses; GitHub-style "type the name" for the irreversible
Settings
A settings screen inside the app (often from a tab or profile); some apps also use the Settings app
App menu → Settings… (⌘,) opening a separate window; changes apply immediately
Settings screen, usually reached from a menu, the profile or navigation
Settings at the bottom of the navigation pane, often a gear
Account or avatar menu; a /settings URL
Main navigation
Tab bar at the bottom (iPhone); sidebar on iPad
Sidebar plus toolbar; every command also in the menu bar
Navigation bar at the bottom (3–5 destinations); navigation rail or drawer on larger screens
Navigation view (left pane or top)
Header links or sidebar; every view has a URL
Undo
Undo buttons or toasts; shake to undo exists but is little known
⌘Z, with the Edit menu naming what will be undone
Snackbar with "Undo"
Ctrl+Z
Toast with "Undo"; ⌘/Ctrl+Z inside editors
Text size
Dynamic Type: support it fully
Respect system text size settings where available
Font scale setting: use sp units
Text scaling setting
Browser zoom and default font size: use rem, never block zoom
One dialog, five platforms
Delete “Trip notes”?
This note will be deleted from all your devices.
CancelDelete
Delete “Trip notes”?
This note will be deleted from all your devices.
CancelDelete
Delete “Trip notes”?
This note will be deleted from all your devices. You can’t undo this action.
DeleteCancel
Delete “Trip notes”?
This note will be deleted from all your devices.
DeleteCancel
Delete “Trip notes”? Esc
This note will be deleted from all your devices. You can restore it from Trash for 30 days.
CancelDelete note
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.
Android (Material 3): large rounded container, left-aligned title, and text buttons aligned to the right, with the confirming action on the far right. Material often prefers acting immediately with an Undo snackbar over asking first.
macOS: a compact alert with the app icon. The default button (highlighted, triggered by Return) sits on the right, Cancel works with Esc. Here Cancel is the default because the action cannot be undone, so a reflexive Return keeps the note safe.
Windows: a ContentDialog with title, content and a button row at the bottom. The primary button comes first (on the left), and Close or Cancel comes last. People who switch between Mac and Windows feel this difference in their fingers.
Web: no operating system rule, so your system decides. Use the native dialog element or an accessible library dialog: focus moves inside, Esc closes, focus returns to the trigger. Label the button with the verb and object, and prefer recoverable deletion (Trash for 30 days) over a scary question.
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.
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?
On macOS the default button sits on the right and responds to Return. When an action loses data, make the safe choice the default so the reflex is harmless. A second dialog (d) trains people to click through dialogs faster.
Exercise 12.1 45 minutes · on your own product
Convention audit
For your main platform, copy the seven rows of the conventions table into a note.
For each row, write what your app does today. Mark matches, mismatches and "not applicable".
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.
Beginner
Competent
Strong
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.
Never break the swipe. Custom transitions and full-screen gestures that block the edge swipe are among the most common complaints about iOS apps.Places, not actions. Tabs are where you go, not what you do. A "+" tab that opens a composer is a common misuse.Sheets keep context. A medium detent shows the screen beneath, so people remember what they were doing.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
Hidden places, contested corner. Out of sight is out of mind, and on iOS the top-left corner belongs to Back.
Do
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?
Tabs are places, not actions (a). A floating action button is a Material convention (b). The top-left belongs to Back (d). A toolbar button on the relevant screen opening a sheet is the iOS way.
Exercise 13.1 45 minutes · on an iPhone
Five-structure check
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.
Record each failure with a screenshot.
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.
Beginner
Competent
Strong
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.
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
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.
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.
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.
Windows that behave. Multiple windows where documents warrant them, remembered size and position, full-screen support, and correct behavior when the last window closes.
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.
Right-click everywhere. Context menus on every object, mirroring commands that also exist in the menu bar.
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.
Drag and drop, in and out. Files, text and images should drag into your app and out to Finder and other apps.
Don't
A web page in a Mac costume. Settings replace the document view, need an explicit Save, and are not reachable with ⌘,.
Do
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?
"Feels like an iPad app" is about behavior: missing menus and shortcuts, touch-sized controls, settings in the wrong place. Visual tweaks (b, c, d) do not touch the cause.
Exercise 14.1 30 minutes · on a Mac
The ten-shortcut test
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.
Open each menu and check every command has a shortcut or a reason not to.
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.
Beginner
Competent
Strong
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.
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.
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
Two backs that disagree. The in-app "‹ Library" and the system gesture lead to different places.
Do
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?
Back must always work and be predictable; predictive back makes the destination visible, so your history must be honest. "Are you sure you want to exit?" dialogs (c) are an anti-pattern on Android.
Exercise 15.1 40 minutes · on an Android device or emulator
Back and color pass
Test system back from every screen, from a deep link, and with a sheet open.
Switch the wallpaper and dark theme; note what your app does with dynamic color.
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.
Beginner
Competent
Strong
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.
A URL is a saved state. Design it as deliberately as a screen: readable, stable, and meaningful when shared.
Don't
A web page pretending to be an app. Every web convention people rely on silently breaks.
Do
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.
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?
On the web the URL is the shareable state. Local storage (b) remembers for one person on one device, but cannot be shared, bookmarked or opened in a new tab.
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?
Brand and words are shared; structures and behaviors are native. That is the core of cross-platform design, developed in the next chapter. Identical custom dialogs (a) ignore each platform's button order and keyboard habits; four unrelated designs (c) lose your system.
Exercise 16.1 45 minutes · on your web product (or one you use)
The web conventions test
Click through five screens, then press back five times. Does it retrace your steps?
Refresh on a filtered view. ⌘/Ctrl-click a list item. Copy a URL into a private window.
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.
Beginner
Competent
Strong
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
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.
Approach
How it renders
Design implication
Native per platform (SwiftUI, Jetpack Compose, WinUI)
The platform's own components
Conventions and yearly updates come free. Cost: building each platform separately; sharing only tokens and logic.
React Native
JavaScript driving native platform views
Many controls feel native by default; navigation and gestures need careful platform-specific choices.
Flutter
Draws its own widgets with its own engine
Pixel-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 Multiplatform
Shared Kotlin logic; shared or native UI
Share the model and decide per screen whether the UI is shared or native.
Electron, Tauri
Web views inside a desktop shell
Web conventions inside a desktop app. Budget for menus, shortcuts, window behavior and native dialogs.
Web and PWA
The browser
One 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.
iPhone. Extend the system's selection menu rather than inventing a toolbar. The sheet keeps the passage visible above it.Mac. Same command, now in the Edit menu, the context menu and on the keyboard. No sheet: a sidebar uses the extra width.Android. Act first, offer Undo in a snackbar. Material's bottom sheet holds the optional note.Web. The web's superpower is the link: every highlight gets an address. The toolbar must also work by keyboard.
Decision
iPhone
Mac
Android
Web
Trigger
System edit menu
Context menu, Edit menu, ⇧⌘H
Selection toolbar
Floating toolbar, H key
Note entry
Medium sheet
Inspector sidebar
Bottom sheet
Popover under toolbar
Confirmation
Visual + light haptic
Visual, appears in sidebar
Snackbar with Undo
Toast with Undo
Undo
Undo in toast or edit menu
⌘Z
Snackbar
Toast, ⌘/Ctrl+Z
Shared
Colors (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?
Flutter renders its own widgets, so platform fidelity is your job. Adapting navigation, controls and physics fixes the feeling without a rewrite. Consistency across platforms (c) matters less than consistency with each platform.
Exercise 17.1 90 minutes · on your own product
Adaptation table for your key feature
Pick your most important feature and two platforms (the one you have and the one you want next).
Fill the table: trigger, entry, confirmation, undo, and what is shared.
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.
Beginner
Competent
Strong
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.
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
Hover as the only door. Touch users never discover edit and delete.
Do
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.
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?
1,024 dp is the expanded size class, so the sidebar and detail layout should appear. With a trackpad, pointer: fine and hover: hover are true, so denser rows and hover affordances are appropriate. Device sniffing (c, d) breaks with every new device.
Exercise 18.1 60 minutes · on your own product
The slow drag
Open your app in a resizable window. Drag slowly from the narrowest to the widest size. Screenshot every moment something looks wrong.
Decide your three layouts and their breakpoints from those screenshots.
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.
Beginner
Competent
Strong
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.
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
SAVESave changesSaveUpdateContinue →SubmitDone
Destructive buttons
DeleteremoveDelete forever
One Button component, three kinds, one size token
Save notePreviewSkip
Destructive, one style
Delete noteDelete 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.
After: one height, one radius, one weight; primary, secondary, quiet and two destructive levels (the filled one only inside the final confirmation). Labels are verbs in sentence case. Ten styles became five uses of one component.
Running an inventory alone, in one afternoon
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).
Group. Paste into one canvas or document, one row per type. Do not judge yet.
Count. Write the count of distinct variants per row: "buttons: 10 styles".
Decide. For each row, choose the keepers (usually the ones closest to your tokens) and map every other instance to one of them.
Consolidate. Replace instances in code with the system component. Do the most-used screens first.
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).
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?
An alias makes the rename non-breaking, so you can migrate gradually and verify. Two independent tokens (b) will drift apart. A good name is worth the migration (d) when the old one misleads.
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?
Consistency has two directions: with your own product and with the platform. Across platforms, consistency with each platform wins for behavior, while brand and words stay shared. Within one platform, inconsistency is always debt.
Exercise 19.1 3 hours · on your own product
Your first interface inventory
Follow the six-step afternoon above for buttons, text styles, form fields and dialogs.
Write the count per type, before and after your planned consolidation.
Consolidate the single worst category in code this week.
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.
Beginner
Competent
Strong
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
A design system is decisions made once and encoded in code. For one person, its value is speed, consistency and saved attention.
Start small: tokens, about a dozen components, a handful of patterns, and voice. Extract from real screens; never build for imaginary ones.
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.
Tokens come in layers: primitives (values), semantic (purposes), and optionally component tokens. Screens read only purposes.
Names outlive values. No hues in semantic names; order name parts from general to specific.
A theme is a new mapping from semantic tokens to primitives. Dark mode is not inversion; high contrast is a real theme.
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.
Keep one source of truth, almost always code. Drawing tools are for exploring, not for specifying.
Follow each platform's conventions for behavior; share brand, words and the data model. Adapt, don't copy.
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
Days 1–2: census and tokens. Exercises 1.1 and 4.1. End with a token file and two migrated screens.
Days 3–4: scales and themes. Exercises 5.1 and 6.1. Ship dark mode and the contrast table.
Days 5–6: components. Exercises 7.1 and 8.1. Build the torture page (2.1) with the state matrix on it.
Day 7: rest and review. Use your app for a full day in dark mode with large text. Write down everything that annoys you.
Days 8–9: button and docs. Exercises 9.1 and 10.1. Five documented components, three patterns.
Day 10: one source of truth. Exercise 11.1.
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.
Day 13: sizes. Exercise 18.1, the slow drag.
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.