Introducing Nuvyx UI v1.0.0

Morphing Button

Interactive buttons with shape-changing animations and multi-state visual feedback.

Installation Guide

1

Copy Component Code

Morphing Button.tsx
TypeScript
1"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

NameTypeDefaultDescription
variantstringexpandButton transformation animation variant - (expand, collapse, rotate, skew, liquid, gradient, glow, pulse, reveal, bounce)
sizestringmdButton size - (xs, sm, md, lg, xl)
colorstringprimaryButton color theme - (primary, secondary, success, danger, warning, info, dark, slate, violet, indigo, teal, rose, amber, custom)
roundedstringmdButton corner radius - (none, sm, md, lg, full)
shadowstringmdButton shadow effect - (none, sm, md, lg, xl, inner, glow)
iconReact.ReactNodeundefinedOptional icon element to display in the button
iconPositionstringleftPosition of the icon relative to button text - (left, right, only)
classNamestring-Additional CSS classes to apply
onClickfunctionundefinedCallback function to be called when the button is clicked

Examples