Learn Figma prototyping essentials — connecting frames with hotspots, configuring interaction triggers and animations, and translating prototype flows into CSS transition and animation specifications for developers.
Figma's Prototype panel transforms static designs into clickable flows that demonstrate how a product works before a single line of code is written. For developers, the prototype is not just a communication tool — it is a specification for interactive behavior. The animation settings on each connection (trigger, timing, easing) are the designer's intent for how transitions should feel in the live product.
Reading prototypes efficiently means you can implement interactions correctly the first time, rather than building and then iterating based on vague feedback like 'make it feel smoother.' This lesson covers the Prototype panel structure, how to read connection specifications, and how to translate every animation setting to CSS.
The Prototype panel (right sidebar, Prototype tab) shows all interactive connections on the current frame. A connection is an arrow from a hotspot (interactive area) on one frame to a destination frame.
Connection properties:
Reading a prototype connection:
Select a frame → Prototype panel → click a connection arrow → properties appear in the panel.
Figma animation to CSS mapping:
1Instant → no transition (state change immediate)
2Dissolve → opacity transition
3Move In (Right) → transform: translateX(-100%) → translateX(0)
4Move Out (Right) → transform: translateX(0) → translateX(100%)
5Push (Right) → both old and new slide simultaneously
6Smart Animate → uses matching layer names, CSS best effortEasing to CSS timing function:
1Linear → linear
2Ease In → ease-in (or cubic-bezier(0.4, 0, 1, 1))
3Ease Out → ease-out (or cubic-bezier(0, 0, 0.2, 1))
4Ease In and Out → ease-in-out
5Gentle Spring → cubic-bezier(0.34, 1.56, 0.64, 1)
6Quick Spring → cubic-bezier(0.22, 1, 0.36, 1)
7Bouncy Spring → cubic-bezier(0.68, -0.55, 0.265, 1.55)A Figma prototype flow is a series of connected frames representing pages or states. Reading the flow helps you plan routing and component state management before coding.
Flow reading method:
Distinguishing page navigation from in-page state changes:
Flow annotation to routing structure:
1Figma flow:
2Landing → On Click CTA → Navigate To 'Sign Up' (Move In Right, 300ms, Ease Out)
3Sign Up → On Click Back → Navigate To 'Landing' (Move Out Right, 300ms, Ease Out)
4Sign Up → On Click 'Sign In' → Navigate To 'Sign In' (Move In Right, 300ms, Ease Out)
5Sign In → On Click Submit → Open Overlay 'Success Modal' (Dissolve, 200ms, Ease Out)1/* Page transition: Move In Right — new page slides in from right */
2/* Used by Next.js with framer-motion or CSS page transitions */
3.page-enter {
4 transform: translateX(100%);
5 opacity: 0;
6}
7.page-enter-active {
8 transform: translateX(0);
9 opacity: 1;
10 transition: transform 300ms cubic-bezier(0, 0, 0.2, 1), /* ease-out */
11 opacity 300ms cubic-bezier(0, 0, 0.2, 1);
12}
13.page-exit {
14 transform: translateX(0);
15 opacity: 1;
16}
17.page-exit-active {
18 transform: translateX(-100%);
19 opacity: 0;
20 transition: transform 300ms cubic-bezier(0, 0, 0.2, 1),
21 opacity 300ms cubic-bezier(0, 0, 0.2, 1);
22}
23
24/* Modal overlay: Dissolve — fade in */
25.modal-overlay {
26 opacity: 0;
27 transition: opacity 200ms ease-out;
28}
29.modal-overlay[aria-hidden='false'] {
30 opacity: 1;
31}A common Figma prototype pattern: button triggers 'Open Overlay' connecting to a dialog frame. The overlay settings specify the animation type and duration.
Figma prototype spec for a modal:
CSS implementation of this exact spec:
1/* Modal system matching Figma prototype specification */
2
3/* Scrim overlay: Figma background #00000066 */
4.modal-backdrop {
5 position: fixed;
6 inset: 0;
7 background: rgb(0 0 0 / 0.4); /* #00000066 = ~40% opacity */
8 z-index: 100;
9 display: grid;
10 place-items: center;
11 padding: 1rem;
12 /* Fade in */
13 opacity: 0;
14 transition: opacity 200ms ease-out;
15}
16.modal-backdrop.is-open {
17 opacity: 1;
18}
19
20/* Dialog: Move In from Bottom, 320ms, Quick Spring */
21.modal-dialog {
22 background: white;
23 border-radius: 1rem;
24 padding: 1.5rem;
25 width: 100%;
26 max-width: 480px;
27 box-shadow: 0 25px 50px rgb(0 0 0 / 0.25);
28 /* Start: below viewport */
29 transform: translateY(40px);
30 opacity: 0;
31 transition:
32 transform 320ms cubic-bezier(0.22, 1, 0.36, 1), /* Quick Spring */
33 opacity 200ms ease-out;
34}
35.modal-backdrop.is-open .modal-dialog {
36 transform: translateY(0);
37 opacity: 1;
38}
39
40/* Close animation: reverse — move out to bottom */
41.modal-backdrop.is-closing {
42 opacity: 0;
43}
44.modal-backdrop.is-closing .modal-dialog {
45 transform: translateY(40px);
46 opacity: 0;
47}The Quick Spring cubic-bezier(0.22, 1, 0.36, 1) creates a slight overshoot that feels springy and energetic without being distracting — matching Figma's spring easing behavior as closely as CSS can without the Web Animations API.
Figma's Smart Animate matches layers with the same name between two frames and animates their position, size, and properties between the two states. This is the equivalent of FLIP (First, Last, Invert, Play) animations in JavaScript or the View Transitions API in CSS.
Smart Animate in Figma: layers named 'Card/React' in Frame A animate to 'Card/React' in Frame B — transitioning position, size, and any changed properties.
CSS equivalents of Smart Animate:
1/* Enable cross-document transitions */
2@view-transition {
3 navigation: auto;
4}
5
6/* Assign view-transition-name to elements that should animate between pages */
7.course-card[data-id='react'] {
8 view-transition-name: course-card-react;
9 /* CSS handles the FLIP automatically when navigating */
10}1/* Expandable card that animates to detail view */
2.card {
3 border-radius: 0.75rem;
4 transition:
5 border-radius 300ms cubic-bezier(0.22, 1, 0.36, 1),
6 transform 300ms cubic-bezier(0.22, 1, 0.36, 1),
7 box-shadow 300ms cubic-bezier(0.22, 1, 0.36, 1);
8}
9.card.expanded {
10 border-radius: 0;
11 transform: scale(1.02);
12 box-shadow: 0 25px 50px rgb(0 0 0 / 0.2);
13}1/* Card flip animation (Figma: rotate transform between states) */
2@keyframes card-flip-out {
3 from { transform: rotateY(0); }
4 to { transform: rotateY(90deg); opacity: 0; }
5}
6@keyframes card-flip-in {
7 from { transform: rotateY(-90deg); opacity: 0; }
8 to { transform: rotateY(0); opacity: 1; }
9}
10.card.flipping-out {
11 animation: card-flip-out 200ms ease-in forwards;
12}
13.card.flipping-in {
14 animation: card-flip-in 200ms ease-out forwards;
15}A notification toast component implementing the exact animation from a Figma prototype: slide in from top-right, auto-dismiss after delay, slide out to right.
1<!--
2FIGMA PROTOTYPE SPEC for Toast notification:
3 Trigger: After Delay (0ms — fires immediately when shown)
4 Action: Navigate To 'Toast Visible'
5 Animation: Move In, Direction: Right, Duration: 350ms, Easing: Ease Out
6 Auto-dismiss: After Delay 3000ms → Navigate To 'Toast Hidden'
7 Dismiss animation: Move Out, Direction: Right, 250ms, Ease In
8
9 Toast frame dimensions: 320px wide, Hug height
10 Corner radius: 12px
11 Shadow: Elevation/3
12 Background: #1E293B (dark)
13 Text: white
14 Progress bar: brand blue, animates from 100% to 0% over 3000ms
15-->
16<!DOCTYPE html>
17<html lang="en">
18<head>
19 <meta charset="UTF-8">
20 <title>Toast Notification Demo</title>
21 <style>
22 *, *::before, *::after { box-sizing: border-box; }
23 body { margin: 0; font-family: system-ui, sans-serif;
24 background: #f1f5f9; min-height: 100vh;
25 display: flex; align-items: flex-start;
26 justify-content: center; padding: 2rem; }
27
28 /* Toast container: fixed, top-right */
29 .toast-region {
30 position: fixed;
31 top: 1.5rem;
32 right: 1.5rem;
33 z-index: 9999;
34 display: flex;
35 flex-direction: column;
36 gap: 0.75rem;
37 align-items: flex-end;
38 width: 320px;
39 pointer-events: none;
40 }
41
42 /* Individual toast */
43 .toast {
44 width: 320px;
45 background: #1e293b;
46 border-radius: 0.75rem;
47 box-shadow: 0 10px 15px rgb(0 0 0 / 0.1), 0 4px 6px rgb(0 0 0 / 0.05);
48 overflow: hidden;
49 pointer-events: auto;
50 /* Enter: Move In from Right → starts translated right, animates to 0 */
51 animation: toast-enter 350ms cubic-bezier(0, 0, 0.2, 1) forwards;
52 }
53
54 /* Figma: Move In from Right, 350ms, Ease Out */
55 @keyframes toast-enter {
56 from {
57 transform: translateX(calc(100% + 1.5rem));
58 opacity: 0;
59 }
60 to {
61 transform: translateX(0);
62 opacity: 1;
63 }
64 }
65
66 /* Figma: Move Out to Right, 250ms, Ease In */
67 @keyframes toast-exit {
68 from {
69 transform: translateX(0);
70 opacity: 1;
71 }
72 to {
73 transform: translateX(calc(100% + 1.5rem));
74 opacity: 0;
75 }
76 }
77
78 .toast.dismissing {
79 animation: toast-exit 250ms cubic-bezier(0.4, 0, 1, 1) forwards;
80 }
81
82 .toast-body {
83 display: flex;
84 align-items: flex-start;
85 gap: 0.75rem;
86 padding: 1rem;
87 }
88 .toast-icon {
89 width: 1.5rem;
90 height: 1.5rem;
91 border-radius: 50%;
92 background: #22c55e;
93 display: flex;
94 align-items: center;
95 justify-content: center;
96 flex-shrink: 0;
97 font-size: 0.75rem;
98 }
99 .toast-content { flex: 1; }
100 .toast-title {
101 font-size: 0.9375rem;
102 font-weight: 600;
103 color: white;
104 margin: 0 0 0.25rem;
105 }
106 .toast-message {
107 font-size: 0.8125rem;
108 color: rgb(255 255 255 / 0.7);
109 margin: 0;
110 }
111 .toast-close {
112 background: none;
113 border: none;
114 color: rgb(255 255 255 / 0.5);
115 cursor: pointer;
116 padding: 0.25rem;
117 border-radius: 0.25rem;
118 font-size: 1rem;
119 line-height: 1;
120 flex-shrink: 0;
121 align-self: flex-start;
122 transition: color 150ms ease;
123 }
124 .toast-close:hover { color: white; }
125
126 /* Progress bar: Figma auto-dismiss animation */
127 .toast-progress {
128 height: 3px;
129 background: rgb(255 255 255 / 0.1);
130 margin: 0;
131 }
132 .toast-progress-bar {
133 height: 100%;
134 background: #2563eb;
135 /* Animates from 100% to 0% over 3000ms */
136 animation: progress-drain 3000ms linear forwards;
137 }
138 @keyframes progress-drain {
139 from { width: 100%; }
140 to { width: 0%; }
141 }
142
143 /* Trigger button */
144 .trigger-btn {
145 padding: 0.75rem 1.5rem;
146 background: #2563eb;
147 color: white;
148 border: none;
149 border-radius: 0.5rem;
150 font-size: 1rem;
151 font-weight: 600;
152 cursor: pointer;
153 }
154 </style>
155</head>
156<body>
157 <button class="trigger-btn" type="button" onclick="showToast()">Show toast notification</button>
158
159 <div class="toast-region" aria-live="polite" aria-label="Notifications">
160 <!-- Toasts are injected here via JS -->
161 </div>
162
163 <script>
164 function showToast() {
165 const region = document.querySelector('.toast-region');
166 const toast = document.createElement('article');
167 toast.className = 'toast';
168 toast.setAttribute('role', 'alert');
169 toast.innerHTML = `180`;
181 region.appendChild(toast);
182 function dismiss() {
183 toast.classList.add('dismissing');
184 toast.addEventListener('animationend', () => toast.remove(), { once: true });
185 }
186 toast.querySelector('.toast-close').addEventListener('click', dismiss);
187 setTimeout(dismiss, 3000);
188 }
189 </script>
190</body>
191</html>The toast animation implements the Figma prototype spec precisely: toast-enter uses translateX(calc(100% + 1.5rem)) to start just off-screen to the right, then animates to translateX(0) over 350ms with ease-out cubic-bezier (0,0,0.2,1). The dismiss animation (toast-exit) reverses this with ease-in cubic-bezier (0.4,0,1,1) over 250ms. The progress-drain animation runs 3000ms linear from 100% to 0% — exactly matching the Figma 'After Delay: 3000ms' auto-dismiss prototype connection. The aria-live='polite' region ensures screen readers announce toast messages without interrupting other content.
The next lesson covers Advanced Auto Layout — responsive frames, min/max constraints, and how these map to CSS min-width, max-width, and clamp() for truly responsive components.
Your progress is saved in this browser, on this device only.