Interaction Design

Interaction design is the art of making interfaces feel alive and responsive. Every tap, click, hover, and scroll should produce appropriate feedback. When feedback is absent, users feel uncertain. When it's excessive, they feel annoyed. The goal is feedback that's immediate, proportional, and informative.

Feedback Principles

Every user action needs appropriate feedback. The type and intensity of feedback should match the importance of the action.

User ActionExpected FeedbackTimingExample
Hover over interactive elementVisual change (color, shadow, cursor)Immediate (<50ms)Button darkens slightly
Click/tap a buttonImmediate visual response< 100msButton press animation + spinner
Submit a formLoading indicator → result< 100ms to show loadingButton shows spinner, then "Saved" toast
Complete a taskConfirmation messageWithin 1s of completion"Order placed! Check your email for confirmation."
Error occursClear error explanationImmediately on detectionRed border on field + inline error message
Toggle a switchState change + position change< 100msToggle slides, color changes, label updates
Delete somethingConfirmation → success/undoImmediate confirmation dialog"Deleted. [Undo]" toast with 10s timer
Drag an elementVisual connection to pointerEvery frame (60fps)Element follows cursor with slight opacity change

The Feedback Hierarchy

Not all actions deserve the same level of feedback:

LOW IMPORTANCE                           HIGH IMPORTANCE
(Subtle feedback)                        (Prominent feedback)

  Hover    →    Focus    →    Click    →    Submit    →    Error/Success
  color       outline      animation     spinner/toast   modal/banner
  change      ring         + state        + message       + persist
                           change

Rule: The more consequential or irreversible the action, the more prominent the feedback.

Animation Guidelines

When to Animate

PurposeExampleAnimation Type
State changeButton hover, toggle switchColor transition, position shift
FeedbackForm submission, button pressScale, color, spinner
OrientationNew content appearingFade in, slide from direction of source
AttentionNew notification, required fieldPulse, shake, bounce
DelightTask completion, milestoneConfetti, checkmark draw, celebration
ContinuityPage transitions, expanding cardsSlide, scale, morph

When NOT to Animate

  • When it delays task completion. A 500ms animation before every page load is maddening
  • When it adds no information. Gratuitous bouncing logos
  • When the user is repeating an action. First expand of an accordion? Animate. Every subsequent expand? Consider faster/no animation.
  • When the user prefers reduced motion. Always respect prefers-reduced-motion

Animation Timing

Duration Guidelines

:root {
  /* Micro interactions (hover, focus, small state changes) */
  --duration-fast: 100ms;

  /* Standard interactions (dropdowns, tooltips, toggles) */
  --duration-normal: 200ms;

  /* Medium transitions (modals, sidebars, expanding content) */
  --duration-medium: 300ms;

  /* Large transitions (page transitions, full-screen overlays) */
  --duration-slow: 400ms;

  /* Complex animations (onboarding, celebrations) */
  --duration-complex: 500ms;
}

Rule of thumb: If the animation distance is small (toggle switch), use 100-200ms. If the distance is large (sidebar opening), use 300-400ms. Never exceed 500ms for functional animations.

Easing Functions

Easing makes animations feel natural. Linear motion feels robotic.

EasingCSS ValueWhen to Use
Ease-outcubic-bezier(0, 0, 0.2, 1)Elements entering the screen (fast start, slow end)
Ease-incubic-bezier(0.4, 0, 1, 1)Elements leaving the screen (slow start, fast end)
Ease-in-outcubic-bezier(0.4, 0, 0.2, 1)Elements moving between positions
Springcubic-bezier(0.34, 1.56, 0.64, 1)Playful, bouncy interactions (toggles, notifications)
LinearlinearProgress bars, color changes, opacity fades
/* Standard transitions */
.button {
  transition: background-color 150ms ease-out;
}

.modal {
  transition: opacity 200ms ease-out, transform 200ms ease-out;
}

.sidebar {
  transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1);
}

