Search

Ctrl + K

Spinner

This document outlines the steps to create Spinner component styled withTailwind CSS and using some npm dependency libraries.

Prerequisites

  • Node.js and npm installed on your machine.
  • Tailwind CSS installed in your project.
  • CVA(class-variance-authority) is a utility for managing CSS class names based on various conditions.
  • clsx is a tiny utility for constructing className strings conditionally.
Step 1: Create Tailwind config
1// tailwind.config.js
2module.exports = {
3  ...
4  theme: {
5    ...
6    extend: {
7      ...
8      keyframes: {
9        pulse: {
10          "0%": {
11            transform: "scale(0)",
12            opacity: "0.5",
13          },
14          "50%": {
15            transform: "scale(1)",
16            opacity: "1",
17          },
18          "100%": {
19            transform: "scale(0)",
20            opacity: "0.5",
21          },
22        },
23        bounce: {
24          from: { transform: "translateY(-10px)" },
25          to: { transform: "translateY(10px)" },
26        },
27        fade: {
28          from: { opacity: "1" },
29          to: { opacity: "0" },
30        },
31        scale: {
32          "0%": {
33            transform: "scaleY(0.4)",
34          },
35          "20%": {
36            transform: "scaleY(1)",
37          },
38          "40%": {
39            transform: "scaleY(0.4)",
40          },
41          "100%": {
42            transform: "scaleY(0.4)",
43          },
44        },
45        "clip-loader": {
46          "100%": {
47            transform: "rotate(360deg)",
48          },
49        },
50        "clip-circle": {
51          "0%": {
52            strokeDasharray: "1,200",
53            strokeDashoffset: "0",
54          },
55          "50%": {
56            strokeDasharray: "90,200",
57            strokeDashoffset: "-35px",
58          },
59          "100%": {
60            strokeDashoffset: "-125px",
61          },
62        },
63      },
64      animation: {
65        pulse: "pulse 1.111s ease-in-out infinite",
66        bounce: "bounce 0.5s ease-in infinite alternate",
67        fade: "fade 1s infinite linear",
68        scale: "scale 0.9s ease-in-out infinite",
69        "clip-loader": "clip-loader 2s linear infinite",
70        "clip-circle": "clip-circle 1.5s ease-in-out infinite",
71      },
72    },
73  },
74  plugins: [require("tailwindcss-animate")]
75};
76
Step 2: Create Bounce component
1// bounce.tsx
2import clsx from "clsx";
3import { cva, type VariantProps } from "class-variance-authority";
4
5const sizeVariants = cva("grid grid-cols-4", {
6  variants: {
7    size: {
8      small: "w-[2.25rem] h-6 gap-1",
9      medium: "w-[2.875rem] h-8 gap-1.5",
10      large: "w-[4.5rem] h-12 gap-2",
11    },
12  },
13});
14
15interface Props extends VariantProps<typeof sizeVariants> {
16  color: string;
17  className?: string;
18}
19
20const Bounce: React.FC<Props> = ({ color, size, className }) => {
21  return (
22    <div className={clsx(sizeVariants({ size }), className)}>
23      {new Array(4).fill(null).map((_, index) => (
24        <div
25          key={index}
26          style={{
27            backgroundColor: color,
28            animationDelay: `${index * 0.16}s`,
29          }}
30          className="my-auto aspect-square flex-shrink-0 animate-bounce rounded-full first-letter:flex-grow-0"
31        />
32      ))}
33    </div>
34  );
35};
36
37export default Bounce;
38
Step 3: Create Clip component
1// clip.tsx
2import clsx from "clsx";
3import { cva, type VariantProps } from "class-variance-authority";
4
5const sizeVariants = cva("animate-clip-loader", {
6  variants: {
7    size: {
8      small: "w-6 h-6",
9      medium: "w-8 h-8",
10      large: "w-12 h-12",
11    },
12  },
13});
14
15interface Props extends VariantProps<typeof sizeVariants> {
16  color: string;
17  className?: string;
18}
19
20const Clip: React.FC<Props> = ({ color, size, className }) => {
21  return (
22    <svg
23      viewBox="25 25 50 50"
24      className={clsx(sizeVariants({ size }), className)}
25    >
26      <circle
27        r="20"
28        cy="50"
29        cx="50"
30        fill="none"
31        stroke={color}
32        strokeWidth={4}
33        strokeDasharray="1,200"
34        strokeDashoffset={0}
35        strokeLinecap="round"
36        className="animate-clip-circle"
37      ></circle>
38    </svg>
39  );
40};
41
42export default Clip; 
43
Step 4: Create Fade component
1// fade.tsx
2import clsx from "clsx";
3import { cva, type VariantProps } from "class-variance-authority";
4
5const sizeVariants = cva("relative inline-block w-[1em] h-[1em]", {
6  variants: {
7    size: {
8      small: "text-[1.5rem]",
9      medium: "text-[2rem]",
10      large: "text-[3rem]",
11    },
12  },
13});
14
15interface Props extends VariantProps<typeof sizeVariants> {
16  color: string;
17  className?: string;
18}
19
20const Fade: React.FC<Props> = ({ color, size, className }) => {
21  return (
22    <div className={clsx(sizeVariants({ size }), className)}>
23      {new Array(12).fill(null).map((_, index) => (
24        <div
25          key={index}
26          style={{
27            backgroundColor: color,
28            animationDelay: `${index * 0.083}s`,
29            transform: `rotate(${index * 30}deg)`,
30          }}
31          className="animate-fade absolute bottom-0 left-[0.4629em] h-[0.2777em] w-[0.074em] origin-[center_-0.2222em] rounded-[0.0555em]"
32        />
33      ))}
34    </div>
35  );
36};
37
38export default Fade;
39
Step 5: Create Pulse component
1// pulse.tsx
2import clsx from "clsx";
3import { cva, type VariantProps } from "class-variance-authority";
4
5const sizeVariants = cva("relative flex items-center", {
6  variants: {
7    size: {
8      small: "w-6 h-6",
9      medium: "w-8 h-8",
10      large: "w-12 h-12",
11    },
12  },
13});
14
15interface Props extends VariantProps<typeof sizeVariants> {
16  color: string;
17  className?: string;
18}
19
20const Pulse: React.FC<Props> = ({ color, size, className }) => {
21  return (
22    <div className={clsx(sizeVariants({ size }), className)}>
23      {new Array(8).fill(null).map((_, index) => (
24        <div
25          key={index}
26          style={{ transform: `rotate(${index * 45}deg)` }}
27          className="absolute left-0 top-0 flex h-full w-full items-center"
28        >
29          <div
30            style={{
31              backgroundColor: color,
32              animationDelay: `${(8 - index) * 0.125}s`,
33            }}
34            className="h-1/5 w-1/5 scale-0 animate-pulse rounded-full opacity-50"
35          />
36        </div>
37      ))}
38    </div>
39  );
40};
41
42export default Pulse;
43
Step 6: Create Scale component
1// scale.tsx
2import clsx from "clsx";
3import { cva, type VariantProps } from "class-variance-authority";
4
5const sizeVariants = cva("flex justify-center items-center gap-1.5", {
6  variants: {
7    size: {
8      small: "w-6 h-6",
9      medium: "w-8 h-8",
10      large: "w-12 h-12",
11    },
12  },
13});
14
15const barVariants = cva("flex-shrink-0 h-full animate-scale", {
16  variants: {
17    size: {
18      small: "w-[2px]",
19      medium: "w-[3px]",
20      large: "w-[4px]",
21    },
22  },
23});
24
25interface Props extends VariantProps<typeof sizeVariants> {
26  color: string;
27  className?: string;
28}
29
30const Scale: React.FC<Props> = ({ color, size, className }) => {
31  return (
32    <div className={clsx(sizeVariants({ size }), className)}>
33      {new Array(5).fill(null).map((_, index) => (
34        <div
35          key={index}
36          style={{
37            backgroundColor: color,
38            animationDelay: `-${index ? 0.9 - index * 0.1 : 0}s`,
39            transform: `rotate(${index * 30}deg)`,
40          }}
41          className={barVariants({ size })}
42        />
43      ))}
44    </div>
45  );
46};
47
48export default Scale;
49
Step 7: Create Spinner component
1// spinner.component.tsx
2import { cva, type VariantProps } from "class-variance-authority";
3import Bounce from "./bounce";
4import Clip from "./clip";
5import Fade from "./fade";
6import Pulse from "./pulse";
7import Scale from "./scale";
8
9const colorVariants = cva("", {
10  variants: {
11    color: {
12      primary: "#3b82f6",
13      secondary: "#6b7280",
14      success: "#22c55e",
15      danger: "#ef4444",
16      warning: "#eab308",
17      info: "#06b6d4",
18      light: "#d1d5db",
19      dark: "#000000",
20    },
21  },
22});
23
24export interface SpinnerProps extends VariantProps<typeof colorVariants> {
25  variant?: "clip" | "fade" | "scale" | "bounce" | "pulse";
26  size?: "small" | "medium" | "large";
27  className?: string;
28}
29
30const Spinner: React.FC<SpinnerProps> = ({
31  variant = "clip",
32  color = "primary",
33  size = "medium",
34  className,
35}) => {
36  const colorValue = colorVariants({ color });
37
38  return variant === "clip" ? (
39    <Clip size={size} color={colorValue} className={className} />
40  ) : variant === "fade" ? (
41    <Fade size={size} color={colorValue} className={className} />
42  ) : variant === "scale" ? (
43    <Scale size={size} color={colorValue} className={className} />
44  ) : variant === "bounce" ? (
45    <Bounce size={size} color={colorValue} className={className} />
46  ) : variant === "pulse" ? (
47    <Pulse size={size} color={colorValue} className={className} />
48  ) : null;
49};
50
51export default Spinner;
52