Pure TailwindMIT licensed

Tailwind Text Flip Button Hover with 3D Rotation

The label pivots away on a horizontal axis and a second message pivots in behind it. The port's structural gain is group: the button declares it once and both panels read group-hover:, so the two halves of the flip are described on the elements that actually rotate.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

The code

<section class="tab-10 w-full min-h-svh grid place-items-center bg-tab10-bg font-tab10-sans px-6 py-10">
  <div class="flex flex-col items-center gap-8">
    <p class="text-[0.78rem] tracking-[0.14em] uppercase text-tab10-mut m-0">Hover to flip text</p>
    <div class="flex flex-wrap justify-center gap-5">
      <button type="button" class="group p-0 bg-tab10-indigo rounded-[10px] cursor-pointer perspective-[600px] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab10-indigo">
        <span class="relative block w-36 h-12 overflow-hidden transform-3d">
          <span class="absolute inset-0 flex items-center justify-center text-[0.95rem] font-bold tracking-[0.04em] text-white whitespace-nowrap backface-hidden transition-transform duration-[350ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-hover:-translate-y-full group-hover:rotate-x-90 group-focus-visible:-translate-y-full group-focus-visible:rotate-x-90 motion-reduce:transition-opacity motion-reduce:group-hover:translate-y-0 motion-reduce:group-hover:rotate-x-0 motion-reduce:group-hover:opacity-0">View Pricing</span>
          <span class="absolute inset-0 flex items-center justify-center text-[0.95rem] font-bold tracking-[0.04em] text-white whitespace-nowrap backface-hidden translate-y-full -rotate-x-90 transition-transform duration-[350ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-hover:translate-y-0 group-hover:rotate-x-0 group-focus-visible:translate-y-0 group-focus-visible:rotate-x-0 motion-reduce:translate-y-0 motion-reduce:rotate-x-0 motion-reduce:opacity-0 motion-reduce:transition-opacity motion-reduce:group-hover:opacity-100">From $9/mo &rarr;</span>
        </span>
      </button>
      <button type="button" class="group p-0 bg-tab10-teal rounded-[10px] cursor-pointer perspective-[600px] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab10-teal">
        <span class="relative block w-36 h-12 overflow-hidden transform-3d">
          <span class="absolute inset-0 flex items-center justify-center text-[0.95rem] font-bold tracking-[0.04em] text-white whitespace-nowrap backface-hidden transition-transform duration-[350ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-hover:-translate-y-full group-hover:rotate-x-90 group-focus-visible:-translate-y-full group-focus-visible:rotate-x-90 motion-reduce:transition-opacity motion-reduce:group-hover:translate-y-0 motion-reduce:group-hover:rotate-x-0 motion-reduce:group-hover:opacity-0">Follow Us</span>
          <span class="absolute inset-0 flex items-center justify-center text-[0.95rem] font-bold tracking-[0.04em] text-white whitespace-nowrap backface-hidden translate-y-full -rotate-x-90 transition-transform duration-[350ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-hover:translate-y-0 group-hover:rotate-x-0 group-focus-visible:translate-y-0 group-focus-visible:rotate-x-0 motion-reduce:translate-y-0 motion-reduce:rotate-x-0 motion-reduce:opacity-0 motion-reduce:transition-opacity motion-reduce:group-hover:opacity-100">@codefronts</span>
        </span>
      </button>
      <button type="button" class="group p-0 bg-tab10-rose rounded-[10px] cursor-pointer perspective-[600px] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab10-rose">
        <span class="relative block w-36 h-12 overflow-hidden transform-3d">
          <span class="absolute inset-0 flex items-center justify-center text-[0.95rem] font-bold tracking-[0.04em] text-white whitespace-nowrap backface-hidden transition-transform duration-[350ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-hover:-translate-y-full group-hover:rotate-x-90 group-focus-visible:-translate-y-full group-focus-visible:rotate-x-90 motion-reduce:transition-opacity motion-reduce:group-hover:translate-y-0 motion-reduce:group-hover:rotate-x-0 motion-reduce:group-hover:opacity-0">Join Beta</span>
          <span class="absolute inset-0 flex items-center justify-center text-[0.95rem] font-bold tracking-[0.04em] text-white whitespace-nowrap backface-hidden translate-y-full -rotate-x-90 transition-transform duration-[350ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-hover:translate-y-0 group-hover:rotate-x-0 group-focus-visible:translate-y-0 group-focus-visible:rotate-x-0 motion-reduce:translate-y-0 motion-reduce:rotate-x-0 motion-reduce:opacity-0 motion-reduce:transition-opacity motion-reduce:group-hover:opacity-100">Spots Left: 3</span>
        </span>
      </button>
    </div>
  </div>
</section>
@import "tailwindcss";