Animation Direction Principles

Animations should reflect the spatial model of the interface:

ActionAnimation Direction
Opening a dropdownSlides/fades down from trigger
Closing a dropdownFades/slides up toward trigger
Opening a modalFades in + scales up from center (or slides from bottom on mobile)
Closing a modalFades out + scales down
Navigating forwardContent slides left (new content enters from right)
Navigating backContent slides right (previous content enters from left)
Sidebar openingSlides in from the side it's positioned on
Toast notificationSlides in from top-right or bottom-center
Deleting an item from a listItem collapses/fades, siblings animate to fill the gap

Respecting Motion Preferences

Some users experience motion sickness, migraines, or vestibular disorders from animations. Always respect the system setting:

/* Reduce all animations for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

Better approach: Instead of removing all motion, replace motion-heavy animations with simple fades:

.modal {
  transition: opacity 200ms ease-out, transform 200ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
  .modal {
    transition: opacity 200ms ease-out;
    /* Remove transform animation, keep opacity fade */
  }
}

Microinteractions

Microinteractions are small, focused animations that provide feedback or delight. They're the difference between an interface that feels mechanical and one that feels alive.

Essential Microinteractions

InteractionImplementationPurpose
Button presstransform: scale(0.97) on :activeConfirms the tap/click registered
Toggle switchSlide circle + change color, 150msShows state change clearly
Checkbox checkCheckmark draws in (SVG animation)Satisfying confirmation
Like/favoriteHeart fills + brief scale-upEmotional reward, fun
Pull to refreshSpinner appears, content slides downIndicates refresh in progress
Input focusBorder color change + label floats upShows which field is active
Copy to clipboard"Copied!" tooltip for 2 secondsConfirms the action worked
Loading dotsThree dots pulsing sequentiallyIndicates something is happening
Form field validationGreen check fades in on validReal-time confirmation

Implementing a Button Press

.button {
  transition: transform 100ms ease-out, background-color 150ms ease-out;
}

.button:hover {
  background-color: var(--primary-600);
}

.button:active {
  transform: scale(0.97);
  background-color: var(--primary-700);
}

/* Subtle shadow lift on hover */
.card {
  transition: box-shadow 200ms ease-out, transform 200ms ease-out;
}

.card:hover {
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
  transform: translateY(-2px);
}

Implementing a Toggle Switch

.toggle {
  width: 48px;
  height: 28px;
  background: #CCC;
  border-radius: 14px;
  position: relative;
  cursor: pointer;
  transition: background-color 200ms ease-out;
}

.toggle.is-on {
  background: var(--primary-500);
}

.toggle-thumb {
  width: 24px;
  height: 24px;
  background: white;
  border-radius: 50%;
  position: absolute;
  top: 2px;
  left: 2px;
  transition: transform 200ms cubic-bezier(0.34, 1.56, 0.64, 1);
  /* Spring easing for a bouncy feel */
}

.toggle.is-on .toggle-thumb {
  transform: translateX(20px);
}

Loading States

Loading state design directly impacts perceived performance and user patience.

Loading State Decision Tree

Wait DurationUser PerceptionRecommended Feedback
< 300msInstantNo loading indicator needed. Disable the trigger.
300ms - 1sBrief pauseSubtle indicator: button spinner, content dim
1s - 3sNoticeable waitSkeleton screen or spinner
3s - 10sUser actively waitingProgress bar (determinate if possible)
> 10sRisk of abandonmentProgress bar + time estimate + allow background

Skeleton Screens

Skeletons show the shape of content before it loads. They're better than spinners because they set expectations about the layout.

