Product design for engineers, from research to prototyping in Figma to evaluation

Design is not decoration applied after the code compiles. It is a sequence of decisions about what to build and for whom, and most of those decisions are cheaper to get wrong on paper than in production. This page treats the design process as something an engineer can run without a designer in the room. It covers how to find a real need through interviews that do not lead the witness, how to frame a problem so that it admits many solutions, how to climb the fidelity ladder from a paper sketch to an interactive prototype only as far as the question demands, and how to build that prototype concretely in Figma with auto layout, components, variants, and variables that map onto the same tokens the production CSS will use. It derives the quantitative parts that engineers respect and interviewers ask about, the WCAG relative-luminance and contrast-ratio formulas computed for a real color pair, Fitts's law and Hick's law derived and applied to target sizing, the \( 1 - (1-p)^n \) model that says five users find most usability problems and exactly where that model breaks, and the two-proportion power and significance calculations behind an honest A/B test. The rendering and implementation half of this material, turning a design into shipped markup, lives in a sibling page on modern web development. This page owns the process that comes before the first line of production code.

Why this subject matters now

Two shifts have made design literacy a baseline expectation for engineers rather than a specialty someone else owns. The first is that the tooling collapsed the distance between a design and a running interface. A decade ago a mockup was a flat picture handed across a wall. Today a Figma file with auto layout and variables is a structured document whose constraints, spacing scale, and token names line up one-to-one with flexbox, a spacing system, and CSS custom properties, so the person who reads the file and the person who writes the code are reasoning about the same objects. An engineer who cannot read that file, or who treats the handoff as a pixel-measuring exercise, is slower and produces interfaces that drift from the intended system. The second shift is that generative tools now produce plausible layouts, copy, and component variations in seconds, which raises rather than lowers the value of judgment. The scarce skill is no longer producing a screen but deciding whether a screen solves the right problem, meets a contrast threshold, respects a touch-target minimum, and will survive a usability test. The parts of design that are subjective have not changed much, but the parts that are correctness properties, contrast ratios, focus order, target sizes, reading order for a screen reader, are now testable, regressible, and increasingly enforced, and those are exactly the parts an engineer is best equipped to own.

The through-line of this page is that most of design reduces to two disciplines an engineer already respects, reducing the cost of being wrong, and measuring. Needfinding, framing, and the fidelity ladder are all mechanisms for spending the least effort required to learn that an idea is bad. Contrast ratios, Fitts's law, the usability-discovery curve, and A/B statistics are all ways to replace an argument about taste with a number. Where taste genuinely governs, typographic scale, hierarchy, the feel of a motion curve, the page still gives the underlying structure, a modular scale is a geometric sequence, a spacing system is a quantized grid, an easing curve is a cubic Bezier, so that even the aesthetic choices are made from a small set of principled parameters rather than nudged pixel by pixel.

Core theory

Needfinding with interviews, contextual inquiry, and jobs to be done

Design begins with a need, and the reliable failure mode of engineers is to skip needfinding because the need feels obvious. It rarely is. The purpose of user research is to replace the designer's model of the user, which is a projection of the designer, with evidence about the actual user, whose context, constraints, and goals differ in ways that are invisible from the inside. The two workhorse methods are the semi-structured interview and contextual inquiry. An interview is a conversation guided by a short list of open questions. Contextual inquiry is an interview conducted where and while the person does the work, so that the researcher sees the artifacts, workarounds, and interruptions that the person would never think to mention. The master-apprentice framing captures the stance. The researcher is the apprentice, the user is the master of their own work, and the researcher's job is to watch and ask rather than to instruct or sell.

The single most important technical skill in an interview is not leading the witness. A leading question smuggles the answer into the question and collects an artifact of the researcher's hypothesis rather than a fact about the user. "Wouldn't it be great if this were faster?" almost always returns yes and teaches nothing, because agreement is the socially easy response and the question supplied the desirable frame. The corrections are mechanical enough to learn. Ask about the past, not the hypothetical future. "Tell me about the last time you did X" elicits a concrete episode with real details, whereas "Would you use a feature that does Y?" elicits a prediction that people are famously bad at making about themselves. Ask open questions that cannot be answered yes or no, and when a yes or no arrives, follow it with "why" or "tell me more." Embrace silence. The most useful sentence often comes after an uncomfortable pause the researcher was tempted to fill. Separate what people say from what they do, and weight what they do more heavily, which is exactly why contextual inquiry, watching the work, outperforms a conference-room interview about the work.

The jobs-to-be-done frame, developed in the innovation literature by Christensen and colleagues, organizes the findings. It holds that people do not buy products, they "hire" them to make progress in a particular circumstance, and the unit of analysis is the job, phrased as a situation plus a motivation plus a desired outcome, such as "when I am commuting and want to feel caught up on the news without using my hands, help me consume it hands-free." Phrased this way, the job is stable and solution-agnostic, a podcast, a text-to-speech reader, and a smart speaker are all candidate hirings for the same job, so the frame keeps the team from confusing the current solution with the underlying need. The competing products are whatever the person hires today, including doing nothing, which is why the honest baseline in almost every study is the manual workaround the user already has.

Problem framing, how-might-we, problem versus solution, and point-of-view statements

Raw research does not hand you a problem statement. It hands you observations, and the framing step turns observations into a problem that can be solved many ways. The discipline here is to state the problem at the right altitude, specific enough to be actionable, general enough that it does not name a solution. "The user has no dark-mode toggle" is not a problem, it is a missing solution masquerading as one, and it forecloses the possibility that the real issue is eye strain that a different fix, larger type, warmer default colors, an automatic schedule, might serve better. The corresponding problem is "the user reads in low light at night and the bright interface is uncomfortable enough that they stop." That statement admits the dark-mode toggle as one option among several and keeps the team honest about what success means.

A point-of-view statement compresses a framed problem into a sentence of the form user plus need plus insight, such as "a night-shift nurse needs to log observations without looking at a bright screen because glancing at a phone in a dim ward both hurts their dark-adapted vision and risks waking a patient." The insight clause is what distinguishes a point-of-view statement from a bland requirement. It carries the surprising fact from research that justifies caring. From a set of point-of-view statements the team generates how-might-we questions, deliberately phrased to invite many answers, such as "how might we let a nurse record an observation without visual attention?" The phrasing matters. "How" presumes a solution exists. "Might" grants permission to propose imperfect ideas without committing. "We" makes it collaborative. A good how-might-we is neither so narrow that it names the answer ("how might we add a voice button") nor so broad that it is unanswerable ("how might we improve healthcare"). Calibrating that altitude is the whole craft of framing.

Ideation, structured divergence and convergence, and crazy-8s

Ideation is governed by one principle that engineers routinely violate, which is to separate divergence from convergence in time. Divergence is generating options without judging them. Convergence is selecting and combining. Done together, they interfere, because the critical faculty that convergence requires kills the half-formed ideas that divergence needs to survive long enough to be built upon. The practical rule is to generate first, deliberately withholding evaluation, then evaluate in a separate pass. Quantity is the explicit goal of the generation pass because the first several ideas are almost always the obvious ones already latent in the problem statement, and the interesting options appear only after the obvious ones are exhausted and off the table.

Crazy-8s is the standard forcing function for this. Each participant folds a sheet into eight panels and sketches eight distinct ideas in eight minutes, one per minute. The time pressure is the point. A minute is too short to self-edit, so the internal critic is starved and the hand produces variations it would otherwise suppress. The output is not meant to be good. It is meant to be plural, and the value is that the seventh sketch, produced in a state of mild desperation, is often the one nobody would have reached deliberately. Convergence then follows with an explicit, structured method, dot voting to surface favorites, or scoring each option against the framed problem and known constraints, rather than a discussion that lets the loudest voice or the highest-paid opinion win by default.

The fidelity ladder, from paper to low-fi wireframe to high-fi mockup to interactive prototype

A prototype is an artifact built to answer a question, and its correct fidelity is the lowest fidelity that answers the question, because fidelity costs time and, worse, buys unwanted commitment. The ladder has four rungs. A paper sketch answers "is this flow structurally sensible?" in minutes and invites blunt feedback precisely because it obviously cost nothing. A low-fidelity wireframe, grey boxes and placeholder text, answers questions about layout, hierarchy, and information architecture while deliberately withholding color and typography so that reviewers critique the structure instead of the shade of blue. A high-fidelity mockup answers questions about visual design, brand, and the finished feel, and is expensive enough that it should not be built until the structure is settled. An interactive prototype, clickable and animated, answers questions that only motion and sequence can answer, whether this transition explains the state change, whether this flow is learnable, and where users get stuck. Each rung costs an order of magnitude more than the one below it, so climbing prematurely is the most common and most expensive process error in design.

