NEW
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
shadcn registry is live·
Learn more
Skip to content
Docs

Swap Text

A component that swaps text on click or hover.

requires interactionhoverclick
Loading...

Installation

CLI

pnpm dlx shadcn@latest add https://animata.design/r/text/swap-text.json

Manual

Add to your CSS

@theme {
  --ease-slow: cubic-bezier(.405, 0, .025, 1);
  --ease-minor-spring: cubic-bezier(0.18, 0.89, 0.82, 1.04);
}

Run the following command

It will create a new file called swap-text.tsx inside the components/animata/text directory.

touch components/animata/text/swap-text.tsx

Paste the code

Open the newly created file and paste the following code:

import { useState } from "react";
 
import { cn } from "@/lib/utils";
 
interface SwapTextProps extends React.ComponentPropsWithoutRef<"div"> {
  /**
   * The initial text to display.
   */
  initialText: string;
 
  /**
   * The final text to display.
   */
  finalText: string;
 
  /**
   * Whether the component should toggle on hover as well as click.
   */
  supportsHover?: boolean;
 
  /**
   * The class name for the text.
   */
  textClassName?: string;
 
  /**
   * The class name for the initial text.
   */
  initialTextClassName?: string;
 
  /**
   * The class name for the final text.
   */
  finalTextClassName?: string;
 
  /**
   * Whether to disable the click interaction.
   */
  disableClick?: boolean;
}
 
export default function SwapText({
  initialText,
  finalText,
  className,
  supportsHover = true,
  textClassName,
  initialTextClassName,
  finalTextClassName,
  disableClick,
  // The rest of the props are passed to the container div.
  ...props
}: SwapTextProps) {
  const [active, setActive] = useState(false);
  const common = "block transition duration-1000 ease-slow";
 
  const longWord = finalText.length > initialText.length ? finalText : null;
 
  return (
    <div {...props} className={cn("relative overflow-hidden text-foreground", className)}>
      <div
        className={cn("group/swap cursor-pointer select-none text-3xl font-bold", textClassName)}
        onClick={() => !disableClick && setActive((current) => !current)}
      >
        <span
          className={cn(common, initialTextClassName, {
            "flex flex-col": true,
            "-translate-y-full": active,
            "group-hover/swap:-translate-y-full": supportsHover,
          })}
        >
          {initialText}
          {
            /* Trick to make sure it can always fit all available words after transition as the second word is set to absolute*/
            Boolean(longWord?.length) && <span className="invisible h-0">{longWord}</span>
          }
        </span>
        <span
          className={cn(`${common} absolute top-full`, finalTextClassName, {
            "-translate-y-full": active,
            "group-hover/swap:-translate-y-full": supportsHover,
          })}
        >
          {finalText}
        </span>
      </div>
    </div>
  );
}

Credits

Built by hari