Loading with spinner:              Loading with skeleton:
┌─────────────────────┐            ┌─────────────────────┐
│                     │            │ ░░░░░░░░░░░░░░░░░░  │
│                     │            │                     │
│      ⟳ Loading...   │            │ ░░░░░░░░░░░░░░░░░░░ │
│                     │            │ ░░░░░░░░░░░░░░░░░░░ │
│                     │            │ ░░░░░░░░░░░░        │
│                     │            │                     │
│                     │            │ ░░░░░░░░            │
└─────────────────────┘            └─────────────────────┘
User has no idea what's coming.    User sees the page structure.
/* Skeleton shimmer animation */
.skeleton {
  background: linear-gradient(
    90deg,
    #f0f0f0 25%,
    #e0e0e0 50%,
    #f0f0f0 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s ease-in-out infinite;
  border-radius: 4px;
}

@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

Optimistic UI

Show the result immediately, sync with the server in the background. If the server request fails, revert and show an error.

User clicks "Like":
1. Immediately: Heart fills, count increments (no server wait)
2. Background: POST request to server
3. If success: Nothing changes (already updated)
4. If failure: Revert heart, show "Couldn't save. Try again."

Best for: Likes, favorites, toggles, adding items to lists. Low-risk actions where server failure is rare.

Don't use for: Payments, deleting data, sending messages. High-risk actions where failure has consequences.

Scroll-Based Interactions

PatternDescriptionUse When
Sticky headerHeader stays visible while scrollingAlways on data-heavy pages
Scroll-to-topButton appears after scrolling downLong pages
Infinite scrollLoad more content as user scrollsSocial feeds, search results
Scroll-triggered animationsElements animate when they enter viewportMarketing/landing pages
ParallaxBackground moves slower than foregroundSparingly on marketing pages

Infinite Scroll vs. Pagination

ApproachProsConsBest For
Infinite scrollEffortless, great for browsingCan't bookmark position, footer unreachable, uses memorySocial feeds, image galleries
PaginationBookmarkable, predictable, good for SEORequires click, interrupts flowSearch results, products, data tables
"Load more" buttonUser-controlled, less jarring than paginationStill requires clickComments, reviews, secondary content

Gesture Design (Mobile)

GestureCommon ActionExample
TapPrimary selectionTap a card to open it
Long pressSecondary actions, context menuLong press to select/edit
Swipe horizontalNavigate between views, or reveal actionsSwipe to delete in email
Swipe downRefresh content or dismissPull to refresh
PinchZoom in/outMap, image zoom
Double tapQuick action or zoomDouble-tap to like (Instagram)

Important: Every gesture should have a non-gesture alternative (a visible button). Not all users discover gestures, and accessibility tools may not support them.

Common Mistakes

MistakeImpactFix
No loading indicationUsers don't know if their action registeredShow feedback within 100ms of any interaction
Animations longer than 400msInterface feels sluggishKeep functional animations under 300ms
Not respecting reduced motionMotion-sensitive users get sickImplement prefers-reduced-motion media query
Skeleton screens that don't match the layoutSets wrong expectations, causes layout shiftMatch skeleton shapes to actual content shapes
Spinner for every loading stateBoring, tells user nothing about progressUse skeletons for content, progress bars for uploads
Animating from the wrong directionFeels unnatural and disorientingElements should enter/exit from their logical source
No undo for destructive actionsUsers can't recover from mistakesProvide undo toast for 10 seconds, or use soft delete
Gesture-only interactionsSome users can't discover or perform gesturesAlways provide a visible button alternative

Key Takeaways

  • Every interaction needs feedback. The more important the action, the more prominent the feedback.
  • Keep functional animations under 300ms. Use 100-150ms for micro-interactions (hover, press).
  • Use ease-out for entering elements, ease-in for exiting, ease-in-out for moving between positions.
  • Always implement prefers-reduced-motion. Replace motion with simple fades.
  • Prefer skeleton screens over spinners for content loading. They set layout expectations.
  • Use optimistic UI for low-risk actions (likes, toggles) to make the interface feel instant.
  • Gestures should always have a visible button alternative for accessibility.
  • Loading state design: under 300ms = no indicator, 300ms-1s = subtle, 1-3s = skeleton, 3-10s = progress bar.