The counterintuitive cost of high fidelity is psychological, not just temporal. A polished mockup signals that decisions are made, and reviewers respond to that signal by critiquing surface details rather than questioning the whole direction, which is exactly the feedback you do not want early. This is why experienced designers keep early artifacts deliberately rough. Roughness is an invitation to rethink. The engineer's instinct to jump straight to a coded prototype because "I can build it faster than I can draw it" is usually a false economy, because code is high-fidelity by default and inherits all of high fidelity's commitment cost, plus the sunk-cost pull that makes a built thing hard to throw away.

Prototyping in Figma with frames, constraints, and auto layout as flexbox

Figma is the current standard tool for the middle rungs of the ladder, and its core abstractions are worth understanding precisely because they are near-isomorphic to the CSS layout model, which means the design and the implementation share a mental model. A frame is a container with its own coordinate system and clipping, the analogue of a block-level element with a defined box. Frames nest, and the top-level frame is a screen or a component. Constraints govern how a child responds when its parent frame resizes. Each child pins to left, right, center, both (stretch), or scale on each axis, which is the manual, absolute-positioning model of responsiveness and corresponds to anchoring an absolutely positioned element to edges of its containing block.

Auto layout is the important one, because it is flexbox. An auto-layout frame lays its children out along one axis with a defined gap and padding, sizes itself to its content or to its parent, and distributes and aligns the children, which is precisely the flexbox model. The mapping is exact enough to write down as a translation table, and internalizing it means an engineer can read an auto-layout frame and know the CSS before opening the inspector.

Figma auto layoutCSS flexboxNote
Direction (horizontal / vertical)flex-direction: row / columnThe main axis.
Gap between itemsgapSpace between children only, not the edges.
Padding (per side)paddingSpace inside the frame, around the children.
Alignment (the 3x3 packing grid)justify-content + align-itemsMain-axis packing and cross-axis alignment.
Spacing mode "space between"justify-content: space-betweenGap becomes the minimum, and extra space is distributed.
Child sizing "hug contents"width: fit-content (flex-basis auto, no grow)The frame shrinks to its children.
Child sizing "fill container"flex: 1 1 0The child grows to absorb free space.
Child sizing "fixed"fixed width / heightNo grow, no shrink.
Absolute position (escape auto layout)position: absoluteRemoves the child from the flex flow, and constraints then apply.
Wrapflex-wrap: wrapAuto layout added wrapping to match flexbox line-breaking.

The mapping is not a coincidence. Figma's auto layout was designed after flexbox had won, and it deliberately borrowed the model so that the tool would produce layouts that translate cleanly to the web. There is one worthwhile subtlety. Figma's alignment control is a single nine-cell grid that sets main-axis and cross-axis alignment together, whereas CSS splits them into justify-content (main axis) and align-items (cross axis). Reading the nine-cell grid is just a matter of remembering which axis is "main," and the main axis is whichever direction the arrow points. Grid layouts, two-dimensional arrangements that flexbox handles awkwardly, map instead onto CSS grid, and recent Figma additions for multi-dimensional layout track that model, but auto layout remains the flexbox analogue and the one an engineer meets constantly.

Components, variants, properties, and variables

A Figma component is a reusable definition with instances that inherit from it, the design-file analogue of a class with instances or a React component with props. Editing the main component propagates to every instance, which is what makes a design system maintainable at scale. Variants group related components into a single set indexed by properties. A Button component with a type property (primary, secondary, ghost), a size property (small, medium, large), and a state property (default, hover, disabled) is one component set whose instances are selected by choosing property values, exactly as a React button is rendered by passing props. Component properties go further and expose text, boolean (show/hide a layer), and instance-swap knobs on an instance without detaching it, which is the design equivalent of passing children and conditional-render flags. When the variant matrix and the props line up with the engineering component's API, the handoff stops being a translation and becomes a lookup.

Variables (and the older styles) are Figma's design tokens, named values for color, number, string, and boolean that can be referenced anywhere and switched in bulk by mode. A color variable bg/default with a Light mode value of white and a Dark mode value of near-black is exactly a CSS custom property with a light and dark theme, and switching the frame's mode is switching the data-theme attribute. Variables can alias other variables, which is how the token tiers (primitive to semantic to component, derived below) are expressed inside the file, and they are the single most important feature for keeping a design and its code in sync, because the same token names can be exported and consumed by the build.

Interactive prototyping with smart animate, overlays, and conditional flows

The interactive layer turns static frames into a clickable artifact. A prototype connection is a trigger (click, hover, drag, key, after-delay) on a layer that navigates to a destination frame with a transition. Smart animate is the transition worth understanding. Given two frames, it matches layers by name and interpolates the differences, position, size, opacity, rotation, so that a card that exists in both frames at different positions appears to slide rather than cut. It is keyframe tweening driven by name-matching, and its one rule that trips people up is that layers must share a name to be matched, which is the same discipline that makes shared-element transitions work on the web. Overlays are frames opened on top of the current one, the prototype analogue of a modal, tooltip, or dropdown, with their own open and close transitions and a choice of whether clicking outside dismisses them. Conditional flows and variables in prototypes add real logic. A boolean or number variable can be set on a click and read in an if-condition to branch the flow, so a prototype can model a toggle that actually toggles or a form that actually validates, which is what lets a usability test exercise a genuine decision path rather than a scripted happy path.

Dev mode and handoff

Dev mode is Figma's read-optimized view for engineers. It exposes measurements, the spacing and padding auto layout produced, the resolved values of variables (so a color reads as its token name and hex, not just a swatch), and generated CSS or platform code for a selection, and it marks frames as "ready for development" with change tracking so an engineer sees what moved since last look. The important mental adjustment is that a well-built file makes dev mode nearly redundant. If the auto layout matches the intended flexbox, the variables match the token names, and the components match the component API, then the handoff carries no surprises and the generated CSS is a confirmation rather than a revelation. Handoff quality is therefore mostly determined upstream, by whether the file was built as a structured document or as a flat picture, which is the strongest argument for engineers learning to read and build these files rather than treating them as images to measure. The Figma REST API and the plugin API expose the same document tree programmatically, which is how token-sync tools read variables out of a file and emit a token JSON that a build can consume.

Canva and where it fits versus Figma

Canva is a template-first graphics tool aimed at producing finished marketing, social, and presentation assets quickly, and it occupies a genuinely different point in the space than Figma, which is worth stating plainly so the tools are not confused. Canva optimizes for a non-designer producing a good-enough deliverable fast. It leads with templates, a large stock library, brand kits, and one-click resizing across formats (an Instagram post to a story to a slide), and it hides the box model and constraint system that Figma exposes. Figma optimizes for a team designing an interactive product system, components, variants, variables, auto layout, prototyping, and developer handoff, none of which Canva provides in a form an engineer would build a UI from. The honest division of labor is that Canva is the right tool for a launch graphic, a social card, a conference slide, a one-off email header, or a quick internal deck, the marketing and communication surface around a product, while Figma is the right tool for the product's actual interface, its design system, and anything that will be handed to engineering. Using Figma to make a single social post is overkill. Using Canva to design a component library is impossible. Many teams use both, and the boundary is roughly "will an engineer implement this?" If yes, Figma. If it is a finished asset a person will consume directly, Canva is faster.

Visual fundamentals, the modular type scale

Type sizes should not be chosen one at a time. They should come from a scale, because a scale gives related sizes a visible ratio and keeps hierarchy legible. The modular scale is a geometric sequence. Pick a base size \( t_0 \) and a ratio \( r \), and every step is \( t_k = t_0 \, r^{k} \) for integer \( k \), positive for headings, negative for captions. The ratio is borrowed from musical intervals because those ratios are pleasant and well separated. The minor third is \( r = 1.2 \), the major third \( r = 1.25 \), the perfect fourth \( r = 4/3 \approx 1.333 \), the golden ratio \( r \approx 1.618 \). A larger ratio produces more dramatic jumps and fewer usable steps. A smaller ratio produces a subtler, denser hierarchy. With a base of 16px and the major third \( r = 1.25 \), the ramp is

