# Text Flip

Animated text that cycles through items with a smooth flip transition.

```tsx
"use client"

import { motion } from "motion/react"

import { TextFlip } from "@/components/text-flip"

export function TextFlipDemo() {
  return (
    <div className="text-2xl font-medium text-muted-foreground">
      <span>I am a </span>
      <TextFlip as={motion.span} className="min-w-32 text-foreground">
        <span>Developer</span>
        <span>Designer</span>
        <span>Creator</span>
        <span>Builder</span>
      </TextFlip>
    </div>
  )
}

```

## Installation

<CodeTabs>
  <TabsListInstallType />

  <TabsContent value="cli">
    ```bash
    npx shadcn add @ncdai/text-flip
    ```
  </TabsContent>

  <TabsContent value="manual">
    <Steps>
      <Step>Install the following dependencies</Step>

      ```bash
      npm install motion clsx tailwind-merge
      ```

      <Step>Add a cn helper</Step>

      ```ts title="lib/utils.ts" showLineNumbers
      import type { ClassValue } from "clsx"
      import { clsx } from "clsx"
      import { twMerge } from "tailwind-merge"

      export const cn = (...inputs: ClassValue[]) => {
        return twMerge(clsx(inputs))
      }

      ```

      <Step>Copy and paste the following code into your project</Step>

      ```tsx title="components/text-flip.tsx" showLineNumbers
      "use client"

      import type { Transition, Variants } from "motion/react"
      import { AnimatePresence, motion } from "motion/react"
      import { Children, useEffect, useState } from "react"

      import { cn } from "@/lib/utils"

      const defaultVariants: Variants = {
        initial: { y: -8, opacity: 0 },
        animate: { y: 0, opacity: 1 },
        exit: { y: 8, opacity: 0 },
      }

      type MotionElement = typeof motion.p | typeof motion.span | typeof motion.code

      export type TextFlipProps = {
        /**
         * Motion element to render.
         * @defaultValue motion.p
         * */
        as?: MotionElement
        className?: string
        /** Array of children to cycle through. */
        children: React.ReactNode[]

        /**
         * Time in seconds between each flip.
         * @defaultValue 2
         * */
        interval?: number
        /**
         * Motion transition configuration.
         * @defaultValue { duration: 0.3 }
         * */
        transition?: Transition
        /** Motion variants for enter/exit animations. */
        variants?: Variants

        /** Called with the new index after each flip. */
        onIndexChange?: (index: number) => void
      }

      export function TextFlip({
        as: Component = motion.p,
        className,
        children,

        interval = 2,
        transition = { duration: 0.3 },
        variants = defaultVariants,

        onIndexChange,
      }: TextFlipProps) {
        const [currentIndex, setCurrentIndex] = useState(0)

        const items = Children.toArray(children)

        useEffect(() => {
          const timer = setInterval(() => {
            setCurrentIndex((prev) => {
              const next = (prev + 1) % items.length
              onIndexChange?.(next)
              return next
            })
          }, interval * 1000)

          return () => clearInterval(timer)
        }, [items.length, interval, onIndexChange])

        return (
          <AnimatePresence mode="wait" initial={false}>
            <Component
              key={currentIndex}
              className={cn("inline-block", className)}
              initial="initial"
              animate="animate"
              exit="exit"
              transition={transition}
              variants={variants}
            >
              {items[currentIndex]}
            </Component>
          </AnimatePresence>
        )
      }

      ```

      <Step>Update the import paths to match your project setup</Step>
    </Steps>
  </TabsContent>
</CodeTabs>

## Usage

```tsx
import { TextFlip } from "@/components/text-flip"
```

```tsx
<TextFlip>
  <span>Developer</span>
  <span>Designer</span>
  <span>Creator</span>
</TextFlip>
```

## API Reference

### TextFlip

<TypeTable
  id="type-table-text-flip.tsx-TextFlipProps"
  type={{
  "id": "text-flip.tsx-TextFlipProps",
  "name": "TextFlipProps",
  "description": "",
  "entries": [
    {
      "name": "as",
      "description": "Motion element to render.",
      "tags": [
        {
          "name": "defaultValue",
          "text": "motion.p"
        }
      ],
      "type": "MotionElement | undefined",
      "simplifiedType": "ForwardRefComponent<object, HTMLMotionProps<\"code\">> | ForwardRefComponent<object, HTMLMotionProps<\"span\">> | ForwardRefComponent<object, HTMLMotionProps<\"p\">>",
      "required": false,
      "deprecated": false
    },
    {
      "name": "className",
      "description": "",
      "tags": [],
      "type": "string | undefined",
      "simplifiedType": "string",
      "required": false,
      "deprecated": false
    },
    {
      "name": "children",
      "description": "Array of children to cycle through.",
      "tags": [],
      "type": "ReactNode[]",
      "simplifiedType": "array",
      "required": true,
      "deprecated": false
    },
    {
      "name": "interval",
      "description": "Time in seconds between each flip.",
      "tags": [
        {
          "name": "defaultValue",
          "text": "2"
        }
      ],
      "type": "number | undefined",
      "simplifiedType": "number",
      "required": false,
      "deprecated": false
    },
    {
      "name": "transition",
      "description": "Motion transition configuration.",
      "tags": [
        {
          "name": "defaultValue",
          "text": "{ duration: 0.3 }"
        }
      ],
      "type": "Transition | undefined",
      "simplifiedType": "TransitionWithValueOverrides<any> | object",
      "required": false,
      "deprecated": false
    },
    {
      "name": "variants",
      "description": "Motion variants for enter/exit animations.",
      "tags": [],
      "type": "Variants | undefined",
      "simplifiedType": "object",
      "required": false,
      "deprecated": false
    },
    {
      "name": "onIndexChange",
      "description": "Called with the new index after each flip.",
      "tags": [],
      "type": "((index: number) => void) | undefined",
      "simplifiedType": "function",
      "required": false,
      "deprecated": false
    }
  ]
}}
/>


Last updated on February 20, 2026