The Interaction Layer: seven principles of interaction design, each shown with a live demo
Interaction design is how a product behaves when you use it, not how it looks.
It covers things like whether you can tell your click worked, how fast the product feels, and whether you can undo a mistake. This page walks through seven principles. Each one is grounded in human computer interaction research and has a demo you can use right here, so you can try it for yourself.
- Context
- My own reference piece
- My role
- Researched, wrote and built it alone
- What I built
- Seven interaction design principles, each with a live demo, built by hand with no animation library
- Status
- Live
Every principle in one working demo
This is a real interface, built by hand with no animation library, and it uses every principle on the page. Open a pattern and it grows into a detail view you can use. Press ⌘K to open the command menu.
Open a tile and it grows out of the spot where it sat, instead of a new screen replacing it. Inside, you can drag the puck and feel the spring, publish a post and undo it, or reveal extra options from the keyboard. Turn on reduced motion from the command menu and every transition becomes an instant, clear change.
Principle 01
Every action gets an answer
Feedback & system status
If you click and nothing visibly happens, the product looks broken, even if it worked.
The first job of any interaction is to confirm it happened: the press registers, progress shows, success is clear and failure explains itself. Without that, people click again. That's where double orders and duplicate messages come from, and it isn't the user's fault.
Examples: the apps people call delightful are usually just very responsive. A task that checks off with a small animation, live cursors that show who else is on a shared canvas, a letter that appears the instant you type it. All of that is feedback.
Live demo: click both buttons and compare
This button adds the item but shows nothing, so you click again to be sure.
Cart: 0
Shows a pressed state, then "Adding…", then "Added ✓", and tells screen readers too. It can't be clicked again while the request runs.
Cart: 0
The left button invites extra clicks, and each one could add a duplicate. The right one makes the result clear, so you click once.
When pressed, the button shrinks to 0.86 of its size and back in 120ms. That's fast enough to say "got it" without slowing down the result.
Principle 02
Perceived speed matters more than measured speed
Perceived performance & latency
What people notice is how fast it feels, and you can design that separately from how fast it is.
You can't make the network faster, but you do control what people see while they wait. Show the result right away and confirm it with the server in the background (an optimistic update), and a 600ms request feels instant. Show a spinner for the same 600ms and the app feels slow.
Example: sometimes the right move is to add a delay on purpose. Gmail's "Undo Send" holds your message for 5, 10, 20 or 30 seconds (you choose) before it goes, so a mistake can be taken back. The point is to spend time where it helps the person.
Live demo: add a few tasks each way and watch the wait
In "wait" mode, the task only shows up after the server answers, so you sit and watch. In optimistic mode it shows up the moment you press Enter, marked "saving," then confirms. It isn't faster. It just doesn't make you wait.
New items fade in over 220ms, starting fast and slowing to a stop. Removals are quicker, about 150ms, so arrivals get noticed and dismissals get out of the way.
Principle 03
Show people what they can do
Affordances & signifiers
If people can't see that something is possible, they won't try it.
Don Norman separates two ideas. An affordance is what an object lets you do. A signifier is the visible cue that tells you so. A card might be draggable (the affordance), but it's the grip dots, the cursor change and the lift on hover (the signifiers) that tell you. Most products don't need more affordances. They need clearer signifiers for the ones they have.
Example: pull-to-refresh, invented by Loren Brichter in the Tweetie app (it earned him an Apple Design Award in 2009 and was patented in 2013), works because of its signifier. The list stretches as you pull, which tells you something will happen when you let go. Refreshing is the affordance. The stretch is how you find out about it.
Live demo: the same draggable rows, with and without signifiers
With signifiers off, the rows look fixed and you can't tell they drag. Turn them on and the grip, the grab cursor and the hover lift make it obvious. What you can do didn't change. Only what you can see did.
On hover the handle lifts 5px in 120ms. The movement itself tells you it can be picked up, before you touch it.
Principle 04
Prefer undo to "Are you sure?"
Forgiveness & reversibility
A confirmation dialog slows everyone down, every time, to catch a rare mistake. Undo only costs something for the person who made the mistake.
Confirmations feel safe, so they pile up, until people click "Yes" without reading and the dialog protects no one. It's better to act right away and offer a quick, easy way back. You get speed and safety instead of trading one for the other.
Examples: Gmail's "Undo Send" lets you take a message back for 5 to 30 seconds, whichever you choose. The undo message that pops up after you archive or delete works the same way. So does Cmd/Ctrl-Z, the most common keyboard shortcut there is.
Live demo: delete the same emails two ways and compare
"Confirm" asks you a question for every deletion, including the 95% you meant. "Delete + undo" just deletes, and you only do extra work for the one you regret. Count your clicks in each mode.
The undo message rises 16px as it fades in over 220ms, and leaves in 160ms. That's long enough to notice and quick enough not to get in the way.
Principle 05
Show what most people need, and keep the rest one step away
Progressive disclosure & density
Some complexity can't be removed. The job is to decide who deals with it, and when.
Put every option on screen and new users are overwhelmed. Hide everything in menus and experienced users are slowed down. Progressive disclosure means showing the few things most people need now and keeping advanced options one deliberate step away. A command palette is a good example: a search box you open with a shortcut that can run any action in the app.
Example: the command palette started in code editors and is now common in productivity apps. One shortcut (usually ⌘K or Ctrl-K) reaches every action without any of them taking up screen space.
Live demo: press the button or ⌘/Ctrl + K
Project workspace
A clean surface. Everything else is one keystroke away.
No command run yet.
Type to filter, ↑↓ to move, Enter to run, Esc to close. Every action is within reach, but the screen stays simple.
The palette grows from 0.96→1 of its size while fading in over 220ms, slowing as it arrives. It grows out of its button instead of just appearing.
Principle 06
Let people act on the thing itself
Direct manipulation & spatial consistency
Let people drag the card, instead of opening a "move card" dialog and typing a position number.
Direct manipulation means the thing you care about is on screen, you can grab it, and it changes as you move it, in a way you can reverse. When things also stay where you left them, people build a mental map of the interface instead of treating it like a form.
Examples: dragging a shape on a design canvas instead of typing its coordinates, dragging a card between columns on a board, reordering a list by dragging rows. This goes with spatial consistency: navigation, controls and items stay in the same place between visits, so people learn where things are.
Live demo: drag to reorder, or use the keyboard
Drag a row with the mouse, or select one with Tab, press Space to grab, move with ↑↓, and Space again to drop. Dragging that only works with a mouse leaves keyboard users out, so this works both ways and announces each move to screen readers.
While you drag, the row follows the pointer 1:1 with no lag. When you let go, a spring (stiffness 300 / damping 22) carries it into place.
Principle 07
Support both mouse and keyboard
Input, shortcuts & accessibility
A first-time user with a mouse and an expert who never leaves the keyboard should both be able to reach every action.
The mouse is easy to discover. The keyboard is fast. You don't have to pick one. Give every important action a visible button and a keyboard shortcut, and show the shortcut on the button so people learn it. This is also where interaction design and accessibility overlap: an interface you can fully use from the keyboard, with a visible focus ring, is faster for experts and usable by people who can't use a mouse at all.
Examples: keyboard-first productivity tools win loyal users by making the shortcut the main way in and the mouse the backup. Every demo on this page follows the same idea. Each one works from the keyboard, announces changes to screen readers, and respects the system setting for less motion (prefers-reduced-motion).
Live demo: use this toolbar with the mouse or the keyboard
Click a button, or hold Alt and tap the highlighted letter while focus is in this card.
No action yet.
The shortcut is shown on the button, so using the mouse teaches you the keyboard shortcut. Press Tab to see the focus ring that makes all of this usable without a mouse.
The focus ring does not animate between fields. It jumps straight there. Animating it would leave keyboard users waiting on their own Tab key, so here the right amount of motion is none.
Part two: the motion layer
How motion supports the seven principles
The seven principles cover what an action does and how you recover from it. This part covers motion: the timing, speed curves and movement that make those changes easy to follow. Every demo is built by hand with no animation library, and each one still works when the system asks for less motion (prefers-reduced-motion). You can drag them, fling them and stop them mid-move.
Motion 01
How the speed of a movement changes how it feels
Physics: easing & springs
The same move, played on three different speed curves, feels completely different: one mechanical, one smooth, one lively.
Easing is how speed changes during an animation. Linear motion keeps a constant speed, which nothing in the real world does, so it looks mechanical. Eased motion speeds up and slows down like a real object. A spring goes further: it behaves like a weight on a spring, so it can overshoot and settle. Because it tracks its own speed, it can also be interrupted without looking broken, which matters most on a touch screen.
Examples: iOS animates with springs rather than fixed durations, which is why a sheet you fling feels like a real object. In a work app, a button that squashes slightly when pressed and overshoots on release feels solid. The pixels are the same. Only the speed curve is different.
Live demo: play the same move on three curves, then adjust the spring
Linear
cubic-bezier(0,0,1,1)
Eased
cubic-bezier(.2,0,0,1)
Spring (overshoot)
stiffness 220 · damping 18
Watch how each puck finishes. Linear stops dead. Eased slows to a stop. The spring goes past and settles back. Stiffness is how hard the spring pulls toward its target, and damping is how quickly it calms down. Move the sliders and the graph redraws from the same code that moves the puck. Lower the damping to see more overshoot.
snappy spring (stiffness 300, damping 30) instead of a fixed-length animation. It can be interrupted, it adjusts to how far it travels, and the small overshoot feels responsive without looking bouncy. I keep bouncy springs (low damping) for playful, low-stakes moments.
Reduced motion: the curves are drawn but nothing plays on a loop. The graphs still show the idea.
Motion 02
Keep the same object on screen as the view changes
Continuity: shared-element transitions
When a small item opens into a larger view, the item itself should grow into that view, not vanish while a new screen appears.
A hard cut makes the eye find everything again. A shared-element transition keeps one object on screen through the change: the tapped tile's position, size and corner radius all animate into the detail view, so your attention stays with it. It feels like you opened something, not like a new screen loaded.
Examples: tap a photo in iOS Photos and the thumbnail itself zooms to fill the screen. Linear and Notion open a list row into a detail preview that grows out of the row you clicked, rather than a panel that slides over it.
Live demo: click any tile or row and watch it grow into the detail view
Detail
The card you clicked grew into this view, same element, no cut.
The detail view isn't a new element fading in. It's the same card, moved with a technique called FLIP: measure where it starts (First), place it at its final size (Last), shift it back to where it started (Invert), then let it animate to the end (Play). Close it and it shrinks back into its tile.
320ms on a standard curve and animates only transform, opacity and border-radius, which are cheap for the browser to draw. Closing takes ~220ms, so opening feels deliberate and closing feels quick.
Reduced motion: the detail view fades in place, with no zoom or movement. It still reads as the same item, without the kind of motion that makes some people dizzy.
Motion 03
Bring items in one after another, not all at once
Orchestration, stagger & follow-through
When many things appear together, the order they appear in helps people understand the layout.
If all of a dashboard's cards appear at the same instant, the screen just pops in and is harder to read. If they appear in a quick sequence, a few dozen milliseconds apart (a stagger), the eye follows them in reading order without anyone noticing why. Too much stagger and loading feels slow. The slider lets you find the right amount.
Examples: analytics dashboards bring their cards in one after another on load, so the page builds up instead of blinking on. Social feeds reveal posts in a quick top-to-bottom sequence. Move the slider below to see the difference.
Live demo: replay the reveal, change the stagger, then replay again
At 0 ms the cards appear together, flat and a bit jarring. Around 40 to 60 ms they arrive in a clear order. Past about 100 ms it starts to feel like waiting. Every card uses the same spring. Only its start time changes.
~50ms with a soft spring, capped so the last item lands within ~500ms in total. For longer lists I shorten the delay per item rather than make people wait for the page to finish.
Reduced motion: every card fades in at once. No rise, no stagger, no overshoot.
Motion 04
Animate the change instead of jumping to it
Layout diff: animating a change in order
When a list is sorted or filtered, rows should move to their new places, so you can follow the one you were looking at.
If a sort isn't animated, every row jumps at once and you have to scan the list again to find yours. Animating the difference fixes that: record where each row was, apply the new order, then slide each row from its old place to its new one. Filtering works the same way. Rows that leave fade out, and the rest slide up to close the gap.
Examples: sort a table in Linear and every row slides to its new place. Things reorders to-dos the same way. Reorder a playlist and the tracks slide rather than jump. Sort and filter the table below and nothing jumps.
Live demo: sort and filter, and every row slides to its new place
Each button uses FLIP again: record every row's position (First), reorder the rows in the page (Last), move each row back to where it was (Invert), then let them all slide to their new places (Play). Hide the paused rows and the rest slide up to fill in. You never lose your place.
260ms on a standard curve, animating transform only. The rows are already in their final order, so the browser doesn't have to recalculate the layout on every frame. Rows that leave fade out over ~160ms first, so they're gone before the others arrive.
Reduced motion: rows reorder and filter instantly. The end result is the same, with no movement.
Motion 05
Every animation can be interrupted
Springs that keep their speed when you grab them
The test: grab something while it's moving and push it the other way. Does it restart, or does it change direction and keep the speed it had?
A fixed-length animation can't be interrupted well. Catch it halfway and it either jumps to the end or restarts from a standstill, and both feel broken. A spring tracks position and speed every frame, so giving it a new target mid-move just changes where it's heading. It carries on from where it was, at the speed it had. That's why an iOS sheet feels like something you can throw, catch and redirect.
Examples: iOS Control Center and the share sheet, which you can fling, catch and reverse. In a work app, a panel you drag to resize that snaps to set points at 25, 50 and 75%. Both demos below run on the same hand-built spring, about 30 lines of code.
Live demo: drag and fling the sheet, then grab it mid-move and reverse
Now Playing
Fling me up or down. Catch me mid-animation and drag the other way. I keep my momentum and re-target. I don't restart.
Half open.
Work app version: drag to resize, with snap points. Drag the divider (or select it and use ←→), let go, and it springs to the nearest snap point, keeping its speed.
Sidebar at 50%.
It all happens on release. The speed of your last movement is passed straight to the spring, so a fast flick goes farther than a slow drag. Reverse mid-move and the spring changes direction without dropping to zero speed.
snappy spring (stiffness 300, damping 30, mass 1), updated every frame with requestAnimationFrame using a simple physics step (semi-implicit Euler). Release speed comes from the last few pointer positions and is passed to .setTarget(), so the same spring changes target instead of starting over.
Reduced motion: dragging still follows your finger exactly, but on release it jumps straight to the nearest snap point. No spring, no overshoot.
Motion 06
Things appear from whatever opened them
Origin: cause & effect
A menu that grows out of the button you pressed clearly came from that press. A menu that fades in at the center of the screen doesn't.
Motion is the clearest way to show cause and effect. When a popover grows from the corner of its button (its transform-origin, the point it grows from), you can see where it came from. When a new row slides out from the + button that made it, the link between action and result is obvious. Get the starting point wrong and even a nice animation feels random.
Examples: long-press an iOS app icon and the menu grows out of the icon itself. In a work app, a "+ New" button sends the new row out from under your cursor. Try both below.
Live demo: open the menu, then add a row with the button
The menu grows from its top-left corner, where the button is, so it clearly belongs to that button. New rows start small and slightly offset toward the button, then settle into place, so you can see the path from click to result.
0.92 → 1 with its transform-origin at the button, over 180ms, slowing as it arrives. New rows come in on a soft spring from a small offset, so the eye follows the path from button to row.
Reduced motion: the menu and rows fade in place. The movement cue is gone, but they still appear next to the button that made them, so the link stays clear.
Motion 07
Act now, then show whether it worked
Optimistic updates, success & failure
The rule: act now, reconcile later, visibly. Show the result as soon as someone acts, then use motion to show whether the server agreed.
This is Principle 2 (perceived speed) in motion. The message moves into place before the network answers, so sending feels instant. Then the app checks with the server, which is what reconcile means here. On success the message settles and gets a timestamp. On failure it shakes, is marked as failed and offers a retry. The animation is how the result gets communicated, including when something goes wrong.
Examples: iMessage shows your bubble right away and changes its color if it fails to send. A task manager adds a new task instantly and checks with the server afterwards. Turn on "Force failure" below to see what happens when it goes wrong.
Live demo: send a few messages, then turn on "Force failure"
The bubble appears the moment you press Send, marked "sending." On success it settles and gets a timestamp. On failure it shakes once, turns red and stays (with a retry) instead of quietly disappearing. Both outcomes get the same care.
200ms, slowing as it arrives. On success, the "sending" label fades. On failure it plays one ~320ms shake (small, once, never looping) and changes color, so the meaning doesn't depend on motion alone.
Reduced motion: the bubble appears without sliding in, and failure is shown with color and text only, no shake.
Motion 08
Knowing when not to animate
Restraint & reduced motion
For some people, zooms and parallax (layers moving at different speeds) cause real nausea and dizziness. Respecting that shouldn't cost them anything. The interface still has to say everything, without the movement.
A reduced-motion mode isn't just "animations off." Every big movement gets a calm replacement, like a fade instead of a zoom or an instant reorder instead of a slide, so the information the motion carried still gets across. The switch below controls this whole page, and every demo above responds to it. I design the motion first, then design what replaces it with the same care.
Example: turn on Reduce Motion in iOS and the zoom when you open an app becomes a gentle fade everywhere. The app still opens, just more calmly. Every demo on this page does the same when you flip the switch.
Live demo: this switch controls the whole page
Reduce motion (whole page)
Motion: full. Synced to your system setting.
Can cause dizziness · large zoom + parallax
Safe replacement · fade only
With full motion, the left card zooms and drifts. Most people find that fine, but it can make some people feel sick. Turn on the switch and play it again: it becomes the same calm fade as the right one, and nothing is lost.
motionOK() helper checks both the system setting (matchMedia('(prefers-reduced-motion: reduce)')) and this switch. Every demo checks it, and the page root carries data-motion="full|reduced" so CSS transitions switch off too. The page follows your system setting by default. The switch can only reduce motion further, never override someone who asked for less.
If your system already asks for reduced motion, this page started in reduced mode and the switch shows it.
Summary
What the principles have in common
All seven are about the same thing: what happens between an action and its result. Every action needs an answer (feedback) and should feel fast (speed). People should be able to see what they can do (affordance), get it wrong safely (forgiveness), meet complexity only when they need it (disclosure), work on things directly (manipulation) and use whichever input they prefer (input). When these are right, people stop noticing the interface. Motion ties them together by showing how things change, and knowing when to hold still matters just as much.
The seven principles at a glance
The motion layer at a glance
Where these principles show up in my work
These principles shaped the main decisions in these projects: interview scores you can challenge and watch change, an app agents use at a property with no signal, and a calculator that shows its assumptions on the page.