$$ t_k = 16 \cdot 1.25^{k}, \qquad \begin{array}{c|ccccccc} k & -2 & -1 & 0 & 1 & 2 & 3 & 4 \\\hline t_k\,(\text{px}) & 10.24 & 12.8 & 16 & 20 & 25 & 31.25 & 39.06 \end{array} $$

which rounds to a usable ramp of 10, 13, 16, 20, 25, 31, 39 pixels, or in rem units (dividing by the 16px root) 0.64, 0.8, 1, 1.25, 1.5625, 1.953, 2.441. The reason to store the ramp as computed constants rather than as a live \( 1.25^k \) at render time is that rounding matters, 31.25px wants to be 31 or 32 for crisp rendering, and the rounded values become tokens. The same idea, a base and a ratio, generates a consistent scale for anything with a magnitude, and the spacing system below is the same construction with a different base and a simpler ratio.

The 4- and 8-point spacing grid

Spacing, the gaps, paddings, and margins that create rhythm, is quantized to a grid so that every distance in the interface is a multiple of a base unit, almost always 4px with 8px as the dominant step. The reason is threefold. First, a small fixed vocabulary of spacings (4, 8, 12, 16, 24, 32, 48, 64) is easier to apply consistently than a continuum, and consistency is most of what reads as "polish." Second, 8 divides evenly at the common display scale factors (1x, 1.5x, 2x, 3x), so an 8px unit lands on whole physical pixels across device densities, avoiding the blurring that half-pixel values cause. Third, a quantized scale composes. A component built on the 8-grid drops into a layout built on the 8-grid without introducing off-grid gaps. The scale is not linear at the top, it opens up (16 to 24 to 32 to 48 to 64) because large spaces need coarser granularity, which is again the modular-scale instinct applied to space. In tokens this becomes a space ramp indexed by step, and every margin and padding in the system references a step rather than a raw pixel value.

Color, from HSL to relative luminance to the WCAG contrast ratio

Color is chosen in HSL (hue, saturation, lightness) rather than RGB because HSL's axes correspond to the decisions a designer actually makes. Hue picks the color, saturation its intensity, lightness its position between black and white. A tonal palette, the 50-through-950 ramp that design systems ship, is one hue held roughly constant while lightness sweeps and saturation is tuned so the mid-tones stay vivid, which is trivial to reason about in HSL and painful in RGB. But HSL lightness is a perceptual convenience, not a photometric quantity, and accessibility is defined on the photometric quantity, so the contrast computation must leave HSL and go through the physics.

The WCAG contrast ratio is built on relative luminance, the perceived brightness of a color on a 0-to-1 scale. A display encodes each channel with the sRGB transfer function (a gamma curve), so the stored 8-bit value is not proportional to emitted light. The first step is to undo that encoding. For a channel value \( c_{8} \in \{0,\dots,255\} \), normalize to \( c = c_{8}/255 \) and linearize,

$$ c_{\text{lin}} = \begin{cases} \dfrac{c}{12.92}, & c \le 0.03928, \\[2mm] \left(\dfrac{c + 0.055}{1.055}\right)^{2.4}, & c > 0.03928, \end{cases} $$

a curve that is linear near black (where the eye is sensitive to small differences) and a 2.4-power elsewhere. Relative luminance weights the linear channels by the eye's sensitivity to each primary, green most, blue least.

$$ L = 0.2126\,R_{\text{lin}} + 0.7152\,G_{\text{lin}} + 0.0722\,B_{\text{lin}}. $$

The contrast ratio between two colors with luminances \( L_1 \ge L_2 \) is then

$$ \mathrm{CR} = \frac{L_1 + 0.05}{L_2 + 0.05}, $$

where the additive 0.05 models ambient viewing flare, the small amount of stray light that lifts the darkest achievable black in a real room, so that two very dark colors do not report an unrealistically high ratio. The scale runs from 1:1 (identical) to 21:1 (pure black on pure white). The conformance thresholds are fixed numbers worth memorizing. For normal-size text, AA requires \( \mathrm{CR} \ge 4.5 \) and AAA requires \( \ge 7 \). For large text (at least 18.66px bold or 24px regular), AA requires \( \ge 3 \) and AAA requires \( \ge 4.5 \). Non-text UI elements and graphical objects that convey information require \( \ge 3 \). Problem 1 computes this end to end for a real pair and reads off the verdict.

Layout, grids, and hierarchy

Layout is the arrangement that tells the eye where to go and in what order, and its three levers are alignment, grouping, and contrast. Alignment creates invisible edges that the eye follows. A shared left edge across a column of elements reads as a unit even when the elements differ. Grouping, by the Gestalt principle of proximity, says that elements close together are perceived as related and elements far apart as separate, which is why the spacing system is a semantic tool and not only an aesthetic one. The gap between a label and its field must be smaller than the gap between one field and the next, or the eye groups wrongly. A layout grid, typically a 12-column grid because 12 factors into 2, 3, 4, and 6, gives columns a shared structure so that content aligns across a page and responsive breakpoints recompose predictably. Hierarchy is the deliberate ranking of elements by importance, expressed through the tools already derived, size from the type scale, weight, color and its contrast, and spatial position, so that a first-time viewer's eye lands on the most important element first. The common failure is flatness, where everything is emphasized and therefore nothing is. The fix is to make the primary element unambiguously dominant and demote everything else, which usually means removing emphasis rather than adding it.

Design systems and the primitive, semantic, and component token tiers

A design system is the productized version of everything above, a token set, a component library built on those tokens, and the documentation that makes them a shared contract. The core technical idea is the token tier, and getting the tiers right is what makes theming and maintenance tractable. Primitive (or global) tokens are the raw palette and scales, such as blue-600 = #2563eb, gray-900 = #111827, space-4 = 16px. They are named for what they are and they carry no meaning about where they are used. Semantic (or alias) tokens name a role and reference a primitive, such as color-text = gray-900, color-bg = white, color-accent = blue-600. This tier is where theming lives, because a dark theme is nothing more than the same semantic tokens pointing at different primitives (color-bg = gray-950, color-text = gray-50), while every component that consumes color-bg is unchanged. Component tokens name a part of a specific component and reference a semantic token, such as button-primary-bg = color-accent, button-primary-fg = color-accent-contrast. The indirection looks like bureaucracy until the first rebrand or theme, at which point changing one primitive or repointing one semantic token propagates correctly through hundreds of components, whereas a flat system requires a find-and-replace that is guaranteed to miss cases.

This is exactly how the theming on this very site is structured, and it is the same construction Figma variables express with modes. Primitives are the palette, semantics are the mode-switched roles, and flipping data-theme repoints the semantics. The token file in the implementation section below is a real, minimal instance of all three tiers with a dark theme, and it is consumed by the CSS custom properties that follow it, so the JSON a designer edits and the CSS an engineer ships are two serializations of one source of truth. Tools like style-dictionary and tokens-studio automate exactly this transform, reading a platform-neutral token JSON and emitting CSS variables, Swift, Android XML, or Tailwind config from the single source.

Components as a contract with engineering

The deepest value of a design system is that a component is a contract. When the design defines a Button with a fixed set of variants (type, size, state) and a fixed set of properties (label text, leading icon, disabled), it has defined an API, and the engineering component that implements that API is correct exactly when its props enumerate the same space. This makes divergence detectable. A design variant with no code counterpart, or a code prop with no design variant, is a gap that shows up in review rather than in production. Libraries like Radix UI and shadcn/ui take the contract further by shipping unstyled or lightly-styled accessible primitives whose behavior (focus management, keyboard interaction, ARIA wiring) is the part that is genuinely hard to get right, leaving the token layer to supply the visual skin. Tailwind CSS is the common way to bind those tokens to markup as utility classes. The implementation details of that binding are the subject of the sibling modern web development page. Here the point is that the contract is defined in the design system and enforced at the component boundary.

Interaction and motion, easing, duration, and purpose

Motion in an interface is not decoration. It is communication, and undisciplined motion is noise. Animation has three legitimate jobs, to show continuity (a shared element that moves rather than cuts tells the eye the two states are the same object), to direct attention (a subtle motion draws the eye to a change), and to communicate causality (an element that springs from the button that spawned it explains where it came from). Motion that does none of these is a tax on the user's time and, for some users, a trigger for motion sickness, which is why prefers-reduced-motion exists and must be honored.

