Rotates a set of words in a sentence or heading, like the framework names in this site's hero. Works in React, Vue, Svelte, Solid, Preact and vanilla JS.
- Flips on a timer, or on demand with
next,prevandgoTo - Sizes to the widest item, so the text around it never shifts
- Hides every item but the current one from assistive technology
- Renders the first item as plain text until hydrated, so server-rendered markup reads fine
- Respects reduced motion
Installation
in the preview, then copy the prompt into Claude Code, Cursor or another coding agent, or add the component yourself with the shadcn CLI or by hand.
Show prompt
Add the text flip from isbatak-labs to this project. It cycles through words or phrases in place, flipping the next one in as the current one flips out. Use the React version styled with Panda CSS and built on the Zag state machine. Docs: https://isbatak-labs.vercel.app/components/prototypes/text-flip.md 1. Install it with the shadcn CLI. Swap `pnpm dlx` for `npx`, `yarn dlx` or `bunx` to match the project's lockfile: pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/text-flip-basic-react-panda.json This installs `@isbatak/zag-text-flip` and `@zag-js/react`, plus `@isbatak/panda-text-flip` as a dev dependency, and adds the component to `src/components/text-flip-basic/`. If the CLI can't run here, install those packages yourself and write the files listed in https://isbatak-labs.vercel.app/r/text-flip-basic-react-panda.json (each entry has its content and target path). 2. Finish the setup: - Panda CSS: add `"@isbatak/panda-text-flip"` to the presets in the Panda config (or import `textFlipPreset` from it if the config already imports its presets), then run `panda codegen`. If the project doesn't use Panda CSS, stop and tell me to pick the CSS version in the docs instead. 3. Ask me where the text flip should go and which words it should cycle through, and suggest a spot if one stands out. Then render it there: copy the installed component, give it a name that fits, replace the items with mine (each item needs its zero-based `index`), and keep the timing options that fit (`interval`, `autoplay`). 4. Start the dev server, check that the words flip in place on a timer without shifting the text around them, then stop it. If you can't run it, tell me how to check it myself.
pnpm dlx shadcn@latest add https://isbatak-labs.vercel.app/r/text-flip-basic-react-panda.jsonThis installs the dependencies and adds the component to src/components/text-flip-basic/. It imports the recipe from styled-system/recipes.
Add "@isbatak/panda-text-flip" to presets in your Panda config, or import textFlipPreset from it and add that instead.
- Install the dependencies:
pnpm add @isbatak/zag-text-flip @zag-js/react pnpm add -D @isbatak/panda-text-flip Add
"@isbatak/panda-text-flip"topresetsin your Panda config, or importtextFlipPresetfrom it and add that instead.- Copy these files into
src/components/text-flip-basic/:"use client" import { css } from "styled-system/css" import { textFlip as textFlipRecipe } from "styled-system/recipes" import * as textFlip from "@isbatak/zag-text-flip" import { normalizeProps, useMachine } from "@zag-js/react" import { useId } from "react" const styles = textFlipRecipe() const classes = { heading: css({ textStyle: "3xl", fontWeight: "semibold", letterSpacing: "tight" }), } export function TextFlipBasic() { const service = useMachine(textFlip.machine, { id: useId(), }) const api = textFlip.connect(service, normalizeProps) return ( <p className={classes.heading}> Build for{" "} <span {...api.getRootProps()} className={styles.root}> <span {...api.getItemProps({ index: 0 })} className={styles.item}> React </span> <span {...api.getItemProps({ index: 1 })} className={styles.item}> Vue </span> <span {...api.getItemProps({ index: 2 })} className={styles.item}> Svelte </span> <span {...api.getItemProps({ index: 3 })} className={styles.item}> Solid </span> </span> </p> ) }
Examples
Scroll to an example to load it in the preview, and open the code view there for the full source. The settings button in the preview changes the example live, and the snippet under it follows. Each example is its own registry item, so you can add it with the CLI the same way.
Every item needs its zero-based index, in the order the items flip.
Controls
The machine exposes next, prev, goTo, play and pause, and playing tells you whether the timer is running.
Wire them to buttons to let people step through the items or stop the animation. With Ark UI, create the machine with
useTextFlip and pass it to TextFlip.RootProvider so the buttons outside the root can use it.
const service = useMachine(textFlip.machine, {})Timing
Set interval to the time in milliseconds each item stays before the next one flips in. The default is 2000. Set
autoplay to false to start paused, and defaultIndex to start on another item.
const service = useMachine(textFlip.machine, {
interval: 1000,
})Styling
The root is an inline grid and every item sits in the same cell, so the root is as wide as the widest item. Each item
gets data-state set to visible for the first item before any flip, entering for the current item, exiting for
the one it replaced and hidden for the rest. The Panda recipe animates entering and exiting with a blurred slide,
and turns the animation off when people prefer reduced motion.
Accessibility
Items that aren't shown get aria-hidden, so screen readers only read the current one, and they don't announce each
flip. When the words are decoration, like a list of supported frameworks, hide the root with aria-hidden and put the
full list in visually hidden text next to it.
Props
| Prop | Type | Default |
|---|---|---|
ids | ElementIds The ids of the elements in the text flip. Useful for composition. | — |
interval | number The time in milliseconds each item stays before the next one flips in. | 2000 |
autoplay | boolean Whether the items start flipping on their own. | true |
defaultIndex | number The index of the item shown first. | 0 |
onIndexChange | (details: IndexChangeDetails) => void Function called when the shown item changes. | — |
id | string The unique identifier of the machine. | — |
getRootNode | () => ShadowRoot | Document | Node A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron. | — |
API
| Property | Type |
|---|---|
index | number The index of the shown item. |
playing | boolean Whether the items are flipping on their own. |
next | () => void Show the next item, wrapping around after the last one. |
prev | () => void Show the previous item, wrapping around before the first one. |
goTo | (index: number) => void Show the item at the given index. |
play | () => void Start flipping on a timer. |
pause | () => void Stop flipping on a timer. |
getItemState | (props: ItemProps) => ItemState Returns whether an item is shown, flipping in, flipping out or hidden. |