Morphing Button
Interactive buttons with shape-changing animations and multi-state visual feedback.
Installation Guide
1
Copy Component Code
Morphing Button.tsx
TypeScript1"use client";
2import React, { useState } from "react";
3
4export type MorphingButtonProps = {
5 variant?:
6 | "expand"
7 | "collapse"
8 | "rotate"
9 | "skew"
10 | "liquid"
11 | "gradient"
12 | "glow"
13 | "pulse"
14 | "reveal"
15 | "bounce";
16 size?: "xs" | "sm" | "md" | "lg" | "xl";
17 color?:
18 | "primary"
19 | "secondary"
20 | "success"
21 | "danger"
22 | "warning"
23 | "info"
24 | "dark"
25 | "slate"
26 | "violet"
27 | "indigo"
28 | "teal"
29 | "rose"
30 | "amber"
31 | "custom";
32 rounded?: "none" | "sm" | "md" | "lg" | "full";
33 shadow?: "none" | "sm" | "md" | "lg" | "xl" | "inner" | "glow";
34 icon?: React.ReactNode;
35 iconPosition?: "left" | "right" | "only";
36 className?: string;
37 children: React.ReactNode;
38 onClick?: () => void;
39};
40
41export const MorphingButton = ({
42 variant = "expand",
43 size = "md",
44 color = "primary",
45 rounded = "md",
46 shadow = "md",
47 icon,
48 iconPosition = "left",
49 className = "",
50 children,
51 onClick,
52}: MorphingButtonProps) => {
53 const [isHovered, setIsHovered] = useState(false);
54 const [isActive, setIsActive] = useState(false);
55
56 const sizeClasses = {
57 xs: "h-8 px-3 text-xs",
58 sm: "h-10 px-4 text-sm",
59 lg: "h-14 px-6 text-lg",
60 xl: "h-16 px-8 text-xl",
61 md: "h-12 px-5 text-base",
62 };
63
64 const roundedClasses = {
65 none: "rounded-none",
66 sm: "rounded-sm",
67 md: "rounded-md",
68 lg: "rounded-lg",
69 full: "rounded-full",
70 };
71
72 const shadowClasses = {
73 none: "shadow-none",
74 sm: "shadow-sm",
75 md: "shadow",
76 lg: "shadow-lg",
77 xl: "shadow-xl",
78 inner: "shadow-inner",
79 glow: `shadow-lg ${
80 color === "primary"
81 ? "shadow-blue-400/40"
82 : color === "secondary"
83 ? "shadow-purple-400/40"
84 : color === "success"
85 ? "shadow-green-400/40"
86 : color === "danger"
87 ? "shadow-red-400/40"
88 : color === "warning"
89 ? "shadow-yellow-400/40"
90 : color === "info"
91 ? "shadow-cyan-400/40"
92 : color === "violet"
93 ? "shadow-violet-400/40"
94 : color === "indigo"
95 ? "shadow-indigo-400/40"
96 : color === "teal"
97 ? "shadow-teal-400/40"
98 : color === "rose"
99 ? "shadow-rose-400/40"
100 : color === "amber"
101 ? "shadow-amber-400/40"
102 : color === "slate"
103 ? "shadow-slate-400/40"
104 : color === "custom"
105 ? ""
106 : "shadow-gray-400/40"
107 }`,
108 };
109
110 const colorClasses = {
111 primary:
112 "bg-gradient-to-br from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white border border-blue-600",
113 secondary:
114 "bg-gradient-to-br from-purple-500 to-purple-600 hover:from-purple-600 hover:to-purple-700 text-white border border-purple-600",
115 success:
116 "bg-gradient-to-br from-emerald-500 to-emerald-600 hover:from-emerald-600 hover:to-emerald-700 text-white border border-emerald-600",
117 danger:
118 "bg-gradient-to-br from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white border border-red-600",
119 warning:
120 "bg-gradient-to-br from-amber-400 to-amber-500 hover:from-amber-500 hover:to-amber-600 text-white border border-amber-500",
121 info: "bg-gradient-to-br from-cyan-400 to-cyan-500 hover:from-cyan-500 hover:to-cyan-600 text-white border border-cyan-500",
122 dark: "bg-gradient-to-br from-gray-700 to-gray-800 hover:from-gray-800 hover:to-gray-900 text-white border border-gray-800",
123 slate:
124 "bg-gradient-to-br from-slate-600 to-slate-700 hover:from-slate-700 hover:to-slate-800 text-white border border-slate-700",
125 violet:
126 "bg-gradient-to-br from-violet-500 to-violet-600 hover:from-violet-600 hover:to-violet-700 text-white border border-violet-600",
127 indigo:
128 "bg-gradient-to-br from-indigo-500 to-indigo-600 hover:from-indigo-600 hover:to-indigo-700 text-white border border-indigo-600",
129 teal: "bg-gradient-to-br from-teal-500 to-teal-600 hover:from-teal-600 hover:to-teal-700 text-white border border-teal-600",
130 rose: "bg-gradient-to-br from-rose-500 to-rose-600 hover:from-rose-600 hover:to-rose-700 text-white border border-rose-600",
131 amber:
132 "bg-gradient-to-br from-amber-500 to-amber-600 hover:from-amber-600 hover:to-amber-700 text-white border border-amber-600",
133 custom: "",
134 };
135
136 const getIconContent = () => {
137 if (!icon) return null;
138 const spacingClass =
139 iconPosition === "only" ? "" : iconPosition === "left" ? "mr-2" : "ml-2";
140 return (
141 <span
142 className={`transition-transform duration-300 ${
143 isHovered ? "scale-110" : ""
144 } ${spacingClass}`}
145 >
146 {icon}
147 </span>
148 );
149 };
150
151 const renderContent = () => {
152 if (iconPosition === "only" && icon) {
153 return getIconContent();
154 }
155 return (
156 <>
157 {iconPosition === "left" && getIconContent()}
158 <span className="relative z-10">{children}</span>
159 {iconPosition === "right" && getIconContent()}
160 </>
161 );
162 };
163
164 const getVariantClasses = () => {
165 switch (variant) {
166 case "expand":
167 return `transition-all duration-300 ${
168 isHovered ? "px-8" : ""
169 } relative overflow-hidden
170 before:absolute before:inset-0 before:transition-all before:duration-300
171 before:bg-white before:opacity-0 ${
172 isActive ? "before:opacity-20 scale-95" : ""
173 }`;
174 case "collapse":
175 return `transition-all duration-300 transform ${
176 isHovered ? "scale-95" : "scale-100"
177 } ${isActive ? "scale-90" : ""}`;
178 case "rotate":
179 return `transition-all duration-300 transform ${
180 isHovered ? "rotate-2" : "rotate-0"
181 } ${isActive ? "rotate-4" : ""}`;
182 case "skew":
183 return `transition-all duration-300 transform ${
184 isHovered ? "skew-x-2" : "skew-x-0"
185 } ${isActive ? "skew-x-4" : ""}`;
186 case "liquid":
187 return `transition-all duration-300 ${
188 isHovered
189 ? rounded === "full"
190 ? "rounded-full"
191 : "rounded-2xl"
192 : roundedClasses[rounded]
193 }
194 ${isActive ? "rounded-full scale-95" : ""}`;
195 case "gradient":
196 if (color === "custom")
197 return `transition-all duration-500 ${
198 isActive ? "scale-95" : "scale-100"
199 }`;
200
201 return `transition-all duration-500 bg-gradient-to-r
202 from-${
203 color === "primary"
204 ? "blue-400"
205 : color === "secondary"
206 ? "purple-400"
207 : color === "success"
208 ? "emerald-400"
209 : color === "danger"
210 ? "red-400"
211 : color === "warning"
212 ? "amber-300"
213 : color === "info"
214 ? "cyan-300"
215 : color === "violet"
216 ? "violet-400"
217 : color === "indigo"
218 ? "indigo-400"
219 : color === "teal"
220 ? "teal-400"
221 : color === "rose"
222 ? "rose-400"
223 : color === "amber"
224 ? "amber-400"
225 : color === "slate"
226 ? "slate-500"
227 : "gray-600"
228 }
229 via-${
230 color === "primary"
231 ? "blue-500"
232 : color === "secondary"
233 ? "purple-500"
234 : color === "success"
235 ? "emerald-500"
236 : color === "danger"
237 ? "red-500"
238 : color === "warning"
239 ? "amber-400"
240 : color === "info"
241 ? "cyan-400"
242 : color === "violet"
243 ? "violet-500"
244 : color === "indigo"
245 ? "indigo-500"
246 : color === "teal"
247 ? "teal-500"
248 : color === "rose"
249 ? "rose-500"
250 : color === "amber"
251 ? "amber-500"
252 : color === "slate"
253 ? "slate-600"
254 : "gray-700"
255 }
256 to-${
257 color === "primary"
258 ? "blue-600"
259 : color === "secondary"
260 ? "purple-600"
261 : color === "success"
262 ? "emerald-600"
263 : color === "danger"
264 ? "red-600"
265 : color === "warning"
266 ? "amber-500"
267 : color === "info"
268 ? "cyan-500"
269 : color === "violet"
270 ? "violet-600"
271 : color === "indigo"
272 ? "indigo-600"
273 : color === "teal"
274 ? "teal-600"
275 : color === "rose"
276 ? "rose-600"
277 : color === "amber"
278 ? "amber-600"
279 : color === "slate"
280 ? "slate-700"
281 : "gray-800"
282 }
283 ${isHovered ? "brightness-110" : ""}
284 ${isActive ? "scale-95" : "scale-100"}
285 border-none
286 `;
287 case "glow":
288 if (color === "custom")
289 return `transition-all duration-300 ${
290 isActive ? "scale-95" : "scale-100"
291 }`;
292
293 return `transition-all duration-300
294 ${
295 isHovered
296 ? `shadow-lg shadow-${
297 color === "primary"
298 ? "blue-400/50"
299 : color === "secondary"
300 ? "purple-400/50"
301 : color === "success"
302 ? "emerald-400/50"
303 : color === "danger"
304 ? "red-400/50"
305 : color === "warning"
306 ? "amber-400/50"
307 : color === "info"
308 ? "cyan-400/50"
309 : color === "violet"
310 ? "violet-400/50"
311 : color === "indigo"
312 ? "indigo-400/50"
313 : color === "teal"
314 ? "teal-400/50"
315 : color === "rose"
316 ? "rose-400/50"
317 : color === "amber"
318 ? "amber-400/50"
319 : color === "slate"
320 ? "slate-400/50"
321 : "gray-400/50"
322 }`
323 : ""
324 }
325 ${isActive ? "scale-95" : "scale-100"}`;
326 case "pulse":
327 return `transition-all duration-300 ${isHovered ? "animate-pulse" : ""}
328 ${isActive ? "scale-95" : "scale-100"}`;
329 case "reveal":
330 return `transition-all duration-300 relative overflow-hidden
331 before:absolute before:inset-0 before:w-full before:h-full before:bg-white/20
332 before:translate-x-full before:skew-x-12 before:transition-transform before:duration-700
333 ${isHovered ? "before:-translate-x-full" : ""}
334 ${isActive ? "scale-95" : "scale-100"}`;
335 case "bounce":
336 return `transition-all duration-300
337 ${isHovered ? "animate-bounce" : ""}
338 ${isActive ? "scale-95" : "scale-100"}`;
339 default:
340 return "";
341 }
342 };
343
344 const getFocusRingColor = () => {
345 if (color === "custom") return "";
346
347 return `focus:ring-${
348 color === "primary"
349 ? "blue"
350 : color === "secondary"
351 ? "purple"
352 : color === "success"
353 ? "emerald"
354 : color === "danger"
355 ? "red"
356 : color === "warning"
357 ? "amber"
358 : color === "info"
359 ? "cyan"
360 : color === "violet"
361 ? "violet"
362 : color === "indigo"
363 ? "indigo"
364 : color === "teal"
365 ? "teal"
366 : color === "rose"
367 ? "rose"
368 : color === "amber"
369 ? "amber"
370 : color === "slate"
371 ? "slate"
372 : "gray"
373 }-400`;
374 };
375
376 const baseClasses = `
377 ${sizeClasses[size]}
378 ${
379 color !== "custom"
380 ? variant === "gradient"
381 ? ""
382 : colorClasses[color]
383 : ""
384 }
385 ${variant !== "liquid" ? roundedClasses[rounded] : ""}
386 ${
387 shadow !== "none" && color !== "custom"
388 ? shadowClasses[shadow]
389 : shadow !== "none" && color === "custom"
390 ? `shadow-${shadow}`
391 : ""
392 }
393 ${getVariantClasses()}
394 inline-flex items-center justify-center font-medium
395 focus:outline-none focus:ring-2 focus:ring-offset-1 ${getFocusRingColor()}
396 transition-all duration-300
397 backdrop-filter backdrop-blur-sm
398 ${className}
399 `;
400
401 const handleClick = () => {
402 onClick?.();
403 };
404
405 return (
406 <button
407 className={baseClasses}
408 onClick={handleClick}
409 onMouseEnter={() => setIsHovered(true)}
410 onMouseLeave={() => {
411 setIsHovered(false);
412 setIsActive(false);
413 }}
414 onMouseDown={() => setIsActive(true)}
415 onMouseUp={() => setIsActive(false)}
416 >
417 {renderContent()}
418 </button>
419 );
420};
421
422export default MorphingButton;
423
2
Final Steps
Update Import Paths
Make sure to update the import paths in the component code to match your project structure. For example, change @/components/ui/button
to match your UI components location.
Props
Name | Type | Default | Description |
---|---|---|---|
variant | string | expand | Button transformation animation variant - (expand, collapse, rotate, skew, liquid, gradient, glow, pulse, reveal, bounce) |
size | string | md | Button size - (xs, sm, md, lg, xl) |
color | string | primary | Button color theme - (primary, secondary, success, danger, warning, info, dark, slate, violet, indigo, teal, rose, amber, custom) |
rounded | string | md | Button corner radius - (none, sm, md, lg, full) |
shadow | string | md | Button shadow effect - (none, sm, md, lg, xl, inner, glow) |
icon | React.ReactNode | undefined | Optional icon element to display in the button |
iconPosition | string | left | Position of the icon relative to button text - (left, right, only) |
className | string | - | Additional CSS classes to apply |
onClick | function | undefined | Callback function to be called when the button is clicked |