Two parameters govern a transition, duration and easing. Durations that feel right cluster tightly, roughly 100 to 150ms for small, local changes (a hover, a small toggle), 200 to 300ms for a typical transition (a panel opening), and longer for large or full-screen movements, with anything past about 400ms starting to feel sluggish for routine interactions. Easing is the velocity profile over that duration, expressed as a cubic Bezier. Linear motion (constant velocity) looks mechanical because nothing in the physical world starts and stops instantly. Natural motion accelerates and decelerates. Ease-out (fast start, slow settle) is the default for elements entering or responding to a user action because it feels immediately responsive and settles gently. Ease-in (slow start, fast exit) suits elements leaving the screen. Ease-in-out suits elements moving from one on-screen position to another. The standard material easing curve, roughly cubic-bezier(0.4, 0, 0.2, 1), is an ease-out chosen so motion decelerates into place, and it is worth recognizing as a deliberate, reusable token rather than a magic number.

Fitts's law, derived and applied

Fitts's law is the quantitative backbone of target sizing and the reason "make the button bigger and closer" is a theorem rather than an opinion. Fitts modeled rapid aimed movement as an information-transmission problem. The difficulty of acquiring a target of width \( W \) at distance \( D \) grows with how precisely the movement must be controlled, which is the ratio of the distance to be covered to the tolerance allowed at the end. He defined the index of difficulty in bits as

$$ \mathrm{ID} = \log_2\!\left(\frac{2D}{W}\right), $$

an information measure, since covering distance \( D \) while landing within \( W \) is like resolving \( 2D/W \) equally likely locations, which takes \( \log_2(2D/W) \) bits. If the motor system transmits at a roughly constant rate (an index of performance \( \mathrm{IP} \) in bits per second), then movement time is the number of bits divided by the rate, plus a fixed overhead \( a \) for reaction and initiation, giving the Shannon form of Fitts's law

$$ \mathrm{MT} = a + b\,\log_2\!\left(\frac{2D}{W}\right), \qquad b = \frac{1}{\mathrm{IP}}, $$

a linear relationship between movement time and index of difficulty whose empirical fit across mice, touch, and even limbs is very good, with typical constants \( a \approx 0.2 \) s and \( b \approx 0.1 \) to \( 0.16 \) s/bit for pointer input. Two consequences are immediate and design-defining. First, movement time grows only logarithmically with distance, so a target twice as far away is not twice as slow, but it grows the same way with the inverse of width, so doubling a target's size buys the same time saving as halving its distance, and size is usually the cheaper lever. Second, targets at a screen edge are effectively infinitely large along the edge axis (the pointer cannot overshoot past the edge), which is the formal reason menu bars and dock targets live on edges and corners. Problem 3 computes the time cost of undersizing a touch target below the platform minimum.

Hick's law and the cost of choice

Where Fitts governs the motor cost of reaching a target, Hick's law governs the cognitive cost of choosing among targets. Hick found that the time to make a decision grows logarithmically with the number of equally probable alternatives,

$$ \mathrm{RT} = a + b\,\log_2\!\left(n + 1\right), $$

where \( n \) is the number of choices and the \( +1 \) accounts for the option of "no choice yet," i.e. the decision to decide. The logarithm again reflects an information measure. Choosing among \( n \) equally likely options conveys \( \log_2(n+1) \) bits, and the brain resolves them at a roughly constant rate, so a menu of eight items is not eight times slower than a menu of one but only about \( \log_2(9) \approx 3.17 \) times the per-bit cost above baseline. The design lesson is not "always fewer options," because collapsing choices onto deeper menus trades Hick cost for navigation cost and Fitts cost. It is that the structure of choices matters, that categorizing many options into a few groups (so the user makes two small logarithmic decisions instead of one large one) can beat a flat list, and that the law only applies cleanly when options are equally probable and the user is searching rather than recognizing a known target, which is why a familiar toolbar the user has memorized escapes the penalty.

Accessibility as a correctness property

Accessibility is the part of design that is least a matter of taste and most a matter of correctness, which is what makes it natural territory for engineers. The WCAG guidelines are organized under four principles, that content be Perceivable, Operable, Understandable, and Robust, and most of the testable success criteria reduce to properties a program can check. Contrast, derived above, is one. A color pair either meets a numeric threshold or it does not. Touch-target size is another. The platform human-interface guidelines set minimums, roughly a 44-by-44 point target on iOS per the Apple HIG and 48-by-48 density-independent pixels in Material Design, and WCAG 2.2 added a 24-by-24 CSS-pixel minimum as success criterion 2.5.8, all of which are Fitts's law codified as policy because a target too small is both hard to hit and, for users with motor impairments, impossible. Screen-reader semantics require that the accessibility tree, the structure a screen reader narrates, carry the same information as the visual layout, headings marked as headings, buttons as buttons, images with alt text, form fields with labels, so that a non-visual traversal reaches the same understanding as a visual one. Focus order requires that keyboard traversal follow a logical sequence, usually the reading order, and that every interactive element be reachable and visibly focused, which is the keyboard analogue of a sensible tab through a form. None of these is subjective. Each is a property that can be tested in CI and regressed like any other correctness property, and treating them that way is the difference between accessibility as an afterthought and accessibility as engineering.

Usability testing and the discovery curve

A usability test observes real users attempting real tasks and counts where they fail, and its most cited result is the claim that about five users are enough to find the majority of problems. That claim is a consequence of a simple model, not a rule of thumb, and understanding the model matters because its assumptions are exactly where it breaks. Model each usability problem as having a probability \( p \) that any single tested user encounters it. If users are independent, the probability that a given problem is missed by all \( n \) users is \( (1-p)^n \), so the probability it is found by at least one is

$$ P(\text{found}) = 1 - (1-p)^{n}. $$

Averaged over many studies, Nielsen and Landauer (1993) estimated a typical per-user detection probability of \( p \approx 0.31 \). Substituting \( n = 5 \) gives \( 1 - (1-0.31)^5 = 1 - 0.69^5 \approx 0.84 \), the "five users find about 85% of problems" figure. The curve is concave. It rises steeply at first, each of the first few users surfaces many new problems, then flattens as later users mostly re-find known problems, which is the mathematical argument for running several small tests and fixing between them rather than one large test. Problem 2 works this and its extensions numerically.

The limits of the model are as important as the model. It assumes a single homogeneous population with one \( p \). If the product serves distinct user groups (novices and experts, say) with different problem sets, five users drawn from one group tell you nothing about the other, and the effective \( p \) for cross-group problems is far lower, so the five-user claim collapses. It assumes problems are independent and detection is memoryless, whereas in practice finding one problem can mask another downstream. And it speaks only to discovery, whether a problem is seen at least once, not to measurement. Estimating how common a problem is, or comparing two designs on a metric, is a quantitative question that needs a quantitative sample, which is a different and larger number. The discipline is to use small qualitative tests to find problems and larger quantitative tests or A/B experiments to measure them. Standard quantitative usability metrics include task success rate, time on task, error count, and the System Usability Scale (Brooke, 1996), a ten-item questionnaire scored 0 to 100 whose often-cited benchmark is that a score near 68 is average and above roughly 80 is good. The SUS is popular precisely because it is short, technology-agnostic, and comparable across studies.

Hypothesis, power, and significance in A/B testing

An A/B test is a randomized controlled experiment on live traffic. Users are randomly assigned to a control (A) or a treatment (B), and a metric, conversion rate, click-through, retention, is compared. The statistics are the same two-proportion machinery as any clinical trial, and getting them right is what separates a real result from a story told about noise. The null hypothesis \( H_0 \) is that the two variants have the same true conversion rate, \( p_A = p_B \). The alternative is that they differ. The test statistic for two proportions is the standardized difference

$$ z = \frac{\hat{p}_B - \hat{p}_A}{\sqrt{\hat{p}\,(1-\hat{p})\left(\tfrac{1}{n_A} + \tfrac{1}{n_B}\right)}}, \qquad \hat{p} = \frac{x_A + x_B}{n_A + n_B}, $$

where \( \hat{p} \) is the pooled rate under \( H_0 \) and the denominator is the standard error of the difference. Under \( H_0 \) and a large enough sample, \( z \) is approximately standard normal, so a two-sided p-value is \( 2\,[1 - \Phi(|z|)] \), and the result is called significant at level \( \alpha \) (conventionally 0.05) when \( p < \alpha \). Two errors bound the design. A Type I error (\( \alpha \)) is declaring a difference that is not real, a Type II error (\( \beta \)) is missing a difference that is real, and the power \( 1 - \beta \) is the probability of detecting a true effect of a chosen size. Before running the test, one fixes \( \alpha \), the desired power (commonly 0.8), and the minimum detectable effect (the smallest lift worth caring about), and solves for the per-arm sample size