@theme {
  --color-tab10-bg: #0f0c1f;
  --color-tab10-bg: oklch(0.14 0.04 285);
  --color-tab10-mut: #818cf8;
  --color-tab10-mut: oklch(0.7 0.14 279);
  --color-tab10-indigo: #4f46e5;
  --color-tab10-indigo: oklch(0.51 0.24 277);
  --color-tab10-teal: #0d9488;
  --color-tab10-teal: oklch(0.56 0.11 184);
  --color-tab10-rose: #e11d48;
  --color-tab10-rose: oklch(0.57 0.22 13);
  --font-tab10-sans: 'Space Grotesk', system-ui, sans-serif;
}

.tab-10 {
  width: 100%;
  min-height: 100svh;
}

.tab-10 ::selection {
  background: var(--color-tab10-indigo);
  color: #fff;
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>

Compiles in the browser — perfect for a CodePen or a quick test, not for production.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@import "tailwindcss";

@theme {
  --color-tab10-bg: #0f0c1f;
  --color-tab10-bg: oklch(0.14 0.04 285);
  --color-tab10-mut: #818cf8;
  --color-tab10-mut: oklch(0.7 0.14 279);
  --color-tab10-indigo: #4f46e5;
  --color-tab10-indigo: oklch(0.51 0.24 277);
  --color-tab10-teal: #0d9488;
  --color-tab10-teal: oklch(0.56 0.11 184);
  --color-tab10-rose: #e11d48;
  --color-tab10-rose: oklch(0.57 0.22 13);
  --font-tab10-sans: 'Space Grotesk', system-ui, sans-serif;
}

.tab-10 {
  width: 100%;
  min-height: 100svh;
}

.tab-10 ::selection {
  background: var(--color-tab10-indigo);
  color: #fff;
}
3Fonts used by this demooptional — skip if you have your own type stack
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap">

What changed in the port

Which parts became utilities, which stayed CSS, and why.

  • perspective-*, transform-3d, backface-hidden and rotate-x-* are all real v4 utilities — this demo needs no arbitrary properties at all, which was not true in v3.
  • group is what makes the flip readable: each panel's own class list holds its resting transform and its hovered transform, instead of two parent-keyed rules.
  • UI change from the CSS original: group-focus-visible: twins on both panels. The back label carries real information, so hiding it from keyboard users was a content problem.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Text Flip Button Hover with 3D Rotation
Source: https://codefronts.com/motion/tailwind-css-animated-buttons/tailwind-text-flip-button-hover/

The label pivots away on a horizontal axis and a second message pivots in behind it. The port's structural gain is group: the button declares it once and both panels read group-hover:, so the two halves of the flip are described on the elements that actually rotate.
## HTML
```html
<section class="tab-10 w-full min-h-svh grid place-items-center bg-tab10-bg font-tab10-sans px-6 py-10">
  <div class="flex flex-col items-center gap-8">
    <p class="text-[0.78rem] tracking-[0.14em] uppercase text-tab10-mut m-0">Hover to flip text</p>
    <div class="flex flex-wrap justify-center gap-5">
      <button type="button" class="group p-0 bg-tab10-indigo rounded-[10px] cursor-pointer perspective-[600px] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab10-indigo">
        <span class="relative block w-36 h-12 overflow-hidden transform-3d">
          <span class="absolute inset-0 flex items-center justify-center text-[0.95rem] font-bold tracking-[0.04em] text-white whitespace-nowrap backface-hidden transition-transform duration-[350ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-hover:-translate-y-full group-hover:rotate-x-90 group-focus-visible:-translate-y-full group-focus-visible:rotate-x-90 motion-reduce:transition-opacity motion-reduce:group-hover:translate-y-0 motion-reduce:group-hover:rotate-x-0 motion-reduce:group-hover:opacity-0">View Pricing</span>
          <span class="absolute inset-0 flex items-center justify-center text-[0.95rem] font-bold tracking-[0.04em] text-white whitespace-nowrap backface-hidden translate-y-full -rotate-x-90 transition-transform duration-[350ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-hover:translate-y-0 group-hover:rotate-x-0 group-focus-visible:translate-y-0 group-focus-visible:rotate-x-0 motion-reduce:translate-y-0 motion-reduce:rotate-x-0 motion-reduce:opacity-0 motion-reduce:transition-opacity motion-reduce:group-hover:opacity-100">From $9/mo &rarr;</span>
        </span>
      </button>
      <button type="button" class="group p-0 bg-tab10-teal rounded-[10px] cursor-pointer perspective-[600px] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab10-teal">
        <span class="relative block w-36 h-12 overflow-hidden transform-3d">
          <span class="absolute inset-0 flex items-center justify-center text-[0.95rem] font-bold tracking-[0.04em] text-white whitespace-nowrap backface-hidden transition-transform duration-[350ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-hover:-translate-y-full group-hover:rotate-x-90 group-focus-visible:-translate-y-full group-focus-visible:rotate-x-90 motion-reduce:transition-opacity motion-reduce:group-hover:translate-y-0 motion-reduce:group-hover:rotate-x-0 motion-reduce:group-hover:opacity-0">Follow Us</span>
          <span class="absolute inset-0 flex items-center justify-center text-[0.95rem] font-bold tracking-[0.04em] text-white whitespace-nowrap backface-hidden translate-y-full -rotate-x-90 transition-transform duration-[350ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-hover:translate-y-0 group-hover:rotate-x-0 group-focus-visible:translate-y-0 group-focus-visible:rotate-x-0 motion-reduce:translate-y-0 motion-reduce:rotate-x-0 motion-reduce:opacity-0 motion-reduce:transition-opacity motion-reduce:group-hover:opacity-100">@codefronts</span>
        </span>
      </button>
      <button type="button" class="group p-0 bg-tab10-rose rounded-[10px] cursor-pointer perspective-[600px] focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-tab10-rose">
        <span class="relative block w-36 h-12 overflow-hidden transform-3d">
          <span class="absolute inset-0 flex items-center justify-center text-[0.95rem] font-bold tracking-[0.04em] text-white whitespace-nowrap backface-hidden transition-transform duration-[350ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-hover:-translate-y-full group-hover:rotate-x-90 group-focus-visible:-translate-y-full group-focus-visible:rotate-x-90 motion-reduce:transition-opacity motion-reduce:group-hover:translate-y-0 motion-reduce:group-hover:rotate-x-0 motion-reduce:group-hover:opacity-0">Join Beta</span>
          <span class="absolute inset-0 flex items-center justify-center text-[0.95rem] font-bold tracking-[0.04em] text-white whitespace-nowrap backface-hidden translate-y-full -rotate-x-90 transition-transform duration-[350ms] ease-[cubic-bezier(0.4,0,0.2,1)] group-hover:translate-y-0 group-hover:rotate-x-0 group-focus-visible:translate-y-0 group-focus-visible:rotate-x-0 motion-reduce:translate-y-0 motion-reduce:rotate-x-0 motion-reduce:opacity-0 motion-reduce:transition-opacity motion-reduce:group-hover:opacity-100">Spots Left: 3</span>
        </span>
      </button>
    </div>
  </div>
</section>
```
## CSS
```css
@import "tailwindcss";

@theme {
  --color-tab10-bg: #0f0c1f;
  --color-tab10-bg: oklch(0.14 0.04 285);
  --color-tab10-mut: #818cf8;
  --color-tab10-mut: oklch(0.7 0.14 279);
  --color-tab10-indigo: #4f46e5;
  --color-tab10-indigo: oklch(0.51 0.24 277);
  --color-tab10-teal: #0d9488;
  --color-tab10-teal: oklch(0.56 0.11 184);
  --color-tab10-rose: #e11d48;
  --color-tab10-rose: oklch(0.57 0.22 13);
  --font-tab10-sans: 'Space Grotesk', system-ui, sans-serif;
}

.tab-10 {
  width: 100%;
  min-height: 100svh;
}

.tab-10 ::selection {
  background: var(--color-tab10-indigo);
  color: #fff;
}
```

How this works

Two panels, one pivot. The front sits at identity; the back waits at translate-y-full rotate-x-[-90deg]. On hover the front rotates up and out while the back rotates down into place, both on the same duration and curve, so they read as one rigid object turning.

group replaces a descendant selector. The CSS version wrote .btn:hover .front and .btn:hover .back — two rules pointing inward. Tailwind puts group on the button and group-hover: on each panel, so each panel's resting and hovered transform sit in the same class list.

Focus flips too. group-focus-visible: mirrors every group-hover:, which the original did not do — the second message was mouse-only, and on a button whose back label carries real content ("From $9/mo") that is a content bug, not a polish issue.

Make it yours

  • Horizontal flip: swap rotate-x-* for rotate-y-* and the translate-y offsets for translate-x.
  • Depth: perspective-[600px] → perspective-[1200px] for a flatter, more corporate turn.
  • Put an icon in the back panel — it is ordinary markup, not a content string.

Gotchas — read before shipping

  • Drop backface-hidden and some engines render a mirrored ghost of the front panel through the back at 45°.
  • The panel stage needs a fixed height and width; a 3D flip cannot resolve against auto sizing without jumping.
  • If the back label is longer than the front, the button jumps width mid-flip unless the stage has an explicit w-* — which is why the stage here is w-36.

Browser support

ChromeSafariFirefoxEdge
111+16.4+128+111+

3D transforms with backface-visibility are universally supported; the floor is Tailwind v4's generated sheet.

Techniques used in this demo

Search CodeFronts

Loading…