$$ n \approx \frac{\left(z_{1-\alpha/2}\sqrt{2\bar{p}(1-\bar{p})} + z_{1-\beta}\sqrt{p_A(1-p_A)+p_B(1-p_B)}\right)^2}{(p_B - p_A)^2}, $$

with \( \bar{p} = (p_A + p_B)/2 \). The dominant term is the inverse-square dependence on the effect size, \( n \propto 1/(p_B - p_A)^2 \). Halving the effect you want to detect quadruples the required sample, which is why detecting small lifts on a high-traffic product still takes weeks and why underpowered tests, run until they happen to cross significance and then stopped, are the most common way teams fool themselves. Problem 4 computes a real sample size and a real significance verdict. The full treatment of sequential testing, multiple comparisons, and the pitfalls of peeking is developed by Kohavi, Tang, and Xu (2020). Any statistics-heavy sibling material and the deeper hypothesis-testing derivations live in the statistical learning notes.

AI-assisted design workflows and their failure modes

Generative tools now sit inside the design process at several points, and the useful mental model is that they are fast, cheap divergence engines with unreliable convergence. For exploration they are genuinely valuable. Prompting a model for twenty layout variations, a range of color palettes, a first draft of interface copy, or filler content that is more realistic than lorem ipsum compresses the early, plural, low-fidelity part of the process where quantity is the goal and the cost of a bad option is a glance. Image models fill placeholder imagery. Text-to-UI tools sketch a first structural draft to react against. Models embedded in Figma and adjacent tools rename layers, generate variants, and draft content. The failure modes are specific and worth naming. Generated designs regress to the mean of their training data, so they produce competent, generic, already-seen layouts and rarely the insight that came from the specific research finding, which means they are a starting point to diverge from, not an answer to converge on. They confidently violate the correctness properties, inventing color pairs that fail contrast, targets below the size minimum, and reading orders that make no semantic sense, because they optimize for plausible appearance, not for the WCAG threshold or the accessibility tree, so every generated artifact must be run through the same objective checks derived above. And they do not do needfinding. A model cannot interview a user, sit in their context, or discover the surprising insight that reframes the problem, so the parts of the process that are about learning what to build remain human and, if anything, become more valuable as the parts that are about producing screens become cheap. The durable division is that AI accelerates production and exploration while judgment, research, and verification against measurable properties stay with the engineer.

Worked problems

Problem 1

Contrast-ratio verdict for a real pair. A design system uses white text (#ffffff) on a primary button whose fill is blue-600 = #2563eb. Compute the WCAG contrast ratio and state, for each of normal-text AA, normal-text AAA, and large-text AA, whether the pair passes.

Solution. Work in the sRGB linearization. White is trivial. Every channel is \( c = 1 \), which linearizes to \( 1 \), so \( L_{\text{white}} = 0.2126 + 0.7152 + 0.0722 = 1 \). For #2563eb the 8-bit channels are \( (37, 99, 235) \), normalizing to \( R = 37/255 = 0.1451 \), \( G = 99/255 = 0.3882 \), \( B = 235/255 = 0.9216 \), all above the 0.03928 knee, so each uses the power branch \( ((c+0.055)/1.055)^{2.4} \).

$$ R_{\text{lin}} = \left(\tfrac{0.1451+0.055}{1.055}\right)^{2.4} = 0.01850, \quad G_{\text{lin}} = \left(\tfrac{0.3882+0.055}{1.055}\right)^{2.4} = 0.12477, \quad B_{\text{lin}} = \left(\tfrac{0.9216+0.055}{1.055}\right)^{2.4} = 0.83077. $$

The blue's relative luminance is

$$ L_{\text{blue}} = 0.2126(0.01850) + 0.7152(0.12477) + 0.0722(0.83077) = 0.15315. $$

White is lighter, so it is \( L_1 \) and the blue is \( L_2 \).

$$ \mathrm{CR} = \frac{L_1 + 0.05}{L_2 + 0.05} = \frac{1 + 0.05}{0.15315 + 0.05} = \frac{1.05}{0.20315} = 5.17. $$

The verdict follows. \( 5.17 \ge 4.5 \), so it passes normal-text AA. \( 5.17 < 7 \), so it fails normal-text AAA. \( 5.17 \ge 3 \), so it passes large-text AA. White on blue-600 is safe for body-size button labels under AA but would not reach AAA, and if a stricter target were required the fix is a darker blue. Dropping to blue-700 = #1d4ed8 raises the ratio because it lowers \( L_2 \). The computation is verified in the code section.

Problem 2

The five-user discovery curve. Take the model \( P(\text{found}) = 1 - (1-p)^n \) with a per-user detection probability \( p = 0.31 \). (a) What fraction of problems is found by 5 users, by 10, and by 15? (b) How many users are needed to reach 95% discovery? (c) Now suppose the product actually serves two equal-sized user groups whose problems do not overlap, and each tested user belongs to one group. If you test 5 users split roughly evenly, what happens to discovery of a problem that only affects one group?

Solution. (a) Direct substitution with \( 1 - 0.69^n \) gives

$$ n=5:\quad 1 - 0.69^5 = 1 - 0.1564 = 0.844, \qquad n=10:\quad 1 - 0.69^{10} = 0.976, \qquad n=15:\quad 1 - 0.69^{15} = 0.996. $$

So 5 users find about 84%, 10 find about 98%, and 15 find about 99.6%. The sharply diminishing returns past 5 are the whole argument for small iterative tests. (b) Solve \( 1 - 0.69^n \ge 0.95 \), i.e. \( 0.69^n \le 0.05 \). Taking logs, \( n \ge \ln(0.05)/\ln(0.69) = (-2.996)/(-0.371) = 8.07 \), so \( n = 9 \) users reach 95% (as a check, \( 1 - 0.69^9 = 0.965 \), while \( n=8 \) gives 0.949, just short). (c) A problem confined to one group has, per tested user, detection probability \( 0.31 \) only for the roughly half of users in that group and \( 0 \) for the other half, so its effective per-user probability across the mixed sample is about \( 0.31 \times \tfrac{1}{2} = 0.155 \). With only about 2 to 3 users from the affected group, discovery of that problem is \( 1 - 0.69^{2.5} \approx 1 - 0.394 = 0.61 \), far below 84%. This is the model's central limitation made concrete. Heterogeneous populations break the single-\( p \) assumption, and the correct response is to test five users per distinct group, not five users total.

Problem 3

Fitts's law and touch-target size. A user must tap a button whose center is \( D = 400 \) px from where their thumb rests. Using the Shannon form \( \mathrm{MT} = a + b\,\log_2(2D/W) \) with \( a = 0.20 \) s and \( b = 0.15 \) s/bit, compute the movement time when the target is \( W = 44 \) px wide (the iOS minimum) and when a designer shrinks it to \( W = 22 \) px, and again when it is enlarged to \( W = 88 \) px. Interpret the differences.

Solution. Compute the index of difficulty \( \mathrm{ID} = \log_2(2D/W) = \log_2(800/W) \) for each width, then \( \mathrm{MT} = 0.20 + 0.15\,\mathrm{ID} \).

$$ W=44:\quad \mathrm{ID} = \log_2\!\tfrac{800}{44} = \log_2 18.18 = 4.184\ \text{bits}, \quad \mathrm{MT} = 0.20 + 0.15(4.184) = 0.828\ \text{s} = 828\ \text{ms}. $$ $$ W=22:\quad \mathrm{ID} = \log_2\!\tfrac{800}{22} = \log_2 36.36 = 5.184\ \text{bits}, \quad \mathrm{MT} = 0.20 + 0.15(5.184) = 0.978\ \text{s} = 978\ \text{ms}. $$ $$ W=88:\quad \mathrm{ID} = \log_2\!\tfrac{800}{88} = \log_2 9.09 = 3.184\ \text{bits}, \quad \mathrm{MT} = 0.20 + 0.15(3.184) = 0.678\ \text{s} = 678\ \text{ms}. $$

Halving the target from 44 to 22 px adds exactly one bit of difficulty (because \( \log_2 \) of a doubled ratio increases by 1) and therefore exactly \( b = 150 \) ms, a 18% slowdown, and it simultaneously pushes the target below the accessibility minimum where some users cannot hit it reliably at all. Doubling the target from 44 to 88 px removes one bit and saves 150 ms. The symmetry is the practical takeaway. Each doubling or halving of target size is worth one constant \( b \) of movement time regardless of the starting size, so the marginal value of enlarging a small target is the same as enlarging a large one, but the small target also carries a hard accessibility floor that the large one clears. The values are verified in the code section.

Problem 4

A/B sample size and significance. A checkout page converts at \( p_A = 10\% \). A team wants to detect an absolute lift of 2 percentage points (to 12%) at significance \( \alpha = 0.05 \) two-sided with 80% power. (a) How many users per arm are needed? (b) They run the test and observe 200/2000 conversions in control and 248/2000 in treatment. Is the result significant at 0.05? At 0.01?

Solution. (a) Use \( z_{1-\alpha/2} = z_{0.975} = 1.960 \) and \( z_{1-\beta} = z_{0.80} = 0.842 \), with \( p_A = 0.10 \), \( p_B = 0.12 \), and \( \bar{p} = 0.11 \). The numerator is

$$ \left(1.960\sqrt{2(0.11)(0.89)} + 0.842\sqrt{0.10(0.90)+0.12(0.88)}\right)^2 = \left(1.960(0.4425) + 0.842(0.4461)\right)^2 = (1.2431)^2 = 1.5453, $$

and dividing by \( (p_B - p_A)^2 = 0.02^2 = 0.0004 \) gives \( n = 1.5453/0.0004 = 3863 \) per arm (the code's exact computation gives 3841, and the small difference is rounding of the intermediate radicals). So roughly 3,800 to 3,900 users per arm, about 7,700 total, are required, and detecting a 1-point lift instead of 2 would quadruple that to over 30,000 total because \( n \propto 1/(p_B-p_A)^2 \). (b) With \( x_A = 200, n_A = 2000, x_B = 248, n_B = 2000 \), the observed rates are \( \hat{p}_A = 0.100, \hat{p}_B = 0.124 \), the pooled rate is \( \hat{p} = 448/4000 = 0.112 \), and the standard error is

$$ \mathrm{SE} = \sqrt{0.112(0.888)\left(\tfrac{1}{2000}+\tfrac{1}{2000}\right)} = \sqrt{0.09946 \times 0.001} = 0.009973. $$ $$ z = \frac{0.124 - 0.100}{0.009973} = \frac{0.024}{0.009973} = 2.407, \qquad p\text{-value} = 2[1 - \Phi(2.407)] = 0.0161. $$

Since \( 0.0161 < 0.05 \), the result is significant at 0.05. Since \( 0.0161 > 0.01 \), it is not significant at 0.01. Note that with 2,000 per arm the study is somewhat underpowered for a 2-point effect (the part-a target was about 3,900), which is exactly why the observed effect, though real-looking, lands in the ambiguous zone between the two thresholds. A properly powered test would have resolved it more decisively. Both computations are verified in the code section.

Problem 5

A modular type ramp and its rounding error. A team adopts a modular scale with base \( t_0 = 16 \) px and ratio \( r = 1.25 \). Compute the raw sizes for steps \( k = -1 \) through \( 4 \), round each to the nearest whole pixel, and give the largest relative rounding error introduced. Then state the smallest ratio that would keep the step from 16px to the next size at least 3px, so the hierarchy stays visible.

Solution. The raw ramp \( t_k = 16 \cdot 1.25^k \) is

$$ \begin{array}{c|cccccc} k & -1 & 0 & 1 & 2 & 3 & 4 \\\hline t_k & 12.80 & 16.00 & 20.00 & 25.00 & 31.25 & 39.0625 \\ \text{round} & 13 & 16 & 20 & 25 & 31 & 39 \end{array} $$

Only two steps are not already integers, with 31.25 rounding to 31 and 39.0625 rounding to 39. The relative rounding errors are \( |31 - 31.25|/31.25 = 0.008 \) (0.8%) and \( |39 - 39.0625|/39.0625 = 0.0016 \) (0.16%), and 12.80 to 13 is \( 0.20/12.80 = 0.0156 \) (1.56%). The largest is the caption step, \( 1.56\% \), which is imperceptible, confirming that storing the ramp as rounded integer tokens costs essentially nothing. For the second part, the step from \( t_0 = 16 \) to \( t_1 = 16r \) is \( 16r - 16 = 16(r-1) \). Requiring \( 16(r-1) \ge 3 \) gives \( r \ge 1 + 3/16 = 1.1875 \). So any ratio at or above about 1.19, which includes the common 1.2 (minor third) and everything larger, keeps adjacent body-and-heading sizes at least 3px apart. A ratio below that, sometimes chosen for very dense data interfaces, produces heading sizes so close to body text that the hierarchy has to be carried by weight and color instead of size.

Implementation

The first block is a real, minimal design-token file expressing all three tiers, primitive, semantic, and component, with a dark theme, followed by the CSS custom properties that consume it. The JSON is the platform-neutral source a tool like style-dictionary would read. The CSS is one of its outputs, and the two together are the same theming construction this site uses. Primitives are the palette, semantics are the roles that switch by theme, and flipping data-theme="dark" repoints the semantic layer while every component token downstream is untouched.

{
  "$comment": "Tier 1 primitives: named for what they ARE, no usage meaning.",
  "primitive": {
    "color": {
      "white":  "#ffffff",
      "gray":  { "50": "#f9fafb", "100": "#f3f4f6", "500": "#6b7280",
                 "700": "#374151", "900": "#111827", "950": "#0b0c0f" },
      "blue":  { "500": "#3b82f6", "600": "#2563eb", "700": "#1d4ed8" }
    },
    "space": { "1": "4px", "2": "8px", "3": "12px", "4": "16px",
               "6": "24px", "8": "32px", "12": "48px", "16": "64px" },
    "font-size": { "xs": "0.8rem", "sm": "0.9rem", "md": "1rem",
                   "lg": "1.25rem", "xl": "1.5625rem", "2xl": "1.953rem" },
    "radius": { "sm": "4px", "md": "8px", "lg": "12px" },
    "duration": { "fast": "120ms", "base": "220ms" },
    "easing": { "standard": "cubic-bezier(0.4, 0, 0.2, 1)" }
  },

  "$comment2": "Tier 2 semantics: named for ROLE, aliased to a primitive. Theming lives here.",
  "semantic": {
    "light": {
      "color-bg":              "{primitive.color.white}",
      "color-surface":         "{primitive.color.gray.50}",
      "color-border":          "{primitive.color.gray.100}",
      "color-text":            "{primitive.color.gray.900}",
      "color-text-muted":      "{primitive.color.gray.500}",
      "color-accent":          "{primitive.color.blue.600}",
      "color-accent-contrast": "{primitive.color.white}"
    },
    "dark": {
      "color-bg":              "{primitive.color.gray.950}",
      "color-surface":         "{primitive.color.gray.900}",
      "color-border":          "{primitive.color.gray.700}",
      "color-text":            "{primitive.color.gray.50}",
      "color-text-muted":      "{primitive.color.gray.500}",
      "color-accent":          "{primitive.color.blue.500}",
      "color-accent-contrast": "{primitive.color.gray.950}"
    }
  },

  "$comment3": "Tier 3 component: named for a part, aliased to a semantic role.",
  "component": {
    "button-primary-bg":  "{semantic.color-accent}",
    "button-primary-fg":  "{semantic.color-accent-contrast}",
    "button-padding-y":   "{primitive.space.2}",
    "button-padding-x":   "{primitive.space.4}",
    "button-radius":      "{primitive.radius.md}"
  }
}
/* Output of the token transform. Primitives as raw values, semantics as
   theme-switched roles, components referencing semantics. This is the same
   construction the site's own dark mode uses. */
:root {
  /* tier 1: primitives */
  --gray-50: #f9fafb;  --gray-100: #f3f4f6; --gray-500: #6b7280;
  --gray-700: #374151; --gray-900: #111827; --gray-950: #0b0c0f;
  --blue-500: #3b82f6; --blue-600: #2563eb; --blue-700: #1d4ed8;
  --space-2: 8px; --space-4: 16px;
  --radius-md: 8px;
  --duration-base: 220ms;
  --easing-standard: cubic-bezier(0.4, 0, 0.2, 1);

  /* tier 2: semantics (light default) */
  --color-bg: #ffffff;
  --color-surface: var(--gray-50);
  --color-border: var(--gray-100);
  --color-text: var(--gray-900);
  --color-text-muted: var(--gray-500);
  --color-accent: var(--blue-600);
  --color-accent-contrast: #ffffff;
}

:root[data-theme="dark"] {
  /* tier 2 repointed: only the semantics change, nothing downstream does */
  --color-bg: var(--gray-950);
  --color-surface: var(--gray-900);
  --color-border: var(--gray-700);
  --color-text: var(--gray-50);
  --color-accent: var(--blue-500);
  --color-accent-contrast: var(--gray-950);
}

/* tier 3: a component consumes only semantics, so it themes for free */
.button-primary {
  background: var(--color-accent);
  color: var(--color-accent-contrast);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  transition: background var(--duration-base) var(--easing-standard);
}

The second block computes the WCAG contrast ratio from first principles, exactly the arithmetic of Problem 1, and checks the AA and AAA verdicts. It is runnable with the standard library alone.

def _linearize(c8):
    # sRGB 8-bit channel -> linear-light in [0, 1]
    c = c8 / 255.0
    return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4

def relative_luminance(hex_color):
    h = hex_color.lstrip("#")
    r, g, b = (int(h[i:i + 2], 16) for i in (0, 2, 4))
    R, G, B = _linearize(r), _linearize(g), _linearize(b)
    return 0.2126 * R + 0.7152 * G + 0.0722 * B   # eye-weighted, green heaviest

def contrast_ratio(fg, bg):
    L1, L2 = relative_luminance(fg), relative_luminance(bg)
    hi, lo = max(L1, L2), min(L1, L2)
    return (hi + 0.05) / (lo + 0.05)              # 0.05 models viewing flare

def verdict(cr):
    return {
        "AA_normal":  cr >= 4.5,
        "AAA_normal": cr >= 7.0,
        "AA_large":   cr >= 3.0,
    }

cr = contrast_ratio("#ffffff", "#2563eb")
print(round(cr, 3), verdict(cr))
# 5.169 {'AA_normal': True, 'AAA_normal': False, 'AA_large': True}

The third block is the usability discovery curve and its inversion, the arithmetic of Problem 2, including the users-needed-for-95% solve. It uses only the standard library.

import math

def discovered(p, n):
    "Fraction of problems found by n independent users, per-user prob p."
    return 1.0 - (1.0 - p) ** n

def users_for(target, p):
    "Smallest integer n with discovered(p, n) >= target."
    # 1 - (1-p)^n >= target  =>  (1-p)^n <= 1 - target
    n = math.log(1.0 - target) / math.log(1.0 - p)
    return math.ceil(n)

p = 0.31
for n in (5, 10, 15):
    print(n, round(discovered(p, n), 3))
# 5 0.844 / 10 0.976 / 15 0.996

print("n for 95%:", users_for(0.95, p))     # 9
print("check n=8:", round(discovered(p, 8), 3))   # 0.949 (short)
print("check n=9:", round(discovered(p, 9), 3))   # 0.965 (clears)

The fourth block is the A/B machinery of Problem 4, the two-proportion power-based sample size and the significance test with its p-value. It uses statistics.NormalDist from the standard library for the normal quantiles and CDF, so it runs without SciPy.

import math
from statistics import NormalDist

Z = NormalDist()   # standard normal

def sample_size(p_a, p_b, alpha=0.05, power=0.80):
    "Per-arm n to detect p_b vs p_a, two-sided, given power."
    z_a = Z.inv_cdf(1 - alpha / 2)   # 1.9600 for alpha=0.05
    z_b = Z.inv_cdf(power)           # 0.8416 for power=0.80
    pbar = (p_a + p_b) / 2.0
    num = (z_a * math.sqrt(2 * pbar * (1 - pbar))
           + z_b * math.sqrt(p_a * (1 - p_a) + p_b * (1 - p_b))) ** 2
    return math.ceil(num / (p_b - p_a) ** 2)

def ab_significance(x_a, n_a, x_b, n_b):
    "Two-sided p-value for a difference in conversion rates."
    p_a, p_b = x_a / n_a, x_b / n_b
    pool = (x_a + x_b) / (n_a + n_b)
    se = math.sqrt(pool * (1 - pool) * (1 / n_a + 1 / n_b))
    z = (p_b - p_a) / se
    pval = 2 * (1 - Z.cdf(abs(z)))
    return z, pval

print("per-arm n:", sample_size(0.10, 0.12))       # 3841
z, pval = ab_significance(200, 2000, 248, 2000)
print("z=%.3f  p=%.4f" % (z, pval))                 # z=2.407  p=0.0161
print("sig @0.05:", pval < 0.05, " sig @0.01:", pval < 0.01)

How it is done in practice

The tidy ladder of research, framing, ideation, prototyping, and evaluation is a description of the decisions, not of the calendar. In a shipping team the phases interleave and compress. A designer runs a handful of interviews while building a low-fidelity prototype, tests it the same week, and revises before the research is fully "done," because the goal is not a complete study but a decision made with enough evidence to be worth acting on. The artifact that carries the design is a single Figma file, or a small set of them, structured as a system, a page of primitives and semantic variables, a page of components with their variant matrices, a page of screens assembled from those components, and a flow of prototype connections that a stakeholder or a test participant can click. The discipline that makes this scale is that the file is built as a structured document from the start, auto layout everywhere so screens reflow, variables everywhere so a theme or token change propagates, components everywhere so a fix lands once, so that the same file serves ideation, review, testing, and handoff without being rebuilt for each.

Evaluation in practice is a two-speed system that mirrors the discovery-versus-measurement split. Qualitative usability tests are small (roughly five participants per distinct user group, per the model above), run continuously, often remotely and unmoderated through a testing platform, and their output is a ranked list of problems to fix, not a statistic. Quantitative evaluation is the A/B test, run on live traffic once a change is built, and here the engineering reality diverges from the textbook in ways worth knowing. Real tests suffer from peeking (checking significance repeatedly inflates the false-positive rate, which is why sequential or always-valid methods exist), from multiple comparisons (testing many metrics guarantees some cross a threshold by chance), from novelty and primacy effects (a change looks good or bad for the first week purely because it is new), and from sample-ratio mismatch (a bug in assignment that quietly biases the split and invalidates the whole test). The mature version of A/B testing, as documented by the experimentation teams at large web companies, is as much about detecting these failure modes as about computing the p-value, and the p-value derived here is the beginning of the analysis, not the end of it. The design system itself is treated as production software, versioned, documented, published as a package that both the Figma library and the code consume, and governed so that a new component enters the system deliberately rather than accreting as one-off screens.

The current frontier

Three shifts are actively reshaping the practice. The first is the deep integration of generative models into the design tools themselves. Layer renaming, variant generation, content drafting, image synthesis, and text-to-design that produces an editable first draft from a prompt all now live inside or adjacent to Figma and its competitors. The consensus that is forming, and it matches the failure-mode analysis above, is that these accelerate the production and exploration phases dramatically while leaving research, framing, and verification firmly human, because a model regresses to generic solutions and confidently violates the objective correctness properties. The second is the maturing of the design-token pipeline into genuine infrastructure. The emergence of the Design Tokens Community Group format as a cross-tool standard, and tools like style-dictionary and tokens-studio that read a Figma-authored token file and emit CSS, Swift, Android, and Tailwind config, are closing the last gap between the design source of truth and the shipped code, so that a token change is a commit rather than a manual reconciliation. The third is the elevation of accessibility from a late-stage audit to a continuously enforced property. Automated contrast checking, focus-order linting, and accessibility-tree assertions are moving into CI alongside the design system, which is the natural home for an engineer, because it makes the testable parts of accessibility regress like any other correctness property rather than decay between manual audits.

Open source and tools to read

  • amzn/style-dictionary — the reference token transform. Read config.json and a platform's transform group to see how one primitive-to-semantic token JSON becomes CSS variables, Swift, and Android XML.
  • tokens-studio — the Figma plugin and tooling that stores design tokens in a file and syncs them to a repo. Start with the token-format docs to see the three-tier structure expressed in a real schema.
  • radix-ui/primitives — unstyled, accessible component primitives. Open a component like Dialog or Dropdown Menu to see the focus-management and ARIA wiring that is the genuinely hard part of the component contract.
  • shadcn-ui/ui — components you copy into your codebase, built on Radix and Tailwind. The best worked example of tokens-plus-primitives-plus-utility-classes. Read the button component and its variant definition.
  • tailwindlabs/tailwindcss — the utility-class framework whose default theme is a spacing and type scale. Read the default theme config to see the 4/8 spacing ramp and the type scale as data.
  • Figma REST API and Figma plugin API — the document tree, variables, and components exposed programmatically. The plugin variables API is how token-sync tools read a file.
  • WebAIM contrast checker — the standard reference implementation of the WCAG formula derived above. Useful to confirm a computed ratio and read off the AA/AAA verdicts.
  • Canva and Figma — the two tools this page contrasts. Canva for finished marketing and deck assets, Figma for the product interface, design system, and engineering handoff.

Common misconceptions

"Design is how it looks, and the visual part is the whole of it." The visual layer is the last and often the smallest part. The decisions that determine whether a product works, what need it serves, how the problem is framed, whether the flow is learnable, are made before any color is chosen, and a beautiful interface to the wrong problem is a well-executed failure.

"More user research means asking users what features they want." Users are reliable witnesses to their problems and unreliable predictors of their own future behavior. Good research asks about past concrete episodes and observes actual work. A feature request is a solution the user invented, and it should be mined for the underlying need, not implemented literally.

"Higher fidelity is always better." Fidelity is a cost, in time and in the unwanted commitment a polished artifact signals. The right fidelity is the lowest one that answers the current question, and jumping to a coded prototype early is usually the most expensive version of this mistake.

"Five users is a hard rule for usability testing." Five users find about 85% of problems only under a single-population, single-\( p \) model. Distinct user groups, quantitative comparison, and measuring how common a problem is all break the assumption, and the correct response is five users per group for discovery and a proper power calculation for measurement.

"Accessibility is a subjective nice-to-have added at the end." The core of accessibility is a set of objective, testable properties, contrast ratios against fixed thresholds, touch-target minimums, focus order, accessibility-tree semantics, that can be checked in CI and regressed like any correctness bug. It is engineering, not decoration, and it is cheapest to build in from the token layer up.

"An A/B test that crossed p < 0.05 proves the change is good." A single p-value from a test that was peeked at, underpowered, or one of many comparisons proves very little. Significance requires a pre-registered effect size and sample, a fixed stopping rule or a sequential method, and a check for sample-ratio mismatch and novelty effects before the number means anything.

"AI design tools will replace the designer." They replace part of production and accelerate exploration, the parts where quantity is cheap and a bad option costs a glance. They cannot interview a user, discover a reframing insight, or reliably satisfy the objective correctness properties, so the judgment-and-research core of the work becomes more valuable, not less.

Self-check

References

  1. Norman, D. A. (2013). The Design of Everyday Things (Revised and Expanded ed.). Basic Books. Affordances, signifiers, mapping, and feedback. The foundational account of why interfaces confuse and how to fix it.
  2. Krug, S. (2014). Don't Make Me Think, Revisited: A Common Sense Approach to Web Usability (3rd ed.). New Riders. The case for self-evident design and the pragmatics of cheap usability testing.
  3. Cooper, A., Reimann, R., Cronin, D., & Noessel, C. (2014). About Face: The Essentials of Interaction Design (4th ed.). Wiley. Goal-directed design, personas, and interaction patterns.
  4. Tidwell, J., Brewer, C., & Valencia, A. (2020). Designing Interfaces: Patterns for Effective Interaction Design (3rd ed.). O'Reilly. A pattern catalog for layout, navigation, and data presentation.
  5. Nielsen, J. (1993). Usability Engineering. Morgan Kaufmann. The discount-usability method and the empirical basis of the discovery curve.
  6. Nielsen, J., & Landauer, T. K. (1993). A mathematical model of the finding of usability problems. Proceedings of the INTERCHI '93 Conference on Human Factors in Computing Systems, 206-213. The source of the 1-(1-p)^n model and the p about 0.31 estimate. doi:10.1145/169059.169166
  7. Fitts, P. M. (1954). The information capacity of the human motor system in controlling the amplitude of movement. Journal of Experimental Psychology, 47(6), 381-391. The original derivation of the movement-time law. doi:10.1037/h0055392
  8. MacKenzie, I. S. (1992). Fitts' law as a research and design tool in human-computer interaction. Human-Computer Interaction, 7(1), 91-139. The Shannon-form formulation used here and its application to pointing devices. doi:10.1207/s15327051hci0701_3
  9. Hick, W. E. (1952). On the rate of gain of information. Quarterly Journal of Experimental Psychology, 4(1), 11-26. The logarithmic law of choice reaction time. doi:10.1080/17470215208416600
  10. W3C. (2018). Web Content Accessibility Guidelines (WCAG) 2.1. World Wide Web Consortium. The relative-luminance and contrast-ratio definitions and the conformance thresholds. w3.org/TR/WCAG21
  11. W3C. (2023). Web Content Accessibility Guidelines (WCAG) 2.2. World Wide Web Consortium. Adds the 24-by-24 target-size success criterion (2.5.8) and focus-appearance criteria. w3.org/TR/WCAG22
  12. Wathan, A., & Schoger, S. (2018). Refactoring UI. The practical treatment of the type scale, spacing system, hierarchy, and color used throughout this page. refactoringui.com
  13. Google. Material Design 3. The color system, type scale, motion easing, and touch-target guidance cited as a design-system reference. m3.material.io
  14. Apple. Human Interface Guidelines. The 44-point touch-target minimum and platform interaction conventions. developer.apple.com/design/human-interface-guidelines
  15. Brooke, J. (1996). SUS: A "quick and dirty" usability scale. In P. W. Jordan et al. (Eds.), Usability Evaluation in Industry (pp. 189-194). Taylor & Francis. The ten-item System Usability Scale.
  16. Kohavi, R., Tang, D., & Xu, Y. (2020). Trustworthy Online Controlled Experiments: A Practical Guide to A/B Testing. Cambridge University Press. Power, sample size, peeking, sample-ratio mismatch, and the practical failure modes of A/B testing. doi:10.1017/9781108653985
  17. Christensen, C. M., Hall, T., Dillon, K., & Duncan, D. S. (2016). Competing Against Luck: The Story of Innovation and Customer Choice. HarperBusiness. The jobs-to-be-done framing of user need.
  18. Beyer, H., & Holtzblatt, K. (1998). Contextual Design: Defining Customer-Centered Systems. Morgan Kaufmann. The master-apprentice model and contextual inquiry method.
  19. Gothelf, J., & Seiden, J. (2016). Lean UX: Designing Great Products with Agile Teams (2nd ed.). O'Reilly. Hypothesis-driven design and the interleaving of research and build in practice.
  20. Knapp, J., Zeratsky, J., & Kowitz, B. (2016). Sprint: How to Solve Big Problems and Test New Ideas in Just Five Days. Simon & Schuster. The source of the crazy-8s exercise and the structured divergence/convergence process.
  21. Wickens, C. D., Hollands, J. G., Banbury, S., & Parasuraman, R. (2013). Engineering Psychology and Human Performance (4th ed.). Pearson. The information-theoretic account of Fitts's and Hick's laws and human performance modeling.

Design, stripped to its engineering core, is the disciplined reduction of two costs, the cost of being wrong about what to build, and the cost of not knowing whether it worked. Needfinding, framing, structured ideation, and the fidelity ladder are all machinery for learning that an idea is bad before it is expensive, which is why the correct fidelity is always the lowest one that answers the current question, and why interviews probe past episodes rather than solicit predictions. The productive part of the discipline is that so much of it is quantitative and testable. The WCAG contrast of white on blue-600 is 5.17 by a formula that decodes gamma, weights the eye's sensitivities, and adds a flare term, and that number passes AA and fails AAA with no room for argument. Fitts's law makes "bigger and closer" a theorem in which each doubling of a target is worth one constant of time. The 1-(1-p)^n curve explains why five users suffice for discovery and, through its single-p assumption, exactly when they do not. And the two-proportion sample size, inverse-square in the effect you want to detect, tells you before you start how much traffic an honest A/B test will cost. Figma is the tool where these ideas become concrete, its auto layout is flexbox, its variables are the semantic tier of a token system, its components are an API contract with engineering, so that the file a designer builds and the CSS an engineer ships are two serializations of one source of truth, while Canva stays in its lane of finished marketing assets and generative tools stay in theirs of cheap exploration that a human must verify against the objective properties. An engineer who internalizes this owns the parts of design that are correctness, contrast, target size, focus order, statistical power, and can hold the rest, the genuinely subjective parts, to a small set of principled scales rather than nudging pixels. The implementation half of the story, turning this design into shipped markup, continues on the modern web development page.